Extract JIRA issues
[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 import re
17 import sphinx
18
19 __copyright__ = "Copyright(c) 2021 PANTHEON.tech, s.r.o."
20 __license__ = "Eclipse Public License v1.0"
21
22 class JiraFixedIssuesDirective(Directive):
23     """
24     JIRA Fixed Issues directive
25     """
26     has_content = True
27     required_arguments = 0
28     optional_arguments = 0
29
30     option_spec = {
31         "project": directives.unchanged_required,
32         "versions": directives.unchanged_required,
33     }
34
35     def run(self):
36         jira = JIRA(server="https://jira.opendaylight.org")
37         prj = jira.project(self.options.get('project'))
38         (from_ver, to_ver) = self.options.get('versions').split('-', 1)
39
40         versions = set()
41         for ver in jira.project_versions(prj):
42             if ver.name >= from_ver and ver.name <= to_ver:
43                 versions.add(ver.name)
44         versions = ", ".join(versions)
45
46         # FIXME: this is not quite nice: can we emit the table markup directly
47         table = [
48             '.. list-table:: Issues resolved in versions %s through %s' % (from_ver, to_ver),
49             # FIXME: bind to https://datatables.net/
50             '   :class: datatable',
51             '   :header-rows: 1',
52             '   :widths: auto',
53             '',
54             '   * - Type',
55             '     - Key',
56             '     - Summary',
57             '     - Resolution',
58             '     - Fix Version(s)',
59         ]
60
61         issues = jira.search_issues('project = %s AND resolution is not EMPTY AND fixVersion in (%s) ORDER BY type ASC' % (prj, versions))
62         for issue in issues:
63             # Groom fixVersions
64             fixVersions = set()
65             for version in issue.fields.fixVersions:
66                 fixVersions.add(version.name)
67             fixVersions = list(fixVersions)
68             fixVersions.sort()
69
70             table.append('   * - .. image:: %s' % issue.fields.issuetype.iconUrl)
71             table.append('          :align: center')
72             table.append('          :alt: %s' % issue.fields.issuetype.name)
73             table.append('     - `%s <https://jira.opendaylight.org/browse/%s>`_' % (issue.key, issue.key))
74             table.append('     - %s' % issue.fields.summary)
75             table.append('     - %s' % issue.fields.resolution)
76             table.append('     - %s' % ", ".join(fixVersions))
77
78         table.append('')
79
80         for idx, line in enumerate(table):
81             self.content.data.insert(idx, line)
82             self.content.items.insert(idx, (None, idx))
83
84         node = nodes.container()
85         self.state.nested_parse(self.content, self.content_offset, node)
86         return node.children
87
88 class JiraKnownIssuesDirective(Directive):
89     """
90     JIRA Known Issues directive
91     """
92     has_content = True
93     required_arguments = 0
94     optional_arguments = 0
95
96     option_spec = {
97         "project": directives.unchanged_required,
98         "versions": directives.unchanged_required,
99     }
100
101     def run(self):
102         jira = JIRA(server="https://jira.opendaylight.org")
103         prj = jira.project(self.options.get('project'))
104         (from_ver, to_ver) = self.options.get('versions').split('-', 1)
105
106         versions = set()
107         for ver in jira.project_versions(prj):
108             if ver.name >= from_ver and ver.name <= to_ver:
109                 versions.add(ver.name)
110         versions = ", ".join(versions)
111
112         # FIXME: this is not quite nice: can we emit the table markup directly
113         table = [
114             '.. list-table:: Issues affecting versions %s through %s' % (from_ver, to_ver),
115             # FIXME: bind to https://datatables.net/
116             '   :class: datatable',
117             '   :header-rows: 1',
118             '   :widths: auto',
119             '',
120             '   * - Type',
121             '     - Key',
122             '     - Summary',
123             '     - Status',
124             '     - Affected Version(s)',
125             '     - Fix Version(s)',
126         ]
127
128         issues = jira.search_issues('project = %s AND affectedVersion in (%s) AND fixVersion NOT in (%s) ORDER BY type ASC' % (prj, versions, versions))
129         for issue in issues:
130             # Groom fixVersions
131             fixVersions = set()
132             for version in issue.fields.fixVersions:
133                 fixVersions.add(version.name)
134             fixVersions = list(fixVersions)
135             fixVersions.sort()
136
137             # Groom affectedVersions
138             affectedVersions = set()
139             for version in issue.fields.versions:
140                 affectedVersions.add(version.name)
141             affectedVersions = list(affectedVersions)
142             affectedVersions.sort()
143
144             table.append('   * - .. image:: %s' % issue.fields.issuetype.iconUrl)
145             table.append('          :align: center')
146             table.append('          :alt: %s' % issue.fields.issuetype.name)
147             table.append('     - `%s <https://jira.opendaylight.org/browse/%s>`_' % (issue.key, issue.key))
148             table.append('     - %s' % issue.fields.summary)
149             table.append('     - %s' % issue.fields.status)
150             table.append('     - %s' % ", ".join(affectedVersions))
151             table.append('     - %s' % ", ".join(fixVersions))
152
153         table.append('')
154
155         for idx, line in enumerate(table):
156             self.content.data.insert(idx, line)
157             self.content.items.insert(idx, (None, idx))
158
159         node = nodes.container()
160         self.state.nested_parse(self.content, self.content_offset, node)
161         return node.children
162
163 def setup(app):
164     """
165     :type app: sphinx.application.Sphinx
166     """
167     app.add_directive('jira_fixed_issues', JiraFixedIssuesDirective)
168     app.add_directive('jira_known_issues', JiraKnownIssuesDirective)
169
170     return {
171         'version': '0.1',
172         'parallel_read_safe': True,
173         'parallel_write_safe': True,
174     }
175