From 73001bc42ec9064e383d5bc0a71d23730629c148 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Oct 04 2016 17:47:41 +0000 Subject: [PATCH 1/7] Add close_status to issues The list of status is a property of the project and can be used to specify why/how a ticket was closed. --- diff --git a/alembic/versions/644ef887bb6f_add_close_status.py b/alembic/versions/644ef887bb6f_add_close_status.py new file mode 100644 index 0000000..9a20a41 --- /dev/null +++ b/alembic/versions/644ef887bb6f_add_close_status.py @@ -0,0 +1,85 @@ +"""Add close status + +Revision ID: 644ef887bb6f +Revises: 368fd931cf7f +Create Date: 2016-10-04 15:38:41.908679 + +""" + +# revision identifiers, used by Alembic. +revision = '644ef887bb6f' +down_revision = '368fd931cf7f' + +from alembic import op +import sqlalchemy as sa + + +try: + from pagure.lib import model +except ImportError: + import sys + sys.path.insert(0, '.') + from pagure.lib import model + + +def upgrade(): + ''' Add the column _close_status to the table projects. + ''' + op.add_column( + 'projects', + sa.Column('_close_status', sa.Text, nullable=True) + ) + op.add_column( + 'issues', + sa.Column('close_status', sa.Text, nullable=True) + ) + + engine = op.get_bind() + Session = sa.orm.scoped_session(sa.orm.sessionmaker()) + Session.configure(bind=engine) + session = Session() + + # Update all the existing projects + statuses = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate'] + for project in session.query(model.Project).all(): + project.close_status = statuses + session.add(project) + session.commit() + + # Add the status 'Closed' for issues + ticket_stat = model.StatusIssue(status='Closed') + session.add(ticket_stat) + session.commit() + + # Remove the old status + op.execute('''DELETE FROM "status_issue" WHERE "status" NOT IN ('Open', 'Closed'); ''') + + # Set the close_status for all the closed tickets + op.execute('''UPDATE "issues" SET "close_status"=status where status != 'Open'; ''') + + # Mark all the tickets as closed + op.execute('''UPDATE "issues" SET status='Closed' where status != 'Open'; ''') + + +def downgrade(): + ''' Add the column _close_status to the table projects. + ''' + engine = op.get_bind() + Session = sa.orm.scoped_session(sa.orm.sessionmaker()) + Session.configure(bind=engine) + session = Session() + + statuses = ['Invalid', 'Insufficient data', 'Fixed', 'Duplicate'] + for status in statuses: + ticket_stat = model.StatusIssue(status=status) + session.add(ticket_stat) + session.commit() + + # Set the close_status for all the closed tickets + op.execute('''UPDATE "issues" SET status=close_status where status != 'Open'; ''') + + # Remove the old status + op.execute('''DELETE FROM "status_issue" WHERE status = 'Closed'; ''') + + op.drop_column('projects', '_close_status') + op.drop_column('issues', 'close_status') diff --git a/pagure/lib/model.py b/pagure/lib/model.py index 66bfe0b..7edf9bd 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -102,7 +102,7 @@ def create_default_status(session, acls=None): """ Insert the defaults status in the status tables. """ - statuses = ['Open', 'Invalid', 'Insufficient data', 'Fixed', 'Duplicate'] + statuses = ['Open', 'Closed'] for status in statuses: ticket_stat = StatusIssue(status=status) session.add(ticket_stat) @@ -320,6 +320,7 @@ class Project(BASE): _milestones = sa.Column(sa.Text, nullable=True) _reports = sa.Column(sa.Text, nullable=True) _notifications = sa.Column(sa.Text, nullable=True) + _close_status = sa.Column(sa.Text, nullable=True) date_created = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow) @@ -478,6 +479,23 @@ class Project(BASE): self._reports = json.dumps(reports) @property + def close_status(self): + """ Return the dict stored as string in the database as an actual + dict object. + """ + close_status = [] + + if self._close_status: + close_status = json.loads(self._close_status) + + return close_status + + @close_status.setter + def close_status(self, close_status): + ''' Ensures the different close status are properly saved. ''' + self._close_status = json.dumps(close_status) + + @property def open_requests(self): ''' Returns the number of open pull-requests for this project. ''' return BASE.metadata.bind.query( @@ -607,6 +625,7 @@ class Issue(BASE): private = sa.Column(sa.Boolean, nullable=False, default=False) priority = sa.Column(sa.Integer, nullable=True, default=None) milestone = sa.Column(sa.String(255), nullable=True, default=None) + close_status = sa.Column(sa.Text, nullable=True) date_created = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow) From 827c774a239fbb670c0cb8167051aecfcb1d5bb8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Oct 04 2016 17:47:41 +0000 Subject: [PATCH 2/7] Add possibility to set/remove the close status in the settings page of a project --- diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html index f5308c5..1573ccf 100644 --- a/pagure/templates/settings.html +++ b/pagure/templates/settings.html @@ -619,6 +619,60 @@
+ Close status +
+
+

