From 3cea10fdced828f3db3e37a3956b862391af864e Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 1/15] Add a milestone column to the issues table --- diff --git a/pagure/lib/model.py b/pagure/lib/model.py index cfab888..e1c7216 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -586,6 +586,7 @@ class Issue(BASE): nullable=False) 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) date_created = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow) From f52c45870a1d8a9b28ade507d886ca29ad6d3634 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 2/15] Add alembic migration script adding a milestone column to the issues table --- diff --git a/alembic/versions/36386a60b3fd_add_milestone_to_issues.py b/alembic/versions/36386a60b3fd_add_milestone_to_issues.py new file mode 100644 index 0000000..64c4a29 --- /dev/null +++ b/alembic/versions/36386a60b3fd_add_milestone_to_issues.py @@ -0,0 +1,32 @@ +"""Add milestone to issues + +Revision ID: 36386a60b3fd +Revises: 350efb3f6baf +Create Date: 2016-09-14 11:03:45.673932 + +""" + +# revision identifiers, used by Alembic. +revision = '36386a60b3fd' +down_revision = '350efb3f6baf' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column milestone to the table issues. + ''' + op.add_column( + 'issues', + sa.Column('milestone', sa.String(255), nullable=True) + ) + + +def downgrade(): + ''' Add the column milestone to the table issues. + ''' + op.add_column( + 'issues', + sa.Column('milestone', sa.String(255), nullable=True) + ) From 8a9c7e85d511cef263d5f5652e700a84e743d2be Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 3/15] Add support to edit the issue's milestone on pagure.lib.edit_issue --- diff --git a/pagure/lib/__init__.py b/pagure/lib/__init__.py index 2549648..9af27ea 100644 --- a/pagure/lib/__init__.py +++ b/pagure/lib/__init__.py @@ -1269,7 +1269,7 @@ def new_pull_request(session, branch_from, def edit_issue(session, issue, ticketfolder, user, title=None, content=None, status=None, - priority=None, private=False): + priority=None, milestone=None, private=False): ''' Edit the specified issue. ''' user_obj = get_user(session, user) @@ -1304,6 +1304,9 @@ def edit_issue(session, issue, ticketfolder, user, if private in [True, False] and private != issue.private: issue.private = private edit.append('private') + if milestone != issue.milestone: + issue.milestone = milestone + edit.append('milestone') pagure.lib.git.update_git( issue, repo=issue.project, repofolder=ticketfolder) From a0e28719da832f54bcfa667bd7899b10b0550fae Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 4/15] Implement setting and editing milestone on issues --- diff --git a/pagure/forms.py b/pagure/forms.py index 8b46de4..379c08f 100644 --- a/pagure/forms.py +++ b/pagure/forms.py @@ -257,6 +257,11 @@ class UpdateIssueForm(wtf.Form): [wtforms.validators.Optional()], choices=[] ) + milestone = wtforms.SelectField( + 'Milestone', + [wtforms.validators.Optional()], + choices=[] + ) def __init__(self, *args, **kwargs): """ Calls the default constructor with the normal argument but @@ -276,6 +281,12 @@ class UpdateIssueForm(wtf.Form): (key, kwargs['priorities'][key]) ) + self.milestone.choices = [] + if 'milestones' in kwargs: + for key in sorted(kwargs['milestones']): + self.milestone.choices.append((key, key)) + self.milestone.choices.insert(0, ('', '')) + class AddPullRequestCommentForm(wtf.Form): ''' Form to add a comment to a pull-request. ''' diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html index 9a6386b..06de571 100644 --- a/pagure/templates/issue.html +++ b/pagure/templates/issue.html @@ -254,6 +254,22 @@ {% endif %} + {% if repo.milestones %} + + {% if authenticated and g.repo_admin %} + {{ render_bootstrap_field(form.milestone, + formclass="issue-metadata-form") }} + {% endif%} + + {% endif %} + {% endif %} {% if g.repo.reports %} diff --git a/pagure/templates/roadmap.html b/pagure/templates/roadmap.html index 97ce6fb..57f6dbc 100644 --- a/pagure/templates/roadmap.html +++ b/pagure/templates/roadmap.html @@ -15,16 +15,27 @@ 'view_issues', repo=repo.name, username=username, - namespace=repo.namespace) }}" class="btn btn-secondary btn-sm"> - List - - - New Issue + namespace=repo.namespace) }}"> + + {% if g.repo.reports %} + + {% endif %} {% if oth_issues %} From ad30692837c782e831a2b8b391ac17a2434dbfff Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 8/15] Drop the unplanned since it's no longer a feature --- diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py index 6f32d2b..6e5dac0 100644 --- a/pagure/ui/issues.py +++ b/pagure/ui/issues.py @@ -573,8 +573,6 @@ def view_roadmap(repo, username=None, namespace=None): break if saved: continue - if not milestone: - milestone_issues['unplanned'].append(issues[cnt]) if status: for key in milestone_issues.keys(): From c8f2691c02a570a020525c8448ca303f5fdeeae4 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 9/15] Adjust the unit-tests for the new behavior of issues' milestones --- diff --git a/tests/test_pagure_flask_ui_roadmap.py b/tests/test_pagure_flask_ui_roadmap.py index 9f514d6..4c47b78 100644 --- a/tests/test_pagure_flask_ui_roadmap.py +++ b/tests/test_pagure_flask_ui_roadmap.py @@ -162,7 +162,8 @@ class PagureFlaskRoadmaptests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertIn( - u'\n Tag added: roadmap', + u'\n ' + u'Successfully edited issue #1', output.data) def test_update_milestones(self): @@ -392,6 +393,29 @@ class PagureFlaskRoadmaptests(tests.Modeltests): csrf_token = output.data.split( u'name="csrf_token" type="hidden" value="')[1].split(u'">')[0] + # Create an unplanned milestone + data = { + 'milestones': ['v1.0', 'v2.0', 'unplanned'], + 'milestone_dates': ['Tomorrow', '', ''], + 'csrf_token': csrf_token, + } + output = self.app.post( + '/test/update/milestones', data=data, follow_redirects=True) + self.assertEqual(output.status_code, 200) + # Check the redirect + self.assertIn( + u'Settings - test - Pagure', output.data) + self.assertIn(u'

