diff --git a/pagure/templates/roadmap.html b/pagure/templates/roadmap.html
new file mode 100644
index 0000000..cdde25d
--- /dev/null
+++ b/pagure/templates/roadmap.html
@@ -0,0 +1,169 @@
+{% extends "repo_master.html" %}
+
+{% block title %}Roadmap - {{ repo.name }}{% endblock %}
+{% set tag = "home"%}
+
+
+{% block repo %}
+
+
+
+ {{ issues|count }} Milestones
+
+
+
+
+
+
+
+
+
+ {% if oth_issues %}
+
+ {% if (issues | length + oth_issues) %}
+
+ {{ (100.0 * (1 - issues | length / (issues | length + oth_issues)))|round|int }}%
+
+ {% endif %}
+
+ {% endif %}
+
+
+
+{% for milestone in issues | sort %}
+
+
+
+
+
+
+
+
+ {% for issue in issues[milestone] |sort(attribute='priority') %}
+
+ |
+ #{{ issue.id }}
+ {% if status and status != 'Open' %}
+ {{issue.status}}
+ {% endif %}
+ {% if issue.private %}
+
+ {% endif %}
+
+ {{ issue.title | noJS("img") | safe }}
+
+
+ {% if issue.comments|count > 0 %}
+
+
+ {{issue.comments|count}}
+
+ {% endif %}
+ {% for tag in issue.tags%}
+ {{tag.tag}}
+ {% endfor%}
+ |
+
+ {{
+ issue.date_created | humanize}}
+ |
+
+ {% if issue.priority %}
+ {{repo.priorities[issue.priority | string] }}
+ {% endif %}
+ |
+
+ {% if issue.status != 'Open' %}
+ {{ issue.status }}
+ {% else %}
+ {% if issue.assignee %}
+ {{ issue.assignee.default_email | avatar(16) | safe }}
+ {{ issue.assignee.user }}
+ {% else %}
+ unassigned
+ {% endif %}
+ {% endif %}
+ |
+
+ {% else %}
+
+ | No issues found |
+
+ {% endfor %}
+
+
+
+
+{% endfor %}
+{% endblock %}
+{% block jscripts %}
+{{ super() }}
+
+
+
+{% endblock %}
diff --git a/pagure/ui/issues.py b/pagure/ui/issues.py
index 5642016..ae06a30 100644
--- a/pagure/ui/issues.py
+++ b/pagure/ui/issues.py
@@ -10,6 +10,7 @@
import flask
import os
+from collections import defaultdict
import pygit2
from sqlalchemy.exc import SQLAlchemyError
@@ -446,6 +447,88 @@ def view_issues(repo, username=None):
)
+@APP.route('//roadmap/')
+@APP.route('//roadmap')
+@APP.route('/fork///roadmap/')
+@APP.route('/fork///roadmap')
+def view_roadmap(repo, username=None):
+ """ List all issues associated to a repo as roadmap
+ """
+ status = flask.request.args.get('status', 'Open')
+ if status.lower() == 'all':
+ status = None
+ milestone = flask.request.args.getlist('milestone', None)
+
+ repo = pagure.lib.get_project(SESSION, repo, user=username)
+
+ if repo is None:
+ flask.abort(404, 'Project not found')
+
+ if not repo.settings.get('issue_tracker', True):
+ flask.abort(404, 'No issue tracker found for this project')
+
+ # 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 admin, show all tickets included the private ones
+ if is_repo_admin(repo):
+ private = None
+
+ milestones = milestone or list(repo.milestones.keys())
+ tags = ['roadmap'] + milestones
+
+ issues = pagure.lib.search_issues(
+ SESSION,
+ repo,
+ tags=tags,
+ private=private,
+ )
+
+ # Change from a list of issues to a dict of milestone/issues
+ milestone_issues = defaultdict(list)
+ for cnt in range(len(issues)):
+ saved = False
+ for milestone in sorted(milestones):
+ if milestone in issues[cnt].tags_text:
+ milestone_issues[milestone].append(issues[cnt])
+ saved = True
+ break
+ if saved:
+ continue
+ milestone_issues['unplaned'].append(issues[cnt])
+
+ if status:
+ for key in milestone_issues.keys():
+ active = False
+ for issue in milestone_issues[key]:
+ if issue.status == 'Open':
+ active = True
+ break
+ if not active:
+ del(milestone_issues[key])
+
+ tag_list = pagure.lib.get_tags_of_project(SESSION, repo)
+
+ reponame = pagure.get_repo_path(repo)
+ repo_obj = pygit2.Repository(reponame)
+
+ return flask.render_template(
+ 'roadmap.html',
+ select='issues',
+ repo=repo,
+ username=username,
+ tag_list=tag_list,
+ status=status,
+ issues=milestone_issues,
+ tags=tags,
+ repo_admin=is_repo_admin(repo),
+ repo_obj=repo_obj,
+ )
+
+
+
@APP.route('//new_issue/', methods=('GET', 'POST'))
@APP.route('//new_issue', methods=('GET', 'POST'))
@APP.route('/fork///new_issue/', methods=('GET', 'POST'))