+ Here is the list of all the status that can be used when closing + an issue. +

+
+
+ {{ tag_form.csrf_token }} +
+
+
+ Status +
+
+
+ {% for status in repo.close_status | sort %} +
+
+ +
+
+ {% endfor %} +
+ +
+
+ +
+
+
+
+
+
+ +
+
+
Issue Tags
@@ -807,5 +861,17 @@ $('#new_milestone').click(function(e) { ); }); +$('#new_close_status').click(function(e) { + console.log('new close status'); + $('#close_sstatus').append( + '
\ +
\ + \ +
\ +
' + ); +}); + {% endblock %} diff --git a/pagure/ui/repo.py b/pagure/ui/repo.py index 4d19889..c857a4a 100644 --- a/pagure/ui/repo.py +++ b/pagure/ui/repo.py @@ -2046,3 +2046,53 @@ def update_public_notifications(repo, username=None, namespace=None): return flask.redirect(flask.url_for( 'view_settings', username=username, repo=repo.name, namespace=repo.namespace)) + + +@APP.route('//update/close_status', methods=['POST']) +@APP.route('///update/close_status', methods=['POST']) +@APP.route('/fork///update/close_status', methods=['POST']) +@APP.route( + '/fork////update/close_status', + methods=['POST']) +@login_required +def update_close_status(repo, username=None, namespace=None): + """ Update the close_status of a project. + """ + if admin_session_timedout(): + flask.flash('Action canceled, try it again', 'error') + url = flask.url_for( + 'view_settings', username=username, repo=repo, + namespace=namespace) + return flask.redirect( + flask.url_for('auth_login', next=url)) + + repo = flask.g.repo + + if not repo.settings.get('issue_tracker', True): + flask.abort(404, 'No issue tracker found for this project') + + if not flask.g.repo_admin: + flask.abort( + 403, + 'You are not allowed to change the settings for this project') + + form = pagure.forms.ConfirmationForm() + + error = False + if form.validate_on_submit(): + close_status = [ + w.strip() for w in flask.request.form.getlist('close_status') + if w.strip() + ] + try: + repo.close_status = close_status + SESSION.add(repo) + SESSION.commit() + flask.flash('List of close status updated') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + flask.flash(str(err), 'error') + + return flask.redirect(flask.url_for( + 'view_settings', username=username, repo=repo.name, + namespace=namespace)) From 05fc3ce3397ea600e5c360e81fcd13f7e6393698 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Oct 04 2016 17:47:41 +0000 Subject: [PATCH 3/7] Adjust the issue view to include the close_status information Fixes https://pagure.io/pagure/issue/1373 --- diff --git a/pagure/forms.py b/pagure/forms.py index b8a2243..d632905 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -290,6 +290,11 @@ class UpdateIssueForm(FlaskForm): 'Private', [wtforms.validators.optional()], ) + close_status = wtforms.SelectField( + 'Closed as', + [wtforms.validators.Optional()], + choices=[] + ) def __init__(self, *args, **kwargs): """ Calls the default constructor with the normal argument but @@ -315,6 +320,12 @@ class UpdateIssueForm(FlaskForm): self.milestone.choices.append((key, key)) self.milestone.choices.insert(0, ('', '')) + self.close_status.choices = [] + if 'close_status' in kwargs: + for key in sorted(kwargs['close_status']): + self.close_status.choices.append((key, key)) + self.close_status.choices.insert(0, ('', '')) + class AddPullRequestCommentForm(FlaskForm): ''' Form to add a comment to a pull-request. ''' diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 0d8d09d..b4f55bb 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -1289,13 +1289,13 @@ def new_pull_request(session, branch_from, def edit_issue(session, issue, ticketfolder, user, - title=None, content=None, status=None, + title=None, content=None, status=None, close_status=None, priority=None, milestone=None, private=False): ''' Edit the specified issue. ''' user_obj = get_user(session, user) - if status == 'Fixed' and issue.parents: + if status != 'Open' and issue.parents: for parent in issue.parents: if parent.status == 'Open': raise pagure.exceptions.PagureException( @@ -1314,6 +1314,9 @@ def edit_issue(session, issue, ticketfolder, user, if status.lower() != 'open': issue.closed_at = datetime.datetime.utcnow() edit.append('status') + if close_status and close_status != issue.close_status: + issue.close_status = close_status + edit.append('close_status') if priority: try: priority = int(priority) diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html index fe8b982..0287100 100644 --- a/pagure/templates/issue.html +++ b/pagure/templates/issue.html @@ -116,18 +116,26 @@

{{ issue.status }} + {% if issue.status == 'Closed' %} + + as: {{ issue.close_status }} + + {% endif %}

{% if authenticated and g.repo_admin %} {{ render_bootstrap_field(form.status, formclass="issue-metadata-form") }} + {{ render_bootstrap_field(form.close_status, + formclass="issue-metadata-form") }} {% endif%}