Settings for test

', output.data) + self.assertIn(u'Milestones updated', output.data) + # Check the result of the action -- Milestones recorded + repo = pagure.lib.get_project(self.session, 'test') + self.assertEqual( + repo.milestones, + { + u'v1.0': u'Tomorrow', u'v2.0': u'', u'unplanned': u'' + } + ) + # Create the issues for cnt in range(6): cnt += 1 @@ -399,7 +423,6 @@ class PagureFlaskRoadmaptests(tests.Modeltests): 'title': 'Test issue %s' % cnt, 'issue_content': 'We really should improve on this ' 'issue %s' % cnt, - 'status': 'Open', 'csrf_token': csrf_token, } @@ -417,8 +440,14 @@ class PagureFlaskRoadmaptests(tests.Modeltests): output.data) # Mark the ticket for the roadmap + mstone = 'v%s.0' % cnt + if cnt >= 3: + if (cnt % 3) == 0: + mstone = 'unplanned' + else: + mstone = 'v%s.0' % (cnt % 3) data = { - 'tag': ['roadmap, v%s.0' % cnt], + 'milestone': mstone, 'csrf_token': csrf_token, } output = self.app.post( @@ -436,20 +465,21 @@ class PagureFlaskRoadmaptests(tests.Modeltests): output.data) self.assertIn( u'\n ' - 'Tag added: v%s.0, roadmap' % cnt, + u'Successfully edited issue #%s' % cnt, output.data) repo = pagure.lib.get_project(self.session, 'test') # Mark ticket #1 as Fixed - ticket = pagure.lib.search_issues( - self.session, - repo, - issueid=1 - ) - ticket.status = 'Fixed' - self.session.add(ticket) - self.session.commit() + for iid in [1, 4]: + ticket = pagure.lib.search_issues( + self.session, + repo, + issueid=iid + ) + ticket.status = 'Fixed' + self.session.add(ticket) + self.session.commit() # test the roadmap view output = self.app.get('/test/roadmap') @@ -458,7 +488,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests): self.assertIn(u'Milestone: v2.0', output.data) self.assertIn(u'Milestone: unplanned', output.data) self.assertEqual( - output.data.count(u'#'), 5) + output.data.count(u'#'), 4) # test the roadmap view for all milestones output = self.app.get('/test/roadmap?status=All') @@ -476,7 +506,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests): self.assertIn(u'1 Milestones', output.data) self.assertIn(u'Milestone: v2.0', output.data) self.assertEqual( - output.data.count(u'#'), 1) + output.data.count(u'#'), 2) # test the roadmap view for a specific milestone - closed output = self.app.get('/test/roadmap?milestone=v1.0') @@ -492,7 +522,7 @@ class PagureFlaskRoadmaptests(tests.Modeltests): self.assertIn(u'1 Milestones', output.data) self.assertIn(u'Milestone: v1.0', output.data) self.assertEqual( - output.data.count(u'#'), 1) + output.data.count(u'#'), 2) # test the roadmap view for errors output = self.app.get('/foo/roadmap') From 26306f184e6069d7963daee704179c6770cc064b Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 10/15] Include the milestone in the JSON representation of an issue --- diff --git a/pagure/lib/model.py b/pagure/lib/model.py index e1c7216..2e50fb8 100644 --- a/pagure/lib/model.py +++ b/pagure/lib/model.py @@ -673,6 +673,7 @@ class Issue(BASE): 'assignee': self.assignee.to_json( public=public) if self.assignee else None, 'priority': self.priority, + 'milestone': self.milestone, } comments = [] From 725fcabe1edef944dcaa24eb50186336d254ce97 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 11/15] Adjust unit-tests for the change in JSON representation of an issue --- diff --git a/tests/test_pagure_flask_api_issue.py b/tests/test_pagure_flask_api_issue.py index f723942..7bd337d 100644 --- a/tests/test_pagure_flask_api_issue.py +++ b/tests/test_pagure_flask_api_issue.py @@ -164,6 +164,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -217,6 +218,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -273,6 +275,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -315,6 +318,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 2, + "milestone": None, "priority": None, "private": True, "status": "Open", @@ -334,6 +338,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -410,6 +415,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 2, + "milestone": None, "priority": None, "private": True, "status": "Open", @@ -429,6 +435,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -487,6 +494,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 1, + "milestone": None, "priority": None, "private": False, "status": "Open", @@ -582,6 +590,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 2, + "milestone": None, "priority": None, "private": True, "status": "Open", @@ -610,6 +619,7 @@ class PagureFlaskApiIssuetests(tests.Modeltests): "closed_at": None, "depends": [], "id": 2, + "milestone": None, "priority": None, "private": True, "status": "Open", diff --git a/tests/test_pagure_lib_git.py b/tests/test_pagure_lib_git.py index 926e7be..48122ee 100644 --- a/tests/test_pagure_lib_git.py +++ b/tests/test_pagure_lib_git.py @@ -476,7 +476,7 @@ new file mode 100644 index 0000000..60f7480 --- /dev/null +++ b/456 -@@ -0,0 +1,24 @@ +@@ -0,0 +1,25 @@ +{ + "assignee": null, + "blocks": [], @@ -486,6 +486,7 @@ index 0000000..60f7480 + "date_created": null, + "depends": [], + "id": 1, ++ "milestone": null, + "priority": null, + "private": false, + "status": "Open", From a3db50ba1c11472dfa8a11644f6def61c008bc9a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 12/15] Adjust the documentation about using the roadmap feature --- diff --git a/doc/usage/_static/pagure_roadmap2.png b/doc/usage/_static/pagure_roadmap2.png index a08114f..c4509cc 100644 Binary files a/doc/usage/_static/pagure_roadmap2.png and b/doc/usage/_static/pagure_roadmap2.png differ diff --git a/doc/usage/roadmap.rst b/doc/usage/roadmap.rst index 8f4c400..d0b3e92 100644 --- a/doc/usage/roadmap.rst +++ b/doc/usage/roadmap.rst @@ -6,13 +6,12 @@ their tags. The principal is as follow: -* All the ticket with the tag ``roadmap`` will show up on the roadmap page. - * For each milestones defined in the settings of the project, the roadmap - will group tickets with the corresponding tag. + will group tickets with the corresponding milestone. -* Tickets with the tag ``roadmap`` that are not associated with any of the - milestones defined in the settings are group in an ``unplanned`` section. +* If your project has an ``unplanned`` milestone, this milestone will be + shown at the bottom of the roadmap page. This allowing you to put something + on the roadmap without assigning a real milestone to it. Example @@ -21,14 +20,14 @@ Example For a project named ``test`` on ``pagure.io``. - * First, go to the settings page of the project, create the milestones you like, for example: ``v1.0`` and ``v2.0``. * For the tickets you want to be on these milestones, go through each of them - and add them the tags: ``roadmap`` in combination with the milestone you want - ``v1.0`` or ``v2.0``, or none of them if the ticket is on the roadmap but - not assigned to any milestones. + and set their milestone to either ``v1.0`` or ``v2.0``, or none of them + if the ticket is on not on the roadmap. + You can set the milestone on the metadata panel on the right side of the + issue page. * And this is how it will look like From 5b73ec0a096a43a9bc140ab4285d20eec1d0d84f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 13/15] Some more adjustments to the unit-tests for the change when editing an issue --- diff --git a/tests/test_pagure_flask_ui_issues.py b/tests/test_pagure_flask_ui_issues.py index ff4b7bb..a261241 100644 --- a/tests/test_pagure_flask_ui_issues.py +++ b/tests/test_pagure_flask_ui_issues.py @@ -617,10 +617,8 @@ class PagureFlaskIssuestests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertIn( - '\n Tag added: tag2', - output.data) - self.assertNotIn( - '\n No changes to edit', + '\n ' + 'Successfully edited issue #1', output.data) self.assertTrue( '

