From 1cdcf90e121396eb03ba7e17e19ec9cb3e2ee63d Mon Sep 17 00:00:00 2001 From: Ryan Lerch Date: Jun 12 2018 12:54:43 +0000 Subject: new roadmap and milestones pages --- diff --git a/pagure/static/pagure.css b/pagure/static/pagure.css index 4a56eb1..91638fe 100644 --- a/pagure/static/pagure.css +++ b/pagure/static/pagure.css @@ -800,3 +800,11 @@ a.nav-link.btn{ border-bottom: 1px solid #ddd; border-left: 1px solid #ddd; } + +.tooltip-inner{ + max-width: 400px; +} + +.progress-bar{ + height: inherit; +} diff --git a/pagure/templates/_render_issues.html b/pagure/templates/_render_issues.html new file mode 100644 index 0000000..54a058b --- /dev/null +++ b/pagure/templates/_render_issues.html @@ -0,0 +1,71 @@ +{% macro render_issue_row(issue, repo) %} + {% if issue.status == 'Open' %} + {% set status_color = "success" %} + {% else %} + {% set status_color = "danger" %} + {% endif %} + +
+
+ +
+
+ #{{issue.id}} + + + {{issue.title}} + + + +
+
+ Opened {{ issue.date_created | humanize}} by {{ issue.user.user }}. + Modified {{ issue.last_updated | humanize}} + +
+
+ {% for tag in issue.tags %} + + {{ tag.tag }} + + {% endfor %} +
+
+ + {% if issue.priority is not none %} + {{ repo.priorities[issue.priority | string] }} + {% endif %} + + {% if issue.assignee %} + + + {{ issue.assignee.username | avatar(size=20) | safe}} + + {% endif %} + + {% if issue.user_comments|count > 0 %} + + + {{issue.user_comments|count}} + + {% endif %} + +
+
+{% endmacro%} \ No newline at end of file diff --git a/pagure/templates/repo_milestone.html b/pagure/templates/repo_milestone.html new file mode 100644 index 0000000..9ce1cee --- /dev/null +++ b/pagure/templates/repo_milestone.html @@ -0,0 +1,76 @@ +{% extends "repo_master.html" %} +{% from "_render_issues.html" import render_issue_row %} +{% block title %}Roadmap - {{ + repo.namespace + '/' if repo.namespace }}{{ repo.name }}{% endblock %} +{% set tag = "home"%} + +{% block repo %} +

+ {{milestone}} +

+ +
+
+
+ {% if total_open + total_closed == 0 %} +
+
+
+

no issues assigned to the {{milestone}} milestone +

+
+
+
+ {% else %} + {% set completed_percentage = (100.0 * total_closed / (total_closed+total_open)) %} +
+
+
+
+
+
+ + {% if open_issues %} +
+
Open Issues
+
+ {% for issue in open_issues %} + {{render_issue_row(issue, repo)}} + {% endfor %} + {% endif %} + {% if closed_issues %} +
+
Closed Issues
+
+ {% for issue in closed_issues %} + {{render_issue_row(issue, repo)}} + {% endfor %} + {% endif %} + {% endif %} +
+
+
+{% endblock %} diff --git a/pagure/templates/repo_roadmap.html b/pagure/templates/repo_roadmap.html new file mode 100644 index 0000000..8260f7c --- /dev/null +++ b/pagure/templates/repo_roadmap.html @@ -0,0 +1,130 @@ +{% extends "repo_master.html" %} + +{% block title %}Roadmap - {{ + repo.namespace + '/' if repo.namespace }}{{ repo.name }}{% endblock %} +{% set tag = "home"%} + +{% block repo %} +

+ Roadmap +
+ {% if g.authenticated %} + {% if g.repo_admin %} + + + Configure Milestones + + {% endif %} + {% endif %} +
+

+ +
+ +
+{% endblock %} diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py index a7d05c3..b8f91f9 100644 --- a/pagure/ui/issues.py +++ b/pagure/ui/issues.py @@ -20,7 +20,7 @@ import datetime import logging import os import re -from collections import defaultdict +from collections import defaultdict, OrderedDict from math import ceil import flask @@ -754,11 +754,7 @@ def view_issues(repo, username=None, namespace=None): def view_roadmap(repo, username=None, namespace=None): """ List all issues associated to a repo as roadmap """ - status = flask.request.args.get('status', 'Open') - milestones = flask.request.args.getlist('milestone', None) - tags = flask.request.args.getlist('tag', None) - all_stones = flask.request.args.get('all_stones') - no_stones = flask.request.args.get('no_stones') + milestones_status_arg = flask.request.args.get('status', 'active') repo = flask.g.repo @@ -772,105 +768,101 @@ def view_roadmap(repo, username=None, namespace=None): if flask.g.repo_committer: private = None - tag_list = [ - tag.tag - for tag in pagure.lib.get_tags_of_project(flask.g.session, repo) - ] - - if all_stones: - milestones_list = sorted([ - k - for k in repo.milestones - ]) - else: - milestones_list = sorted([ - k - for k in repo.milestones - if repo.milestones[k]['active'] - ]) - - if 'unplanned' in milestones_list: - index = milestones_list.index('unplanned') - cnt = len(milestones_list) - milestones_list.insert(cnt, milestones_list.pop(index)) - - if no_stones: - # Return only issues that do not have a milestone set - issues = pagure.lib.search_issues( - flask.g.session, - repo, - no_milestones=True, - tags=tags, - private=private, - status=status if status.lower() != 'all' else None, - ) - return flask.render_template( - 'roadmap.html', - select='roadmap', - repo=repo, - username=username, - tag_list=tag_list, - status=status, - no_stones=True, - issues=issues, - tags=tags, - all_stones=all_stones, - requested_stones=milestones, - ) + milestones_list = [] + milestones_totals = defaultdict(int) + milestones_totals['active'] = 0 + milestones_totals['inactive'] = 0 + + for key in repo.milestones_keys: + if repo.milestones[key]['active']: + milestones_totals['active'] += 1 + if milestones_status_arg == 'active': + milestones_list.append(key) + else: + milestones_totals['inactive'] += 1 + if milestones_status_arg == 'inactive': + milestones_list.append(key) issues = pagure.lib.search_issues( flask.g.session, repo, - milestones=milestones or milestones_list, - tags=tags, + milestones=milestones_list, private=private, status=None, ) # Change from a list of issues to a dict of milestone/issues - milestone_issues = defaultdict(list) - for issue in issues: - saved = False - for mlstone in sorted(milestones or milestones_list): - if mlstone == issue.milestone: - milestone_issues['%s_total' % mlstone] = \ - milestone_issues.get('%s_total' % mlstone, 0) + 1 - if status.lower() == 'all' or ( - status.lower() != 'all' - and status.lower() == issue.status.lower()): - milestone_issues[mlstone].append(issue) - saved = True - break - if saved: - continue - - if status and status.lower() != 'all': - for key in milestone_issues: - if key.endswith('_total'): - continue - active = False - for issue in milestone_issues[key]: - if issue.status == status: - active = True - break - if not active: - del milestone_issues[key] - k2 = '%s_total' % key - if k2 in milestone_issues: - del milestone_issues[k2] + milestone_issues = OrderedDict() + if milestones_list: + for milestone in milestones_list: + milestone_issues[milestone] = defaultdict(int) + for issue in issues: + if issue.milestone: + milestone_issues[issue.milestone][issue.status] += 1 + milestone_issues[issue.milestone]['Total'] += 1 return flask.render_template( - 'roadmap.html', + 'repo_roadmap.html', select='roadmap', + milestones_status_select=milestones_status_arg, repo=repo, username=username, - tag_list=tag_list, - status=status, - all_stones=all_stones, - milestones=milestones_list, - requested_stones=milestones, - issues=milestone_issues, - tags=tags, + milestones=milestone_issues, + milestones_totals=milestones_totals + ) + + +@UI_NS.route('//roadmap/') +@UI_NS.route('//roadmap//') +@UI_NS.route('///roadmap//') +@UI_NS.route('///roadmap/') +@UI_NS.route('/fork///roadmap//') +@UI_NS.route('/fork///roadmap/') +@UI_NS.route('/fork////roadmap//') +@UI_NS.route('/fork////roadmap/') +@has_issue_tracker +def view_milestone(repo, username=None, namespace=None, milestone=None): + """ List all issues associated to a repo as roadmap + """ + + repo = flask.g.repo + + # Hide private tickets + private = False + # If user is authenticated, show him/her his/her private tickets + if authenticated(): + private = flask.g.fas_user.username + + # If user is repo committer, show all tickets including the private ones + if flask.g.repo_committer: + private = None + + open_issues = pagure.lib.search_issues( + flask.g.session, + repo, + milestones=[milestone], + private=private, + status='Open', + ) + + closed_issues = pagure.lib.search_issues( + flask.g.session, + repo, + milestones=[milestone], + private=private, + status='Closed', + ) + + return flask.render_template( + 'repo_milestone.html', + select='roadmap', + repo=repo, + username=username, + milestone=milestone, + open_issues=open_issues, + closed_issues=closed_issues, + total_open=len(open_issues), + total_closed=len(closed_issues) ) diff --git a/tests/test_pagure_flask_ui_roadmap.py b/tests/test_pagure_flask_ui_roadmap.py index cb5d84a..a588be3 100644 --- a/tests/test_pagure_flask_ui_roadmap.py +++ b/tests/test_pagure_flask_ui_roadmap.py @@ -541,66 +541,35 @@ class PagureFlaskRoadmaptests(tests.Modeltests): self.assertEqual(output.status_code, 200) output_text = output.get_data(as_text=True) self.assertIn( - 'title="Filter issues by milestone">\n v2.0', + '\n' + ' v1.0', output_text) self.assertIn( - 'title="Filter issues by milestone">\n unplanned', + '\n' + ' unplanned', output_text) - self.assertEqual( - output_text.count('#'), 4) - - # test the roadmap view for all milestones - output = self.app.get('/test/roadmap?all_stones=True&status=All') - self.assertEqual(output.status_code, 200) - output_text = output.get_data(as_text=True) self.assertIn( - 'title="Filter issues by milestone">\n v1.0', + 'title="100% Completed | 2 Closed Issues | 0 Open Issues"\n', output_text) self.assertIn( - 'title="Filter issues by milestone">\n v2.0', + 'title="0% Completed | 0 Closed Issues | 2 Open Issues"\n', output_text) self.assertIn( - 'title="Filter issues by milestone">\n unplanned', + 'title="0% Completed | 0 Closed Issues | 2 Open Issues"\n', output_text) - self.assertEqual( - output_text.count('#'), 6) # test the roadmap view for a specific milestone - output = self.app.get('/test/roadmap?milestone=v2.0') + output = self.app.get('/test/roadmap/v2.0/') self.assertEqual(output.status_code, 200) output_text = output.get_data(as_text=True) self.assertIn( - 'title="Filter issues by milestone">\n v2.0', + ' 2 Open\n', output_text) - self.assertEqual( - output_text.count('#'), 2) - - # test the roadmap view for a specific milestone - open - output = self.app.get('/test/roadmap?milestone=v1.0') - self.assertEqual(output.status_code, 200) - output_text = output.get_data(as_text=True) - self.assertIn('No issues found', output_text) - self.assertEqual( - output_text.count('#'), 0) - - # test the roadmap view for a specific milestone - closed - output = self.app.get( - '/test/roadmap?milestone=v1.0&status=All&all_stones=True') - self.assertEqual(output.status_code, 200) - output_text = output.get_data(as_text=True) self.assertIn( - 'title="Filter issues by milestone">\n v1.0', + ' 0 Closed\n', output_text) - self.assertEqual( - output_text.count('#'), 2) - - # test the roadmap view for a specific tag - output = self.app.get('/test/roadmap?milestone=v2.0&tag=unknown') - self.assertEqual(output.status_code, 200) - output_text = output.get_data(as_text=True) - self.assertIn('No issues found', output_text) - self.assertEqual( - output_text.count('#'), 0) # test the roadmap view for errors output = self.app.get('/foo/roadmap') @@ -616,62 +585,6 @@ class PagureFlaskRoadmaptests(tests.Modeltests): output = self.app.get('/test/roadmap', data=data) self.assertEqual(output.status_code, 404) - @patch('pagure.lib.git.update_git') - @patch('pagure.lib.notify.send_email') - def test_show_ban_lock_unlock_in_roadmap_ui(self, send_email, update_git): - send_email.return_value = True - update_git.return_value = True - - tests.create_projects(self.session) - tests.create_projects_git( - os.path.join(self.path, 'repos'), bare=True) - - # Create issues to play with - repo = pagure.lib.get_authorized_project(self.session, 'test') - repo.milestones = {'0.1': ''} - - issue_1 = pagure.lib.new_issue( - session=self.session, - repo=repo, - title='Test issue', - content='We should work on this', - user='pingou', - ticketfolder=None, - milestone='0.1', - ) - - repo = pagure.lib.get_authorized_project(self.session, 'test') - issue_2 = pagure.lib.new_issue( - session=self.session, - repo=repo, - title='Test issue #2', - content='We should work on this again', - user='foo', - ticketfolder=None, - milestone='0.1', - ) - - issue_1.children.append(issue_2) - self.session.commit() - - user = tests.FakeUser() - user.username = 'pingou' - with tests.user_set(self.app.application, user): - output = self.app.get('/test/roadmap') - output_text = output.get_data(as_text=True) - self.assertIn( - '', - output_text) - self.assertEqual(1, output_text.count( - 'title="Issue blocked by one or more issue(s)')) - self.assertIn( - '', - output_text) - self.assertEqual(1, output_text.count( - 'title="Issue blocking one or more issue(s)')) - if __name__ == '__main__': unittest.main(verbosity=2)