Fix JIRA directives for empty query results
[docs.git] / docs / ext / odl-jira.py
1 # -*- coding: utf-8 -*-
2 # Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3 #
4 # This program and the accompanying materials are made available under the
5 # terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 # and is available at http://www.eclipse.org/legal/epl-v10.html
7 """
8
9 Embeds a simple table with issues.
10
11 """
12
13 from docutils import nodes
14 from docutils.parsers.rst import directives, Directive
15 from jira import JIRA
16 from urllib.parse import quote
17 import re
18 import sphinx
19
20 __copyright__ = "Copyright(c) 2021 PANTHEON.tech, s.r.o."
21 __license__ = "Eclipse Public License v1.0"
22
23 def jira_prj_versions(project, version_range):
24     jira = JIRA(server="https://jira.opendaylight.org")
25     prj = jira.project(project)
26     (from_ver, to_ver) = version_range.split('-', 1)
27
28     versions = set()
29     for ver in jira.project_versions(prj):
30         if ver.name >= from_ver and ver.name <= to_ver:
31             versions.add(ver.name)
32     versions = list(versions)
33     versions.sort()
34     versions = ", ".join(versions)
35
36     return (jira, prj, from_ver, to_ver, versions)
37
38 def format_versions(versions):
39     result = set()
40     for version in versions:
41         result.add(version.name)
42     result = list(result)
43     result.sort()
44     return ", ".join(result)
45
46 class JiraFixedIssuesDirective(Directive):
47     """
48     JIRA Fixed Issues directive
49     """
50     has_content = True
51     required_arguments = 0
52     optional_arguments = 0
53
54     option_spec = {
55         "project": directives.unchanged_required,
56         "versions": directives.unchanged_required,
57     }
58
59     def run(self):
60         (jira, prj, from_ver, to_ver, versions) = jira_prj_versions(self.options.get('project'), self.options.get('versions'))
61
62         query = 'project = %s AND resolution is not EMPTY AND fixVersion in (%s) ORDER BY type ASC' % (prj, versions)
63         issues = jira.search_issues(query)
64
65         # FIXME: this is not quite nice: can we emit the table markup directly
66         table = [
67             '.. list-table:: Issues resolved in versions %s through %s (`JIRA <https://jira.opendaylight.org/issues/?jql=%s>`__)' % (from_ver, to_ver, quote(query)),
68             '   :class: datatable',
69             '   :header-rows: 1',
70             '   :widths: auto',
71             '',
72             '   * - Type',
73             '     - Key',
74             '     - Summary',
75             '     - Resolution',
76             '     - Fix Version(s)',
77         ]
78
79         if issues:
80             for issue in issues:
81                 table.append('   * - .. image:: %s' % issue.fields.issuetype.iconUrl)
82                 table.append('          :align: center')
83                 table.append('          :alt: %s' % issue.fields.issuetype.name)
84                 table.append('     - `%s <https://jira.opendaylight.org/browse/%s>`_' % (issue.key, issue.key))
85                 table.append('     - %s' % issue.fields.summary)
86                 table.append('     - %s' % issue.fields.resolution)
87                 table.append('     - %s' % format_versions(issue.fields.fixVersions))
88
89             table.append('')
90
91             for idx, line in enumerate(table):
92                 self.content.data.insert(idx, line)
93                 self.content.items.insert(idx, (None, idx))
94
95         node = nodes.container()
96         self.state.nested_parse(self.content, self.content_offset, node)
97         return node.children
98
99 class JiraKnownIssuesDirective(Directive):
100     """
101     JIRA Known Issues directive
102     """
103     has_content = True
104     required_arguments = 0
105     optional_arguments = 0
106
107     option_spec = {
108         "project": directives.unchanged_required,
109         "versions": directives.unchanged_required,
110     }
111
112     def run(self):
113         (jira, prj, from_ver, to_ver, versions) = jira_prj_versions(self.options.get('project'), self.options.get('versions'))
114
115         query = 'project = %s AND affectedVersion in (%s) AND fixVersion NOT in (%s) ORDER BY type ASC' % (prj, versions, versions)
116         issues = jira.search_issues(query)
117
118         # FIXME: this is not quite nice: can we emit the table markup directly
119         table = [
120             '.. list-table:: Issues affecting versions %s through %s (`JIRA <https://jira.opendaylight.org/issues/?jql=%s>`__)' % (from_ver, to_ver, quote(query)),
121             '   :class: datatable',
122             '   :header-rows: 1',
123             '   :widths: auto',
124             '',
125             '   * - Type',
126             '     - Key',
127             '     - Summary',
128             '     - Status',
129             '     - Affected Version(s)',
130             '     - Fix Version(s)',
131         ]
132
133         if issues:
134             for issue in issues:
135                 fixVersions = format_versions(issue.fields.fixVersions)
136                 affectvedVersions = format_versions(issue.fields.versions)
137                 table.append('   * - .. image:: %s' % issue.fields.issuetype.iconUrl)
138                 table.append('          :align: center')
139                 table.append('          :alt: %s' % issue.fields.issuetype.name)
140                 table.append('     - `%s <https://jira.opendaylight.org/browse/%s>`_' % (issue.key, issue.key))
141                 table.append('     - %s' % issue.fields.summary)
142                 table.append('     - %s' % issue.fields.status)
143                 table.append('     - %s' % fixVersions)
144                 table.append('     - %s' % affectvedVersions)
145
146             table.append('')
147
148             for idx, line in enumerate(table):
149                 self.content.data.insert(idx, line)
150                 self.content.items.insert(idx, (None, idx))
151
152         node = nodes.container()
153         self.state.nested_parse(self.content, self.content_offset, node)
154         return node.children
155
156 def setup(app):
157     """
158     :type app: sphinx.application.Sphinx
159     """
160     app.add_directive('jira_fixed_issues', JiraFixedIssuesDirective)
161     app.add_directive('jira_known_issues', JiraKnownIssuesDirective)
162
163     # https://datatables.net/ improvements to tables
164     app.add_css_file("https://cdn.datatables.net/1.11.2/css/jquery.dataTables.min.css")
165     app.add_js_file("https://cdn.datatables.net/1.11.2/js/jquery.dataTables.min.js")
166     app.add_js_file(None, **{"body": "$(document).ready( function () { $('table.datatable').DataTable(); } );", "type": "text/javascript", "class": "init"})
167
168     return {
169         'version': '0.1',
170         'parallel_read_safe': True,
171         'parallel_write_safe': True,
172     }
173