├── launchpad_report
├── __init__.py
├── hcf.html
├── leads.html
├── hcf.yaml
├── render.py
├── config.yaml
├── utils.py
├── leads.yaml
├── template.html
├── checks.py
└── report.py
├── test-requirements.txt
├── requirements.txt
├── .gitignore
├── TODO.txt
├── tox.ini
├── setup.py
├── README.rst
├── cli.py
└── lptool.py
/launchpad_report/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | -r requirements.txt
2 | hacking==0.7
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | launchpadlib
2 | PyYAML
3 | jinja2
4 | argparse
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .*.swp
3 | *.log
4 | dist
5 | nosetests.xml
6 | *.egg
7 | .testrepository
8 | .tox
9 | .venv
10 | .idea
11 | .DS_Store
12 | *.egg-info
13 | *.csv
14 | /report.html
15 | /report.json
16 | /cache
17 |
--------------------------------------------------------------------------------
/TODO.txt:
--------------------------------------------------------------------------------
1 | # TODO: implement work items
2 | # TODO: implement reviews checks + reviews checks as work items
3 | # TODO: implement bugs series checks as work items
4 | # TODO: proceed blueprint header
5 | # TODO: check milestones for bug series, collect statuses for backports, triage them
6 | # TODO: integration with gerrit
7 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 1.6
3 | skipsdist = True
4 | ;envlist = py26,py27,pep8
5 | envlist = pep8
6 |
7 | [testenv]
8 | usedevelop = True
9 | install_command = pip install {packages}
10 | setenv = VIRTUAL_ENV={envdir}
11 | deps = -r{toxinidir}/test-requirements.txt
12 | commands =
13 | nosetests {posargs:launchpad_report}
14 |
15 | [tox:jenkins]
16 | downloadcache = ~/cache/pip
17 |
18 | [testenv:pep8]
19 | deps = hacking==0.7
20 | usedevelop = False
21 | commands =
22 | flake8 {posargs:.}
23 |
24 | [testenv:venv]
25 | commands = {posargs:}
26 |
27 | [testenv:devenv]
28 | envdir = devenv
29 | usedevelop = True
30 |
31 | [flake8]
32 | ignore = H234,H302,H802
33 | exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
34 | show-pep8 = True
35 | show-source = True
36 | count = True
37 |
38 | [hacking]
39 | import_exceptions = testtools.matchers
40 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from setuptools import find_packages
4 | from setuptools import setup
5 |
6 |
7 | def parse_requirements_txt():
8 | root = os.path.dirname(os.path.abspath(__file__))
9 | requirements = []
10 | with open(os.path.join(root, 'requirements.txt'), 'r') as f:
11 | for line in f.readlines():
12 | line = line.rstrip()
13 | if not line or line.startswith('#'):
14 | continue
15 | requirements.append(line)
16 | return requirements
17 |
18 |
19 | setup(
20 | name='launchpad_report',
21 | version='0.0.1',
22 | description='Find inconsistencies in launchpad blueprints and bugs',
23 | classifiers=[
24 | "Programming Language :: Python",
25 | "Topic :: Utilities"
26 | ],
27 | author='Mirantis Inc.',
28 | author_email='dpyzhov@mirantis.com',
29 | url='http://wiki.openstack.org/wiki/Fuel',
30 | packages=find_packages(),
31 | zip_safe=False,
32 | install_requires=parse_requirements_txt(),
33 | include_package_data=True,
34 | package_data={'': ['*.yaml']},
35 | entry_points={'console_scripts': ['lp-report = launchpad_report.cli:main']}
36 | )
37 |
--------------------------------------------------------------------------------
/launchpad_report/hcf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | lp-report: {{ config.project }}
4 |
23 |
24 |
25 | lp-report: {{ config.project }}
26 | Bugs on teams
27 |
28 | {% set team_groups = rows|groupby('team') %}
29 | {% for team in team_groups %}
30 | - {{ team.grouper }}
31 | {% endfor %}
32 |
33 | {% for team_group in team_groups %}
34 |
35 |
36 | {% for status_group in team_group.list|groupby('short_status') %}
37 | | {{ status_group.grouper }} {{ team_group.grouper }} - {{ status_group.list|length }} bug(s) |
38 |
39 | | Title |
40 | Milestone |
41 | Status |
42 | Priority |
43 | Assignee |
44 | Full Name |
45 | Required triage actions |
46 |
47 | {% for item in status_group.list|sort(attribute='milestone') %}
48 | {% if item.type == "bug" %}
49 |
50 | | {{ item.title }} |
51 | {{ item.milestone }} |
52 | {{ item.status }} |
53 | {{ item.priority }} |
54 | {{ item.assignee }} |
55 | {{ item.name }} |
56 | {{ item.triage }} |
57 |
58 | {% endif %}
59 | {% endfor %}
60 | {% endfor %}
61 |
62 | {% endfor %}
63 |
64 |
65 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | launchpad-report
2 | ================
3 |
4 | Gather csv statistics for launchpad project, aggregate teams info, list triage
5 | actions.
6 |
7 | Report example:
8 |
9 | ,Link,Title,Status,Priority,Team,Nick,Name,Triage actions
10 | bp,https://blueprints.launchpad.net/fuel/+spec/horizon-basic-auth-by-default,Please add basic auth to horizon UI,Unknown,Low,unknown,unassigned,unassigned,No assignee
11 | bp,https://blueprints.launchpad.net/fuel/+spec/external-mongodb-support,Implement possibility to set external MongoDB connection,Needs Code Review,Undefined,mos,iberezovskiy,Ivan Berezovskiy,"No priority, No series"
12 | bug,https://bugs.launchpad.net/fuel/+bug/1332097,Error during the deployment,Confirmed,Critical,library,fuel-library,Fuel Library Team,Related to non-current milestone (4.1.2)
13 | bug,https://bugs.launchpad.net/fuel/+bug/1342617,Need to add possibility to build tarballs for patching only,New,High,python,ikalnitsky,Igor Kalnitsky,Not triaged
14 |
15 |
16 | Installation
17 | ============
18 | In virtual env, run pip install -r requirements.txt. Check Known Issues.
19 |
20 | Known Issues
21 | ============
22 |
23 | lazr.authentication (requirement for launchpadlib) is broken on pypi. You can install it manually from `launchpad `_:
24 |
25 | pip install https://launchpad.net/lazr.authentication/trunk/0.1.2/+download/lazr.authentication-0.1.2.tar.gz
26 |
27 |
28 | How to use
29 | ==========
30 | In order to get list of bugs per particular team lead:
31 | $ python cli.py -c launchpad_report/leads.yaml --template=launchpad_report/leads.html
32 |
33 | In order to get list of bugs affecting HCF:
34 | $ python cli.py -c launchpad_report/hcf.yaml --template=launchpad_report/hcf.html
35 |
--------------------------------------------------------------------------------
/launchpad_report/leads.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | lp-report: {{ config.project }}
4 |
23 |
24 |
25 | lp-report: {{ config.project }}
26 | Bugs on teams
27 |
28 | {% set team_groups = rows|groupby('team') %}
29 | {% for team in team_groups %}
30 | - {{ team.grouper }}
31 | {% endfor %}
32 |
33 | {% for team_group in team_groups %}
34 |
35 |
36 | {% for status_group in team_group.list|groupby('short_status') %}
37 | {% if status_group.grouper in ['open', 'untriaged'] %}
38 | | {{ status_group.grouper }} {{ team_group.grouper }} - {{ team_group.list|length }} bug(s) |
39 |
40 | | Title |
41 | Milestone |
42 | Status |
43 | Priority |
44 | Assignee |
45 | Full Name |
46 | Required triage actions |
47 |
48 | {% for item in status_group.list|sort(attribute='milestone') %}
49 | {% if item.type == "bug" %}
50 |
51 | | {{ item.title }} |
52 | {{ item.milestone }} |
53 | {{ item.status }} |
54 | {{ item.priority }} |
55 | {{ item.assignee }} |
56 | {{ item.name }} |
57 | {{ item.triage }} |
58 |
59 | {% endif %}
60 | {% endfor %}
61 | {% endif %}
62 | {% endfor %}
63 |
64 | {% endfor %}
65 |
66 |
67 |
--------------------------------------------------------------------------------
/launchpad_report/hcf.yaml:
--------------------------------------------------------------------------------
1 | project:
2 | - fuel
3 | - mos
4 | # For debug. Proceed only limited amount of items in project
5 | trunc_report: 0
6 | # used for HCF calcs only
7 | hcf: 1
8 | cache_dir: cache
9 | use_auth: True
10 | teams:
11 | Fuel:
12 | fuel-python
13 | alekseyk-ru
14 | dshulyak
15 | rustyrobot
16 | ikalnitsky
17 | nmarkov
18 | aroma-x
19 | akislitsky
20 | kozhukalov
21 | lux-place
22 | fuel-ui
23 | vkramskikh
24 | astepanchuk
25 | bdudko
26 | kpimenova
27 | jkirnosova
28 | fuel-library
29 | fuel-astute
30 | vsharshov
31 | a-gordeev
32 | xenolog
33 | adidenko
34 | raytrac3r
35 | idv1985
36 | vkuklin
37 | sbogatkin
38 | xdeller
39 | andreika-mail
40 | manashkin
41 | ekozhemyakin
42 | akolesnikov
43 | fsoppelsa
44 | fuel-osci
45 | sotpuschennikov
46 | dburmistrov
47 | vparakhin
48 | r0mikiam
49 | mrasskazov
50 | dborodaenko
51 | rmoe
52 | xarses
53 | tzn
54 | salmon
55 | ksambor
56 | prmtl
57 | loles
58 | sgolovatiuk
59 | bogdando
60 | longgeek
61 | berendt
62 | jesse-pretorius
63 | sammiestoel
64 | zynzel
65 | vpleshakov
66 | vdenisov
67 | nikishov-da
68 | atarasov
69 | dtyzhnenko
70 | mos-linux:
71 | mos-linux
72 | apodrepniy
73 | kdanylov
74 | msemenov
75 | asheplyakov
76 | asyriy
77 | mos-openstack:
78 | mos-neutron
79 | mos-nova
80 | mos-horizon
81 | mos-ceilometer
82 | mos-oslo
83 | mos-sahara
84 | mos-heat
85 | mos-murano
86 | iberezovskiy
87 | e0ne
88 | dmitrymex
89 | smurashov
90 | rpodolyaka
91 | skolekonov
92 | vrovachev
93 | ylobankov
94 | alexei-kornienko
95 | aepifanov
96 | akamyshnikova
97 | dbelova
98 | efedorova
99 | enikanorov
100 | iyozhikov
101 | shakhat
102 | mdurnosvistov
103 | ruhe
104 | smelikyan
105 | skolekonov
106 | skraynev
107 | sreshetniak
108 | tnurlygayanov
109 | tsufiev-x
110 | yorik-sar
111 | mmaxur
112 | maxmazurenka
113 | teselkin-d
114 | obondarev
115 | akuznetsova
116 | i159
117 | akurilin
118 | pkholkin
119 | ekudryashova
120 | Partners:
121 | fuel-partner
122 | eshumakher
123 | izinovik
124 | gcon-monolake
125 | igajsin
126 | moshele
127 | aviramb
128 | srogov
129 | ekorekin
130 | excludes:
131 | fuel-devops
132 | afedorova
133 | teran
134 | acharykov
135 | dreidellhasa
136 | docaedo
137 | fuel-qa
138 | apalkina
139 | aurlapova
140 | tatyana-leontovich
141 | asledzinskiy
142 | apanchenko-8
143 | ykotko
144 |
--------------------------------------------------------------------------------
/launchpad_report/render.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 |
4 | from io import BytesIO
5 | from jinja2 import Environment
6 | from jinja2 import FileSystemLoader
7 |
8 |
9 | import codecs
10 | import cStringIO
11 | import csv
12 |
13 |
14 | class UnicodeWriter:
15 | def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
16 | # Redirect output to a queue
17 | self.queue = cStringIO.StringIO()
18 | self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
19 | self.stream = f
20 | self.encoder = codecs.getincrementalencoder(encoding)()
21 |
22 | def writerow(self, row):
23 | self.writer.writerow([s.encode("utf-8") for s in row])
24 | # Fetch UTF-8 output from the queue ...
25 | data = self.queue.getvalue()
26 | data = data.decode("utf-8")
27 | # ... and reencode it into the target encoding
28 | data = self.encoder.encode(data)
29 | # write to the target stream
30 | self.stream.write(data)
31 | # empty queue
32 | self.queue.truncate(0)
33 |
34 | def writerows(self, rows):
35 | for row in rows:
36 | self.writerow(row)
37 |
38 |
39 | class Renderer(object):
40 | def __init__(self, filename):
41 | self.filename = filename
42 |
43 | def render(self, data):
44 | if self.filename == '-':
45 | print(self._render(data))
46 | else:
47 | rep_file = open(self.filename, 'wb')
48 | rep_file.write(self._render(data))
49 |
50 |
51 | class CSVRenderer(Renderer):
52 | def _render(self, data):
53 | csvfile = BytesIO()
54 | reporter = UnicodeWriter(csvfile)
55 | reporter.writerow([
56 | '', 'Link', 'Title', 'Milestone', 'Short status', 'Status',
57 | 'Priority', 'Team', 'Nick', 'Name', 'Triage actions'
58 | ])
59 | for row in data['rows']:
60 | reporter.writerow([
61 | row['type'], row['link'], row['title'], row['milestone'],
62 | row['short_status'], row['status'], row['priority'],
63 | row['team'], row['assignee'], row['name'], row['triage']
64 | ])
65 | return csvfile.getvalue()
66 |
67 |
68 | class JSONRenderer(Renderer):
69 | def _render(self, data):
70 | return json.dumps(data)
71 |
72 |
73 | class HTMLRenderer(Renderer):
74 | def __init__(self, filename, template_filename):
75 | self.filename = filename
76 | self.template_filename = template_filename
77 |
78 | def _render(self, data):
79 | env = Environment(
80 | loader=FileSystemLoader(
81 | os.path.dirname(os.path.abspath(self.template_filename))
82 | )
83 | )
84 | template = env.get_template(
85 | os.path.basename(os.path.abspath(self.template_filename))
86 | )
87 | return template.render(data)
88 |
--------------------------------------------------------------------------------
/launchpad_report/config.yaml:
--------------------------------------------------------------------------------
1 | project:
2 | - fuel
3 | # For debug. Proceed only limited amount of items in project
4 | trunc_report: 0
5 | cache_dir: cache
6 | use_auth: True
7 | teams:
8 | python:
9 | fuel-python
10 | alekseyk-ru
11 | dshulyak
12 | rustyrobot
13 | ikalnitsky
14 | nmarkov
15 | aroma-x
16 | akislitsky
17 | kozhukalov
18 | lux-place
19 | a-gordeev
20 | ui:
21 | fuel-ui
22 | vkramskikh
23 | astepanchuk
24 | bdudko
25 | kpimenova
26 | jkirnosova
27 | library:
28 | fuel-library
29 | sgolovatiuk
30 | xenolog
31 | adidenko
32 | raytrac3r
33 | idv1985
34 | bogdando
35 | vkuklin
36 | sbogatkin
37 | xdeller
38 | andreika-mail
39 | mmaxur
40 | maxmazurenka
41 | l2:
42 | manashkin
43 | ekozhemyakin
44 | akolesnikov
45 | fsoppelsa
46 | astute:
47 | fuel-astute
48 | vsharshov
49 | osci:
50 | fuel-osci
51 | sotpuschennikov
52 | dburmistrov
53 | vparakhin
54 | r0mikiam
55 | mrasskazov
56 | qa:
57 | fuel-qa
58 | apalkina
59 | aurlapova
60 | tatyana-leontovich
61 | asledzinskiy
62 | apanchenko-8
63 | ykotko
64 | devops:
65 | fuel-devops
66 | afedorova
67 | teran
68 | acharykov
69 | us:
70 | dborodaenko
71 | rmoe
72 | xarses
73 | dreidellhasa
74 | docaedo
75 | moslinux:
76 | mos-linux
77 | apodrepniy
78 | kdanylov
79 | msemenov
80 | asheplyakov
81 | asyriy
82 | mosopenstack:
83 | mos-neutron
84 | mos-nova
85 | mos-horizon
86 | mos-ceilometer
87 | mos-oslo
88 | mos-sahara
89 | mos-heat
90 | mos-murano
91 | iberezovskiy
92 | e0ne
93 | dmitrymex
94 | smurashov
95 | rpodolyaka
96 | skolekonov
97 | vrovachev
98 | ylobankov
99 | alexei-kornienko
100 | aepifanov
101 | akamyshnikova
102 | dbelova
103 | efedorova
104 | enikanorov
105 | iyozhikov
106 | shakhat
107 | mdurnosvistov
108 | ruhe
109 | smelikyan
110 | skolekonov
111 | skraynev
112 | sreshetniak
113 | tnurlygayanov
114 | tsufiev-x
115 | yorik-sar
116 | teselkin-d
117 | obondarev
118 | akuznetsova
119 | i159
120 | akurilin
121 | pkholkin
122 | ekudryashova
123 | partners:
124 | fuel-partner
125 | eshumakher
126 | izinovik
127 | gcon-monolake
128 | igajsin
129 | moshele
130 | aviramb
131 | srogov
132 | ekorekin
133 | poland:
134 | tzn
135 | salmon
136 | ksambor
137 | prmtl
138 | loles
139 | external:
140 | longgeek
141 | berendt
142 | jesse-pretorius
143 | sammiestoel
144 | other_mirantis:
145 | zynzel
146 | vpleshakov
147 | vdenisov
148 | nikishov-da
149 | atarasov
150 | dtyzhnenko
151 |
--------------------------------------------------------------------------------
/launchpad_report/utils.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 |
4 |
5 | untriaged_bug_statuses = [
6 | 'New',
7 | ]
8 |
9 | open_bug_statuses = [
10 | 'Incomplete', 'Confirmed', 'Triaged', 'In Progress',
11 | 'Incomplete (with response)', 'Incomplete (without response)',
12 | ]
13 |
14 | open_bug_statuses_for_HCF = [
15 | 'Confirmed', 'Triaged', 'In Progress',
16 | ]
17 |
18 | rejected_bug_statuses = [
19 | 'Opinion', 'Invalid', 'Won\'t Fix', 'Expired',
20 | ]
21 |
22 | closed_bug_statuses = [
23 | 'Fix Committed', 'Fix Released',
24 | ] + rejected_bug_statuses
25 |
26 | all_bug_statuses = (
27 | untriaged_bug_statuses + open_bug_statuses + closed_bug_statuses
28 | )
29 |
30 | untriaged_bp_statuses = [
31 | 'Unknown',
32 | ]
33 |
34 | untriaged_bp_def_statuses = [
35 | 'New',
36 | ]
37 |
38 | rejected_bp_def_statuses = ['Superseded', 'Obsolete']
39 |
40 | closed_bp_statuses = ['Implemented']
41 |
42 | valid_bp_priorities = [
43 | 'Essential', 'High', 'Medium', 'Low'
44 | ]
45 |
46 | valid_bug_priorities = [
47 | 'Critical', 'High', 'Medium', 'Low', 'Wishlist'
48 | ]
49 |
50 |
51 | logger = logging.getLogger(__name__)
52 |
53 | cached_names = {
54 | }
55 |
56 |
57 | def is_bug(obj):
58 | return (
59 | obj.resource_type_link == u'https://api.launchpad.net/devel/#bug_task'
60 | )
61 |
62 |
63 | def is_bp(obj):
64 | return (
65 | obj.resource_type_link ==
66 | u'https://api.launchpad.net/devel/#specification'
67 | )
68 |
69 |
70 | def is_project(obj):
71 | return (
72 | obj.resource_type_link ==
73 | u'https://api.launchpad.net/devel/#project'
74 | )
75 |
76 |
77 | def is_series(obj):
78 | return (
79 | obj.resource_type_link ==
80 | u'https://api.launchpad.net/devel/#project_series'
81 | )
82 |
83 |
84 | def short_status(obj):
85 | if is_bp(obj):
86 | if obj.definition_status in rejected_bp_def_statuses:
87 | return 'rejected'
88 | if obj.implementation_status in closed_bp_statuses:
89 | return 'done'
90 | if (
91 | obj.definition_status in untriaged_bp_def_statuses or
92 | obj.assignee is None or
93 | obj.priority not in valid_bp_priorities or
94 | obj.implementation_status in untriaged_bp_statuses
95 | ):
96 | return 'untriaged'
97 | return 'open'
98 | if is_bug(obj):
99 | if obj.status in rejected_bug_statuses:
100 | return 'rejected'
101 | if obj.status in closed_bug_statuses:
102 | return 'done'
103 | if (
104 | obj.status in untriaged_bug_statuses or
105 | obj.assignee is None or
106 | obj.importance not in valid_bug_priorities
107 | ):
108 | return 'untriaged'
109 | return 'open'
110 | return 'unknown'
111 |
112 |
113 | def get_name(obj):
114 | key = obj._wadl_resource._url
115 | if key not in cached_names:
116 | logger.debug("Miss name: " + key)
117 | cached_names[key] = obj.name
118 | else:
119 | logger.debug("Hit name: " + key)
120 | return cached_names[key]
121 |
122 |
123 | def printn(text):
124 | sys.stdout.write(text)
125 | sys.stdout.flush()
126 |
--------------------------------------------------------------------------------
/launchpad_report/leads.yaml:
--------------------------------------------------------------------------------
1 | project:
2 | - fuel
3 | # For debug. Proceed only limited amount of items in project
4 | trunc_report: 0
5 | cache_dir: cache
6 | use_auth: True
7 | teams:
8 | dpyzhov:
9 | fuel-python
10 | alekseyk-ru
11 | dshulyak
12 | rustyrobot
13 | ikalnitsky
14 | nmarkov
15 | aroma-x
16 | akislitsky
17 | kozhukalov
18 | lux-place
19 | ivankliuk
20 | vkramskikh:
21 | fuel-ui
22 | vkramskikh
23 | astepanchuk
24 | bdudko
25 | kpimenova
26 | jkirnosova
27 | vkuklin:
28 | fuel-library
29 | fuel-astute
30 | vsharshov
31 | a-gordeev
32 | xenolog
33 | adidenko
34 | raytrac3r
35 | idv1985
36 | dmy-ilyin
37 | vkuklin
38 | sbogatkin
39 | xdeller
40 | andreika-mail
41 | manashkin:
42 | manashkin
43 | ekozhemyakin
44 | akolesnikov
45 | fsoppelsa
46 | rvyalov:
47 | fuel-osci
48 | sotpuschennikov
49 | dburmistrov
50 | vparakhin
51 | r0mikiam
52 | mrasskazov
53 | dkaiharodsev
54 | aurlapova:
55 | fuel-qa
56 | apalkina
57 | aurlapova
58 | tatyana-leontovich
59 | asledzinskiy
60 | apanchenko-8
61 | ykotko
62 | komelchenko
63 | ddmitriev
64 | dtyzhnenko
65 | ishishkin:
66 | fuel-devops
67 | afedorova
68 | teran
69 | acharykov
70 | skulanov
71 | dborodaenko:
72 | dborodaenko
73 | rmoe
74 | xarses
75 | dreidellhasa
76 | docaedo
77 | fuel-docs
78 | msemenov:
79 | mos-linux
80 | apodrepniy
81 | kdanylov
82 | msemenov
83 | asheplyakov
84 | asyriy
85 | vlos
86 | amogylchenko
87 | imarnat:
88 | mos-neutron
89 | mos-keystone
90 | mos-nova
91 | mos-horizon
92 | mos-ceilometer
93 | mos-oslo
94 | mos-sahara
95 | mos-heat
96 | mos-cinder
97 | mos-murano
98 | mos-glance
99 | degorenko
100 | iberezovskiy
101 | e0ne
102 | dmitrymex
103 | smurashov
104 | rpodolyaka
105 | skolekonov
106 | vrovachev
107 | ylobankov
108 | alexei-kornienko
109 | aepifanov
110 | akamyshnikova
111 | dbelova
112 | efedorova
113 | enikanorov
114 | iyozhikov
115 | shakhat
116 | mdurnosvistov
117 | ruhe
118 | smelikyan
119 | skolekonov
120 | skraynev
121 | sreshetniak
122 | tnurlygayanov
123 | tsufiev-x
124 | yorik-sar
125 | mmaxur
126 | maxmazurenka
127 | teselkin-d
128 | obondarev
129 | akuznetsova
130 | i159
131 | akurilin
132 | pkholkin
133 | ekudryashova
134 | eshumakher:
135 | fuel-partner
136 | eshumakher
137 | izinovik
138 | gcon-monolake
139 | igajsin
140 | moshele
141 | aviramb
142 | srogov
143 | ekorekin
144 | ipovolotskaya
145 | azemlyanov
146 | nuritv
147 | tnapierala:
148 | tzn
149 | salmon
150 | ksambor
151 | prmtl
152 | loles
153 | sgolovatiuk
154 | bogdando
155 | smakar
156 | omolchanov
157 | external:
158 | longgeek
159 | berendt
160 | jesse-pretorius
161 | sammiestoel
162 | other_mirantis:
163 | zynzel
164 | vpleshakov
165 | vdenisov
166 | nikishov-da
167 | atarasov
168 | agasanoff
169 | slava-val-al
170 | gleb-q
171 | dukov
172 | daniele-pizzolli
173 |
--------------------------------------------------------------------------------
/launchpad_report/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | lp-report: {{ config.project }}
4 |
23 |
24 |
25 | lp-report: {{ config.project }}
26 | Triage actions
27 | Blueprints
28 |
34 | Bugs
35 |
41 | Bugs on teams
42 |
43 | {% set team_groups = rows|groupby('team') %}
44 | {% for team in team_groups %}
45 | - {{ team.grouper }}
46 | {% endfor %}
47 |
48 |
49 | {% for type_group in rows|groupby('type') %}
50 | {% for status_group in type_group.list|groupby('short_status') %}
51 | |
52 | {{ status_group.grouper }} {{ type_group.grouper }}s
53 | |
54 |
55 | | Title |
56 | Milestone |
57 | Status |
58 | Priority |
59 | Team |
60 | Assignee |
61 | Full Name |
62 | Required triage actions |
63 |
64 | {% for item in status_group.list|sort(attribute='milestone') %}
65 | {% if item.triage %}
66 |
67 | | {{ item.title }} |
68 | {{ item.milestone }} |
69 | {{ item.status }} |
70 | {{ item.priority }} |
71 | {{ item.team }} |
72 | {{ item.assignee }} |
73 | {{ item.name }} |
74 | {{ item.triage }} |
75 |
76 | {% endif %}
77 | {% endfor %}
78 | {% endfor %}
79 | {% endfor %}
80 |
81 | {% for team_group in team_groups %}
82 |
83 |
84 | {% for status_group in team_group.list|groupby('short_status') %}
85 | {% if status_group.grouper in ['open', 'untriaged'] %}
86 | | {{ status_group.grouper }} {{ team_group.grouper }} |
87 |
88 | | Title |
89 | Milestone |
90 | Status |
91 | Priority |
92 | Team |
93 | Assignee |
94 | Full Name |
95 | Required triage actions |
96 |
97 | {% for item in status_group.list|sort(attribute='milestone') %}
98 | {% if item.type == "bug" %}
99 |
100 | | {{ item.title }} |
101 | {{ item.milestone }} |
102 | {{ item.status }} |
103 | {{ item.priority }} |
104 | {{ item.team }} |
105 | {{ item.assignee }} |
106 | {{ item.name }} |
107 | {{ item.triage }} |
108 |
109 | {% endif %}
110 | {% endfor %}
111 | {% endif %}
112 | {% endfor %}
113 |
114 | {% endfor %}
115 |
116 |
117 |
--------------------------------------------------------------------------------
/cli.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | import sys
4 | import argparse
5 | from launchpad_report.report import Report
6 |
7 | import httplib
8 | import traceback
9 |
10 |
11 | logger = logging.getLogger(__name__)
12 |
13 |
14 | # All this http-related code is an attempt to cache duplicated requests
15 | # Right now it just finds all duplicated requests and send traceback.
16 |
17 | my_cache = {}
18 |
19 |
20 | def my_request(*args, **kwargs):
21 | # self = args[0]
22 | method = args[1]
23 | url = args[2]
24 | if method == 'GET':
25 | if url in my_cache:
26 | logger.debug("Hit " + url)
27 | logger.debug(''.join(traceback.format_stack()))
28 | # return
29 | else:
30 | logger.debug("Miss " + url)
31 | my_cache[url] = 1
32 | # self.my_url = url
33 | # self.my_method = method
34 | return old_httplib_request(*args, **kwargs)
35 |
36 |
37 | class my_resp_obj(dict):
38 | def __init__(self, obj):
39 | self.status = obj.status
40 | self.reason = obj.reason
41 | self.data = obj.read()
42 | self.headers = obj.getheaders()
43 |
44 | def read(self):
45 | return self.data
46 |
47 | def getheaders(self):
48 | return self.headers
49 |
50 |
51 | def my_response(*args, **kwargs):
52 | self = args[0]
53 | method = self.my_method
54 | url = self.my_url
55 | if (
56 | method == 'GET' and
57 | (url.startswith('/devel/~') or url.startswith('/devel/fuel'))
58 | ):
59 | if url in my_cache:
60 | logger.debug("Hit " + url)
61 | else:
62 | logger.debug("Miss " + url)
63 | my_cache[url] = 1
64 | # my_resp_obj(old_httplib_response(*args, **kwargs))
65 | # return my_cache[url]
66 | # return old_httplib_response(*args, **kwargs)
67 |
68 | old_httplib_request = httplib.HTTPConnection.request
69 | httplib.HTTPConnection.request = my_request
70 | #old_httplib_response = httplib.HTTPConnection.getresponse
71 | #httplib.HTTPConnection.getresponse = my_response
72 |
73 | # /http_magic
74 |
75 | def main():
76 | reload(sys)
77 | sys.setdefaultencoding('utf-8')
78 | description = """
79 | Generate status report for bugs and blueprints in Launchpad project
80 | """
81 | parser = argparse.ArgumentParser(epilog=description)
82 | parser.add_argument(
83 | '--template', dest='template', action='store', type=str,
84 | help='html template file',
85 | default=os.path.join(os.path.dirname(__file__), 'template.html')
86 | )
87 | parser.add_argument(
88 | '-c', '--config', dest='config', action='store', type=str,
89 | help='yaml config file',
90 | default=os.path.join(os.path.dirname(__file__), 'config.yaml')
91 | )
92 | parser.add_argument(
93 | '--outjson', dest='outjson', action='store', type=str,
94 | help='where to output json report', default='report.json'
95 | )
96 | parser.add_argument(
97 | '--outcsv', dest='outcsv', action='store', type=str,
98 | help='where to output csv report', default='report.csv'
99 | )
100 | parser.add_argument(
101 | '--outhtml', dest='outhtml', action='store', type=str,
102 | help='where to output html report', default='report.html'
103 | )
104 | parser.add_argument(
105 | '--load-json', dest='loadjson', action='store', type=str,
106 | help='generate report from previous json report'
107 | )
108 | parser.add_argument(
109 | '-l', '--logfile', dest='logfile', action='store', type=str,
110 | help='Generate debug logfile'
111 | )
112 | parser.add_argument(
113 | '-a', '--all', dest='all', action='store_true', default=False,
114 | help='Generate report both for open and closed bugs and blueprints'
115 | )
116 | params, other_params = parser.parse_known_args()
117 |
118 | report = Report(
119 | config_filename=params.config
120 | )
121 |
122 | if params.logfile:
123 | logger.setLevel(logging.DEBUG)
124 | file_handler = logging.FileHandler(params.logfile)
125 | logger.addHandler(file_handler)
126 | else:
127 | logger.setLevel(logging.WARNING)
128 |
129 | if params.loadjson:
130 | report.load(params.loadjson)
131 | else:
132 | report.generate(all=params.all)
133 |
134 | report.render2csv(params.outcsv)
135 | report.render2json(params.outjson)
136 | report.render2html(params.outhtml, params.template)
137 |
138 | if __name__ == "__main__":
139 | main()
140 |
--------------------------------------------------------------------------------
/lptool.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import print_function
3 |
4 | import argparse
5 | from launchpadlib.launchpad import Launchpad
6 | import yaml
7 |
8 |
9 | def bulk():
10 | bulkfile = open('bulk.yaml')
11 | tasks = yaml.load(bulkfile)
12 | lp = Launchpad.login_with('lp-report-bot', 'production', version='devel')
13 | for prj_name in tasks.keys():
14 | prj = lp.projects[prj_name]
15 | prj_tasks = tasks[prj_name]
16 | for bp_name in prj_tasks['bp'].keys():
17 | bp = prj.getSpecification(name=bp_name)
18 | bp_tasks = prj_tasks['bp'][bp_name]
19 | if 'series' in bp_tasks:
20 | bp.proposeGoal(goal=prj.getSeries(name=bp_tasks['series']))
21 | if 'milestone' in bp_tasks:
22 | if bp_tasks['milestone'] != 'None':
23 | bp.milestone = prj.getMilestone(name=bp_tasks['milestone'])
24 | bp.lp_save()
25 | bp.proposeGoal(goal=bp.milestone.series_target)
26 | else:
27 | bp.milestone = None
28 | bp.lp_save()
29 | if 'approve' in bp_tasks:
30 | pass
31 |
32 | lp = None
33 | prj = None
34 |
35 |
36 | def update_bp(item_id, params):
37 | bp = prj.getSpecification(name=item_id)
38 | if params.milestone:
39 | print("Updating (%s) milestone to (%s)" % (
40 | item_id, params.milestone
41 | ))
42 | if params.milestone != 'None':
43 | bp.milestone = prj.getMilestone(name=params.milestone)
44 | bp.lp_save()
45 | bp.proposeGoal(goal=bp.milestone.series_target)
46 | else:
47 | bp.milestone = None
48 | bp.lp_save()
49 | if params.series:
50 | print("Updating (%s) series to (%s)" % (
51 | item_id, params.series
52 | ))
53 | if params.series != 'None':
54 | bp.proposeGoal(goal=prj.getSeries(name=params.series))
55 | else:
56 | bp.proposeGoal(goal=None)
57 | if params.approved:
58 | print("Approving (%s)" % item_id)
59 | bp.direction_approved = True
60 | bp.definition_status = 'Approved'
61 | bp.lp_save()
62 | if params.create:
63 | print("Creation of blueprints is not implemented")
64 | if params.delete:
65 | print("Removal of blueprints is not available")
66 | if params.priority:
67 | print("TODO")
68 | if params.status:
69 | print("Setting implementation status of (%s) to (%s)" % (
70 | item_id, params.status
71 | ))
72 | bp.implementation_status = params.status
73 | bp.lp_save()
74 |
75 |
76 | def update_bug(item_id, params):
77 | try:
78 | (bug_id, series_name) = item_id.split(":")
79 | except ValueError:
80 | (bug_id, series_name) = (item_id, None)
81 | bug = lp.bugs[bug_id]
82 | if series_name is None:
83 | series = prj
84 | else:
85 | series = prj.getSeries(name=series_name)
86 | bug_task = filter(lambda x: x.target == series, bug.bug_tasks)[0]
87 | if params.milestone:
88 | print("Updating (%s) milestone to (%s)" % (
89 | item_id, params.milestone
90 | ))
91 | if params.milestone != 'None':
92 | milestone = prj.getMilestone(name=params.milestone)
93 | is_active = milestone.is_active
94 | if not is_active:
95 | milestone.is_active = True
96 | milestone.lp_save()
97 | bug_task.milestone = prj.getMilestone(name=params.milestone)
98 | bug_task.lp_save()
99 | if not is_active:
100 | milestone.is_active = False
101 | milestone.lp_save()
102 | else:
103 | bug.milestone = None
104 | bug.lp_save()
105 | if params.series:
106 | print("Not suitable for bugs")
107 | if params.approved:
108 | print("Not suitable for bugs")
109 | if params.create:
110 | bug.addTask(target=series)
111 | if params.delete:
112 | bug_task.lp_delete()
113 | if params.priority:
114 | print("TODO")
115 | if params.status:
116 | print("Setting status of (%s) to (%s)" % (
117 | item_id, params.status
118 | ))
119 | bug.status = params.status
120 | bug.lp_save()
121 |
122 |
123 | def main():
124 | description = """
125 | Command line tool to operate with bugs and blueprints
126 | """
127 | parser = argparse.ArgumentParser(epilog=description)
128 | parser.add_argument('project', type=str)
129 | parser.add_argument('cmd', type=str, choices=['get', 'set'])
130 | parser.add_argument('item_type', type=str, choices=['bp', 'bug'])
131 | parser.add_argument('item_id', type=str, nargs='+')
132 | parser.add_argument('--milestone', type=str)
133 | parser.add_argument('--series', type=str)
134 | parser.add_argument('--approve', dest='approved', action='store_true')
135 | parser.add_argument('--create', action='store_true')
136 | parser.add_argument('--delete', action='store_true')
137 | parser.add_argument('--priority', type=str)
138 | parser.add_argument('--status', type=str)
139 | params, other_params = parser.parse_known_args()
140 | global lp
141 | global prj
142 | lp = Launchpad.login_with('lp-client', 'production', version='devel')
143 | prj = lp.projects[params.project]
144 | if params.cmd == 'set':
145 | for item_id in params.item_id:
146 | if params.item_type == 'bp':
147 | update_bp(item_id, params)
148 | if params.item_type == 'bug':
149 | update_bug(item_id, params)
150 |
151 |
152 | if __name__ == "__main__":
153 | main()
154 |
--------------------------------------------------------------------------------
/launchpad_report/checks.py:
--------------------------------------------------------------------------------
1 | import inspect
2 |
3 | # from launchpad_report.utils import all_bug_statuses
4 | # from launchpad_report.utils import open_bug_statuses
5 | # from launchpad_report.utils import rejected_bug_statuses
6 | # from launchpad_report.utils import untriaged_bp_def_statuses
7 | from launchpad_report.utils import closed_bp_statuses
8 | from launchpad_report.utils import closed_bug_statuses
9 | from launchpad_report.utils import get_name
10 | from launchpad_report.utils import is_bp
11 | from launchpad_report.utils import is_bug
12 | from launchpad_report.utils import is_series
13 | from launchpad_report.utils import rejected_bp_def_statuses
14 | from launchpad_report.utils import rejected_bug_statuses
15 | from launchpad_report.utils import untriaged_bug_statuses
16 | from launchpad_report.utils import valid_bp_priorities
17 | from launchpad_report.utils import valid_bug_priorities
18 |
19 |
20 | class Checks(object):
21 | def __init__(self, mapping):
22 | self.mapping = mapping
23 |
24 | def run(self, obj, series):
25 | tests = inspect.getmembers(
26 | Checks,
27 | lambda x: inspect.ismethod(x) and
28 | x.__name__.startswith('is_')
29 | )
30 | actions = []
31 | for test in tests:
32 | actions.append(getattr(Checks, test[0])(self, obj, series))
33 | return filter(lambda x: x is not None, actions)
34 |
35 | def is_bp_series_defined(self, obj, series):
36 | if is_bp(obj) and series is None:
37 | return "No series"
38 |
39 | def is_rejected_bp_has_milestone(self, obj, series):
40 | if (
41 | is_bp(obj) and
42 | obj.definition_status in rejected_bp_def_statuses and
43 | obj.milestone is not None
44 | ):
45 | return (
46 | "Rejected blueprint has milestone (%s) for series (%s)" %
47 | (obj.milestone.name, series)
48 | )
49 |
50 | def is_milestone_in_series(self, obj, series):
51 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
52 | return
53 | if obj.milestone is None:
54 | return "No milestone for series (%s)" % series
55 | if series is None:
56 | return # There is another check for missed series
57 | if (
58 | get_name(obj.milestone) in self.mapping['milestones'] and
59 | self.mapping['milestones'][get_name(obj.milestone)] != series
60 | ):
61 | return ("Wrong milestone (%s) for series (%s)" % (
62 | get_name(obj.milestone), series))
63 |
64 | def is_milestone_active(self, obj, series):
65 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
66 | return
67 | if obj.milestone is None:
68 | return
69 | if obj.milestone.is_active:
70 | return
71 | if (
72 | (is_bug(obj) and obj.status not in closed_bug_statuses) or
73 | (
74 | is_bp(obj) and
75 | obj.implementation_status not in closed_bp_statuses
76 | )
77 | ):
78 | return (
79 | "Open and targeted to closed milestone (%s) on series (%s)" %
80 | (get_name(obj.milestone), series)
81 | )
82 |
83 | def is_bug_targeted_to_focus_series(self, obj, series):
84 | if (
85 | is_bug(obj) and
86 | is_series(obj.target) and
87 | get_name(obj.target.project.development_focus) == series
88 | ):
89 | return (
90 | "Targeted to the current development focus (%s)" %
91 | series)
92 |
93 | def is_priority_set(self, obj, series):
94 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
95 | return
96 | if (is_bug(obj) and obj.status in rejected_bug_statuses):
97 | return
98 | if is_bp(obj) and obj.priority not in valid_bp_priorities:
99 | return "Priority (%s) is not valid for series (%s)" % (
100 | obj.priority, series)
101 | if is_bug(obj) and obj.importance not in valid_bug_priorities:
102 | return "Priority (%s) is not valid for series (%s)" % (
103 | obj.importance, series)
104 |
105 | def is_assignee_set(self, obj, series):
106 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
107 | return
108 | if (is_bug(obj) and obj.status in rejected_bug_statuses):
109 | return
110 | if not obj.assignee:
111 | return "No assignee for series (%s)" % series
112 |
113 | def is_bug_confirmed(self, obj, series):
114 | if is_bug(obj) and obj.status in untriaged_bug_statuses:
115 | return "Not confirmed for series (%s)" % series
116 |
117 | def is_bp_in_unknown_status(self, obj, series):
118 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
119 | return
120 | if (
121 | is_bp(obj) and
122 | obj.implementation_status == 'Unknown'
123 | ):
124 | return "Status unknown for series (%s)" % series
125 |
126 | def is_bp_done_but_unapproved(self, obj, series):
127 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
128 | return
129 | if (
130 | is_bp(obj) and
131 | obj.implementation_status in closed_bp_statuses
132 | ):
133 | if (
134 | obj.definition_status != 'Approved' or
135 | obj.direction_approved is not True
136 | ):
137 | return "Implemented, but not approved for series (%s)" % series
138 |
139 | def is_bp_semiapproved(self, obj, series):
140 | if (is_bp(obj) and obj.definition_status in rejected_bp_def_statuses):
141 | return
142 | if (
143 | is_bp(obj) and
144 | obj.definition_status == 'Approved' and
145 | obj.direction_approved is not True
146 | ):
147 | return (
148 | "Definition is approved, but direction is not for series (%s)"
149 | % series
150 | )
151 |
--------------------------------------------------------------------------------
/launchpad_report/report.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import json
4 |
5 | from launchpadlib.launchpad import Launchpad
6 | import yaml
7 |
8 | from launchpad_report.checks import Checks
9 | from launchpad_report.render import CSVRenderer
10 | from launchpad_report.render import HTMLRenderer
11 | from launchpad_report.render import JSONRenderer
12 | from launchpad_report.utils import all_bug_statuses
13 | from launchpad_report.utils import get_name
14 | from launchpad_report.utils import is_series
15 | from launchpad_report.utils import open_bug_statuses
16 | from launchpad_report.utils import open_bug_statuses_for_HCF
17 | from launchpad_report.utils import printn
18 | from launchpad_report.utils import short_status
19 | from launchpad_report.utils import untriaged_bug_statuses
20 |
21 |
22 | class ConfigError(Exception):
23 | pass
24 |
25 |
26 | class Report(object):
27 | def __init__(self, config_filename):
28 | with open(config_filename, "r") as f:
29 | self.config = yaml.load(f.read())
30 |
31 | self.teams = self.config['teams']
32 | self.trunc = self.config['trunc_report']
33 |
34 | cache_dir = self.config['cache_dir']
35 |
36 | if self.config['use_auth']:
37 | lp = Launchpad.login_with(
38 | 'lp-report-bot', 'production',
39 | cache_dir, version='devel'
40 | )
41 | else:
42 | lp = Launchpad.login_anonymously(
43 | 'lp-report-bot', 'production', version='devel'
44 | )
45 | #import pdb; pdb.set_trace()
46 | self.projects = [lp.projects[prj] for prj in self.config['project']]
47 |
48 | # for backward compatibility
49 | #self.project = lp.projects[self.config['project'][0]]
50 |
51 | self.blueprint_series = {}
52 |
53 | def render2html(self, filename, template_filename):
54 | HTMLRenderer(filename, template_filename).render(self.data)
55 |
56 | def render2csv(self, filename):
57 | CSVRenderer(filename).render(self.data)
58 |
59 | def render2json(self, filename):
60 | JSONRenderer(filename).render(self.data)
61 |
62 | def load(self, filename):
63 | jsonfile = open(filename)
64 | self.data = json.load(jsonfile)
65 |
66 | def generate(self, all=False):
67 | self.data = {'rows': []}
68 | self.bug_issues = {}
69 | self.data['config'] = self.config
70 | for project in self.projects:
71 | self.checks = Checks(self.iter_series(project))
72 | #self.data['rows'] += self.bp_report(all=all)
73 | self.data['rows'] += self.bug_report(project, all=all)
74 |
75 | def iter_series(self, project):
76 | print("Collecting series data for %s:" % project)
77 | self.bps_series = {}
78 | milestones_series = {}
79 | for series in project.series:
80 | printn(" %s" % get_name(series))
81 | # Blueprints
82 | #for (counter, bp) in enumerate(series.all_specifications):
83 | #self.bps_series[get_name(bp)] = get_name(series)
84 | # Milestones
85 | for milestone in series.all_milestones:
86 | milestones_series[get_name(milestone)] = get_name(series)
87 | printn(" none")
88 | # Search for blueprints without series
89 | #for (counter, bp) in enumerate(self.project.all_specifications):
90 | #self.bps_series.setdefault(get_name(bp), None)
91 | print()
92 | return {
93 | 'milestones': milestones_series,
94 | }
95 |
96 | def bp_report(self, all=False):
97 | report = []
98 | if all:
99 | blueprints = self.project.all_specifications
100 | else:
101 | blueprints = self.project.valid_specifications
102 | printn("Processing blueprints (%d):" % len(blueprints))
103 | for (counter, bp) in enumerate(blueprints, 1):
104 | if counter > self.trunc and self.trunc > 0:
105 | break
106 | if counter % 200 == 10:
107 | print()
108 | if counter % 10 == 0:
109 | printn("%4d" % counter)
110 | assignee = 'unassigned'
111 | assignee_name = 'unassigned'
112 | try:
113 | assignee = get_name(bp.assignee)
114 | assignee_name = bp.assignee.display_name
115 | except Exception:
116 | pass
117 | if bp.milestone:
118 | milestone = get_name(bp.milestone)
119 | else:
120 | milestone = 'None'
121 | team = 'unknown'
122 | for t in self.teams.keys():
123 | if assignee in self.teams[t]:
124 | team = t
125 | triage = self.checks.run(bp, self.bps_series[get_name(bp)])
126 | report.append({
127 | 'type': 'bp',
128 | 'link': bp.web_link.encode('utf-8'),
129 | 'id': bp.web_link[
130 | bp.web_link.rfind('/') + 1:
131 | ].encode('utf-8'),
132 | 'title': bp.title.encode('utf-8'),
133 | 'milestone': milestone,
134 | 'series': self.bps_series[get_name(bp)],
135 | 'status': bp.implementation_status,
136 | 'short_status': short_status(bp),
137 | 'priority': bp.priority,
138 | 'team': team.encode('utf-8'),
139 | 'assignee': assignee.encode('utf-8'),
140 | 'name': assignee_name.encode('utf-8'),
141 | 'triage': ', '.join(triage).encode('utf-8')
142 | })
143 | print()
144 | return report
145 |
146 | def bug_report(self, project, all=False):
147 | report = []
148 | milestone51 = project.getMilestone(name="6.0") # 5.1
149 | milestone502 = project.getMilestone(name="5.0.3") # 5.0.2
150 | if all:
151 | bugs = project.searchTasks(status=all_bug_statuses)
152 | else:
153 | if self.config.get('hcf'):
154 | bugs51 = project.searchTasks(status=(
155 | open_bug_statuses_for_HCF), milestone=milestone51,
156 | importance=["Critical", "High"],
157 | # We would ideally filter our system-tests tag,
158 | # however I saw bugs which were just found during
159 | # sytem-tests run which are being real bugs in Fuel
160 | tags=["-docs", "-devops", "-fuel-devops", "-experimental"],
161 | tags_combinator="All")
162 | bugs502 = project.searchTasks(status=(
163 | open_bug_statuses_for_HCF), milestone=milestone502,
164 | importance=["Critical", "High"],
165 | tags=["-docs", "-devops", "-fuel-devops", "-experimental"],
166 | tags_combinator="All")
167 | else:
168 | bugs51 = project.searchTasks(status=(
169 | untriaged_bug_statuses + open_bug_statuses), milestone=milestone51)
170 | bugs502 = project.searchTasks(status=(
171 | untriaged_bug_statuses + open_bug_statuses), milestone=milestone502)
172 | #printn("Processing bugs (%d):" % (len(bugs51) + len(bugs502)))
173 |
174 | for bugs in (bugs51, bugs502):
175 | for (counter, bug) in enumerate(bugs, 1):
176 | if counter > self.trunc and self.trunc > 0:
177 | break
178 | if counter % 200 == 10:
179 | print()
180 | if counter % 10 == 0:
181 | printn("%4d" % counter)
182 |
183 | assignee = 'unassigned'
184 | assignee_name = 'unassigned'
185 | try:
186 | assignee = get_name(bug.assignee)
187 | # We want to exclude all from QA &
188 | # fuel-devops & docs for HCF calcs
189 | if self.config.get('excludes') and assignee in self.config['excludes']:
190 | continue
191 | assignee_name = bug.assignee.display_name
192 | except Exception:
193 | pass
194 | if bug.milestone:
195 | milestone = get_name(bug.milestone)
196 | else:
197 | milestone = 'None'
198 | team = 'unknown'
199 | self.bug_issues.setdefault(bug.bug.web_link, [])
200 | for t in self.teams.keys():
201 | if assignee in self.teams[t]:
202 | team = t
203 | title = bug.bug.title
204 | triage = []
205 | for task in bug.bug.bug_tasks:
206 | series = task.target
207 | if is_series(series):
208 | series = get_name(series)
209 | if task.target.project != project:
210 | continue
211 | else:
212 | series = None
213 | if task.target != project:
214 | continue
215 | triage += self.checks.run(task, series)
216 | report.append({
217 | 'type': 'bug',
218 | 'link': bug.web_link.encode('utf-8'),
219 | 'id': bug.web_link[
220 | bug.web_link.rfind('/') + 1:
221 | ].encode('utf-8'),
222 | 'title': title.encode('utf-8'),
223 | 'milestone': milestone,
224 | 'status': bug.status,
225 | 'short_status': short_status(bug),
226 | 'priority': bug.importance,
227 | 'team': team.encode('utf-8'),
228 | 'assignee': assignee.encode('utf-8'),
229 | 'name': assignee_name.encode('utf-8'),
230 | 'triage': ', '.join(triage).encode('utf-8'),
231 | })
232 | print()
233 | return report
234 |
--------------------------------------------------------------------------------