Woohoo a second comment !

' in output.data) @@ -941,7 +939,8 @@ class PagureFlaskIssuestests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertIn( - '\n Dependency added', + '\n ' + 'Successfully edited issue #1', output.data) # Add an invalid dependent ticket @@ -960,7 +959,8 @@ class PagureFlaskIssuestests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertNotIn( - '\n Dependency added', + '\n ' + 'Successfully edited issue #1', output.data) repo = pagure.lib.get_project(self.session, 'test') @@ -1036,7 +1036,8 @@ class PagureFlaskIssuestests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertIn( - '\n Dependency added', + '\n ' + 'Successfully edited issue #1', output.data) # Add an invalid dependent ticket @@ -1055,7 +1056,8 @@ class PagureFlaskIssuestests(tests.Modeltests): 'href="/test/issue/1/edit" title="Edit this issue">', output.data) self.assertNotIn( - '\n Dependency added', + '\n ' + 'Successfully edited issue #1', output.data) repo = pagure.lib.get_project(self.session, 'test') From 40e8439b729cbd3907aa4bcad72fe6e4fc6e2878 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 14/15] Fix the alembic script to drop the milestone column upon downgrade --- diff --git a/alembic/versions/36386a60b3fd_add_milestone_to_issues.py b/alembic/versions/36386a60b3fd_add_milestone_to_issues.py index 64c4a29..d50b153 100644 --- a/alembic/versions/36386a60b3fd_add_milestone_to_issues.py +++ b/alembic/versions/36386a60b3fd_add_milestone_to_issues.py @@ -26,7 +26,4 @@ def upgrade(): def downgrade(): ''' Add the column milestone to the table issues. ''' - op.add_column( - 'issues', - sa.Column('milestone', sa.String(255), nullable=True) - ) + op.drop_column('issues', 'milestone') From b0e322b38fd982c5710eb4abfc878e452729e087 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Sep 19 2016 13:10:04 +0000 Subject: [PATCH 15/15] Fix typos on the roadmap doc pointed out by @vivekanand1101 --- diff --git a/doc/usage/roadmap.rst b/doc/usage/roadmap.rst index d0b3e92..d2c3902 100644 --- a/doc/usage/roadmap.rst +++ b/doc/usage/roadmap.rst @@ -2,7 +2,7 @@ Using the roadmap feature ========================= Pagure allows building the roadmap of the project using the tickets and -their tags. +their milestones. The principal is as follow: @@ -25,7 +25,7 @@ For a project named ``test`` on ``pagure.io``. * For the tickets you want to be on these milestones, go through each of them and set their milestone to either ``v1.0`` or ``v2.0``, or none of them - if the ticket is on not on the roadmap. + if the ticket is not on the roadmap. You can set the milestone on the metadata panel on the right side of the issue page.