Merge ~racb/ubuntu-sponsoring:git-mps into ubuntu-sponsoring:master

Proposed by Robie Basak
Status: Merged
Merged at revision: 29422de65f4be83678847e7fa4e42f354aa2d460
Proposed branch: ~racb/ubuntu-sponsoring:git-mps
Merge into: ubuntu-sponsoring:master
Diff against target: 270 lines (+216/-5)
2 files modified
lp_scrape_mps.py (+68/-0)
sponsors-page.py (+148/-5)
Reviewer Review Type Date Requested Status
Simon Quigley Approve
Review via email: mp+426087@code.launchpad.net

Description of the change

Add support for git-ubuntu MPs to appear in the sponsorship reports.

To post a comment you must log in.
Revision history for this message
Simon Quigley (tsimonq2) wrote :

Hi Robie!

First off, thank you for submitting this MR. The Ubuntu Sponsoring Report tends to not get a lot of love these days.

I think this PR looks good, but the fact that it's manually scraping a webpage concerns me. I'd appreciate it if we could get that addressed sooner rather than later.

Lastly, ...why 12.04? Why is that version still in production? That leaves me with many questions... :P

Merging. You may have to poke someone to git pull it on the prod server, I forget exactly how that's handled.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lp_scrape_mps.py b/lp_scrape_mps.py
2new file mode 100644
3index 0000000..81ff0b3
4--- /dev/null
5+++ b/lp_scrape_mps.py
6@@ -0,0 +1,68 @@
7+"""Determine the set of MPs relevant to a particular Launchpad user or team
8+
9+This works around https://bugs.launchpad.net/launchpad/+bug/1979817 by scraping
10+the Launchpad Web UI instead.
11+
12+Since cranberry (the machine that generates
13+http://reqorts.qa.ubuntu.com/reports/sponsoring/index.html) is running 12.04
14+and uses Python 2 to generate the report, this file must be kept Python 2.7
15+compatible.
16+"""
17+
18+# COPIED FROM lp:~ubuntu-servers/+git/ubuntu-helpers/rbasak/lp_scrape_mps.py
19+# Commit: 4237a5a
20+
21+import itertools
22+import re
23+
24+try:
25+ import urllib.request as urllib_request
26+except ImportError:
27+ # Python 2 compatibility shim
28+ import urllib2 as urllib_request
29+
30+import bs4
31+
32+CANONICAL_SERVER_MP_URL = (
33+ "https://code.launchpad.net/~canonical-server-reporter/+activereviews"
34+)
35+GIT_UBUNTU_MP_URL = "https://code.launchpad.net/~usd-import-team/usd-importer/+git/usd-importer/+ref/master/+activereviews"
36+MATCH_RE = re.compile(r"^/.*/\+merge/[\d]+$")
37+MATCH_CAN_DO = re.compile(r"^(Requested reviews)|(Reviews) .* can do$")
38+
39+
40+def _scrape_flat_mps(lp, soup):
41+ current_title = None
42+ for tag in soup(
43+ lambda t: (t.name == "td" and "section-heading" in t.get("class", []))
44+ or (t.name == "a" and MATCH_RE.search(t.get("href", "")))
45+ ):
46+ if tag.name == "td":
47+ current_title = tag.string
48+ else:
49+ yield current_title, lp.load(
50+ "https://api.launchpad.net/devel" + tag["href"]
51+ )
52+
53+
54+def scrape_mps(lp, web_url):
55+ # On Python 2.7, urllib2.urlopen() doesn't return a context manager, so
56+ # this must be handled manually.
57+ f = urllib_request.urlopen(web_url)
58+ try:
59+ soup = bs4.BeautifulSoup(f, features="lxml")
60+ finally:
61+ f.close()
62+ return {
63+ key: [pair[1] for pair in mp_pairs]
64+ for key, mp_pairs in itertools.groupby(
65+ _scrape_flat_mps(lp, soup), key=lambda r: r[0]
66+ )
67+ }
68+
69+
70+def filter_can_do(mps):
71+ for k, v in mps.items():
72+ if MATCH_CAN_DO.search(k):
73+ return v
74+ return []
75diff --git a/sponsors-page.py b/sponsors-page.py
76index 00c7114..33724b1 100755
77--- a/sponsors-page.py
78+++ b/sponsors-page.py
79@@ -13,6 +13,8 @@ import sys
80 from lazr.restfulclient.errors import NotFound, ServerError
81 import launchpad
82
83+import lp_scrape_mps
84+
85 # { a : b }
86 # Look for merge proposals / bug subcriptions for a and output them to b's
87 # page. The default (None) is to use a.
88@@ -43,6 +45,14 @@ BLACKLISTED_REQUESTER = (
89 # A package that is outside of main and package sets
90 UNIVERSE_PACKAGE = "mathjax-docs"
91
92+GIT_UBUNTU_REPOSITORY_OWNER = (
93+ "https://api.launchpad.net/devel/~git-ubuntu-import"
94+)
95+LP_DISTRIBUTION_SOURCE_PACKAGE_RESOURCE_TYPE = (
96+ "https://api.launchpad.net/devel/#distribution_source_package"
97+)
98+LP_UBUNTU_DISTRIBUTION = "https://api.launchpad.net/devel/ubuntu"
99+
100
101 def htmlify_name(person, package, devs, distribution):
102 if not person:
103@@ -382,7 +392,7 @@ def get_bugs(lp, distribution, team, verbose=False):
104 active_releases = [a for a in distribution.series if a.active] + \
105 [distribution]
106 tasks = set()
107- lp_team = lp.people[SPONSOR_TEAMS[team]]
108+ lp_team = lp.people[team]
109 for release in active_releases:
110 release_name = release.name
111 release_tasks = release.searchTasks(bug_subscriber=lp_team,
112@@ -513,6 +523,133 @@ def get_branches(lp, distribution, team, verbose=False):
113 return tasks
114
115
116+def get_git_ubuntu_target_series(ubuntu, target_git_path):
117+ if target_git_path.startswith("refs/heads/ubuntu/"):
118+ effective_target_git_path = target_git_path
119+ elif target_git_path == "refs/heads/debian/sid":
120+ # We use "debian/sid" for package merge MPs. LP: 1976112
121+ effective_target_git_path = "refs/heads/ubuntu/devel"
122+ else:
123+ raise ValueError
124+ assert effective_target_git_path.startswith("refs/heads/ubuntu/")
125+ branch = effective_target_git_path[len("refs/heads/ubuntu/") :]
126+ current_series = ubuntu.current_series
127+ if branch == "devel":
128+ return current_series.name
129+ else:
130+ try:
131+ series, _ = branch.split("-")
132+ except ValueError:
133+ # target is the "release pocket branch" of a stable series; assume
134+ # this is an SRU really intended for -updates via -proposed.
135+ return branch
136+ return series
137+
138+
139+def get_git_branches(lp, distribution, team, verbose=False):
140+ ubuntu = lp.distributions['ubuntu']
141+ tasks = set()
142+ web_url = (
143+ "https://code.launchpad.net/~%s/+activereviews"
144+ % team
145+ )
146+ proposals = [
147+ p for p
148+ in lp_scrape_mps.filter_can_do(lp_scrape_mps.scrape_mps(
149+ lp=lp,
150+ web_url=web_url,
151+ ))
152+ if p.registrant.name not in BLACKLISTED_REQUESTER
153+ ]
154+ for proposal in proposals:
155+ if not proposal.source_git_repository:
156+ # Not git (eg. bzr)
157+ continue
158+ target_repo = proposal.target_git_repository
159+ target = target_repo.target
160+ if (
161+ target.resource_type_link
162+ != LP_DISTRIBUTION_SOURCE_PACKAGE_RESOURCE_TYPE
163+ ):
164+ # Not targetted at a distribution package
165+ continue
166+ if target.distribution_link != LP_UBUNTU_DISTRIBUTION:
167+ # Not targetted at Ubuntu
168+ continue
169+
170+ distribution = target.distribution
171+
172+ # We need to identify the package and target series. For git-ubuntu
173+ # branches, this is straightforward as the branch name schema is known,
174+ # so for now we just support these. To support non git-ubuntu git merge
175+ # proposals, we would need some consistent way of determining the
176+ # target series from the target branch.
177+ if target_repo.owner_link != GIT_UBUNTU_REPOSITORY_OWNER:
178+ continue
179+
180+ try:
181+ series = get_git_ubuntu_target_series(
182+ ubuntu=distribution,
183+ target_git_path=proposal.target_git_path,
184+ )
185+ except ValueError:
186+ continue
187+ # releases, as supplied by kwargs to SponsoringItem(), seems to mean
188+ # the set of stable releases for an SRU, or empty if the development
189+ # release.
190+ if series != distribution.current_series.name:
191+ releases = set([series])
192+ else:
193+ releases = set()
194+
195+ s_types = set()
196+ status = ""
197+ last_comment = ""
198+ package = target.name
199+ tags = set()
200+ try:
201+ comments = [a for a in proposal.all_comments]
202+ except NotFound:
203+ comments = []
204+ if comments:
205+ status = ["%s comments" % (len(comments))]
206+ last_comment = comments[-1].author
207+ else:
208+ last_comment = proposal.registrant
209+ try:
210+ votes = [a for a in proposal.votes if a.comment is not None]
211+ except NotFound:
212+ votes = []
213+ if votes:
214+ status += [a.comment.vote for a in votes]
215+ if proposal.source_git_path.startswith('refs/heads/'):
216+ link_desc = proposal.source_git_path[len('refs/heads/'):]
217+ else:
218+ link_desc = 'MP source branch name unavailable'
219+ title = "%s:%s" % (
220+ proposal.source_git_repository.display_name,
221+ link_desc,
222+ )
223+ tasks.add(SponsoringItem(
224+ distribution=distribution,
225+ team=team,
226+ link=proposal.web_link,
227+ link_desc=link_desc,
228+ release=releases,
229+ s_types=s_types,
230+ package=package,
231+ last_comment=last_comment,
232+ status=status,
233+ importance="",
234+ title=title,
235+ tags=tags,
236+ date_created=proposal.date_created,
237+ date_queued=proposal.date_created,
238+ verbose=verbose,
239+ ))
240+ return tasks
241+
242+
243 def get_packageset_dict(sponsoring_items, packages):
244 packageset_dict = dict()
245 for item in sponsoring_items:
246@@ -616,15 +753,21 @@ def main():
247 if (perm.person.is_team and
248 perm.permission == "Archive Upload Rights")]) \
249 | set(EXTRA_TEAMS.keys())
250- for team in SPONSOR_TEAMS:
251+ for team, team_lpid in SPONSOR_TEAMS.iteritems():
252 sponsoring_items[team] = get_bugs(
253- lp, distribution, team, options.verbose)
254- branches = get_branches(lp, distribution, team, options.verbose)
255+ lp, distribution, team_lpid, options.verbose)
256+ branches = (
257+ get_branches(lp, distribution, team_lpid, options.verbose)
258+ | get_git_branches(lp, distribution, team_lpid, options.verbose)
259+ )
260 sponsoring_items[team] = \
261 sponsoring_items.get(team, set()) | branches
262 for team in all_uploaders:
263 output_team = EXTRA_TEAMS.get(team, SPONSOR_TEAMS["general"]) or team
264- branches = get_branches(lp, distribution, team, options.verbose)
265+ branches = (
266+ get_branches(lp, distribution, team, options.verbose)
267+ | get_git_branches(lp, distribution, team, options.verbose)
268+ )
269 sponsoring_items[output_team] = \
270 sponsoring_items.get(output_team, set()) | branches
271

Subscribers

People subscribed via source and target branches