Merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor into lp:lp-community-utils
- nag_refactor
- Merge into openerp-reviewers-nag
Status: | Needs review |
---|---|
Proposed branch: | lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor |
Merge into: | lp:lp-community-utils |
Diff against target: |
508 lines (+254/-223) 2 files modified
lp.py (+252/-0) openerp-nag (+2/-223) |
To merge this branch: | bzr merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stefan Rijnhart (Opener) | Needs Fixing | ||
Guewen Baconnier @ Camptocamp | code review | Pending | |
Review via email: mp+214445@code.launchpad.net |
This proposal supersedes a proposal from 2014-02-06.
Commit message
Description of the change
Moved functions used in openerp-nag to lp.py so they may be used by other scripts.
As of now, openerp-nag cannot be imported by other scripts due to the dash in its name and the fact it does not end in .py
No code was changed with the exception of the addition of the launchpad_login function which replicates old behaviour.
I have not done any of the cleanup of my old proposal as they can be done later.
Please review this with high priority as any future changed to openerp-nag means I have to rebase this commit.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal | # |
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal | # |
@gbaconnier-c2c
Good point about adding functions to Nag, the reason for turning it into a class is design and optimization.
It gives the option, like to said, to add functions but also, the ability to inherit from it.
I do have big plans with the lib, for example, automatic diff-validating in the style of (https:/
I have proposed something already: https:/
As for the --authenticated option, I think I took it away because it wasn't referenced or used, I can put it back now, however I will have no impact. This was a while ago, though, I could have just taken it out accidentally.
The problem with age was that it declared it negatively, then did * -1 again while sorting. I just took out the (-1 * -1 = 1) redundancy. It also has a one off error where an MP from today is displayed as 1 day old.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal | # |
> On 03/07/2014 07:45 PM, Sandy Carter (http://
> wrote:
> @gbaconnier-c2c
> Good point about adding functions to Nag, the reason for turning it
> into a class is design and optimization.
The rationale about design is very subjective and the one about
optimization is dubious:
http://
Anyway, if it needs methods, and I think the display should be one, then
it makes sense.
> It gives the option, like to said, to add functions but also, the
> ability to inherit from it.
>
> I do have big plans with the lib, for example, automatic
> diff-validating in the style of
> (https:/
> specifically checking style, seeing if the diff introduces pep8
> errors, removes them, testing the patches with jenkins, commenting on
> the MPs, etc.
> I have proposed something already:
> https:/
>
>
> As for the --authenticated option, I think I took it away because it
> wasn't referenced or used, I can put it back now, however I will have
> no impact. This was a while ago, though, I could have just taken it
> out accidentally.
That's right at the time you made the proposal, the option seemed to be
not useful.
>
> The problem with age was that it declared it negatively, then did * -1
> again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> also has a one off error where an MP from today is displayed as 1 day
> old.
Ok.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal | # |
> > On 03/07/2014 07:45 PM, Sandy Carter (http://
> > wrote:
> > @gbaconnier-c2c
> > Good point about adding functions to Nag, the reason for turning it
> > into a class is design and optimization.
> The rationale about design is very subjective and the one about
> optimization is dubious:
>
> http://
Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
Try this: http://
That's close to 15% increase in speed for summing members.
>
> Anyway, if it needs methods, and I think the display should be one, then
> it makes sense.
>
I agree
> > It gives the option, like to said, to add functions but also, the
> > ability to inherit from it.
> >
> > I do have big plans with the lib, for example, automatic
> > diff-validating in the style of
> > (https:/
> > specifically checking style, seeing if the diff introduces pep8
> > errors, removes them, testing the patches with jenkins, commenting on
> > the MPs, etc.
> > I have proposed something already:
> > https:/
> utils/branch_
> >
> >
> > As for the --authenticated option, I think I took it away because it
> > wasn't referenced or used, I can put it back now, however I will have
> > no impact. This was a while ago, though, I could have just taken it
> > out accidentally.
>
> That's right at the time you made the proposal, the option seemed to be
> not useful.
>
> >
> > The problem with age was that it declared it negatively, then did * -1
> > again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> > also has a one off error where an MP from today is displayed as 1 day
> > old.
>
> Ok.
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal | # |
On 03/10/2014 02:12 PM, Sandy Carter (http://
wrote:
>> > On 03/07/2014 07:45 PM, Sandy Carter (http://
>>> wrote:
>>> @gbaconnier-c2c
>>> Good point about adding functions to Nag, the reason for turning it
>>> into a class is design and optimization.
>> The rationale about design is very subjective and the one about
>> optimization is dubious:
>>
>> http://
>
> Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
> Try this: http://
> That's close to 15% increase in speed for summing members.
>
Enlightening, thanks for the explanation.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal | # |
@Guewen
> Couldn't already the module be used as a lib? (as there is if __name__ ==
> "__main__":) If the filename is an issue (it is, i we want to import it),
> maybe we can consider to rename it?
The problem right now is that you can't import from it due to its filename (dash in filename and no .py)
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
I would prefer to keep it in one file and rename to openerp_nag.py then. Would that work for you?
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
@Stefan, yes that would work, we could even be backwards compatible and have a symlink
openerp_nag -> openerp_nag.py
Stefan Rijnhart (Opener) (stefan-opener) wrote : | # |
Great, setting to Needs Fixing pending such a refactoring.
Unmerged revisions
- 29. By Sandy Carter (http://www.savoirfairelinux.com)
-
Moved common code out of openerp-nag into lp.py so it can be used by other scripts
Preview Diff
1 | === added file 'lp.py' |
2 | --- lp.py 1970-01-01 00:00:00 +0000 |
3 | +++ lp.py 2014-04-06 16:40:59 +0000 |
4 | @@ -0,0 +1,252 @@ |
5 | +# Copyright 2012 Canonical Ltd. |
6 | +# Written by: |
7 | +# Zygmunt Krynicki <zygmunt.krynicki@canonical.com> |
8 | +# Hacked for the OpenERP Community Reviewers version by: |
9 | +# Guewen Baconnier <guewen.baconnier@camptocamp.com> |
10 | +# |
11 | +# This program is free software: you can redistribute it and/or modify |
12 | +# it under the terms of the GNU General Public License version 3, |
13 | +# as published by the Free Software Foundation. |
14 | +# |
15 | +# This program is distributed in the hope that it will be useful, |
16 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | +# GNU General Public License for more details. |
19 | +# |
20 | +# You should have received a copy of the GNU General Public License |
21 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 | + |
23 | + |
24 | +from __future__ import unicode_literals, print_function |
25 | + |
26 | +import collections |
27 | +import datetime |
28 | +import logging |
29 | + |
30 | +try: |
31 | + from progressbar import ProgressBar, Bar, Percentage, ETA |
32 | +except ImportError: |
33 | + ProgressBar = None |
34 | + |
35 | +from launchpadlib.launchpad import Launchpad |
36 | + |
37 | + |
38 | +# Nag: |
39 | +# Indication that we want to nag the 'person'... |
40 | +# ...with the specified 'action' |
41 | +# ...about a particular 'subject' |
42 | +Nag = collections.namedtuple( |
43 | + "Nag", "person action subject sort_class sort_priority sort_age project_name votes") |
44 | + |
45 | +SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2) |
46 | + |
47 | + |
48 | +def show_lp_object(obj): |
49 | + print(obj) |
50 | + print("attributes:", obj.lp_attributes) |
51 | + for attr in obj.lp_attributes: |
52 | + print("attribute[%s]: %s" % (attr, getattr(obj, attr))) |
53 | + print("entries:", obj.lp_entries) |
54 | + for entry in obj.lp_entries: |
55 | + print("entry[%s]: %s" % (entry, getattr(obj, entry))) |
56 | + print("collections:", obj.lp_collections) |
57 | + print("operations:", obj.lp_operations) |
58 | + print("-" * 80) |
59 | + |
60 | + |
61 | +class DefaultPolicy: |
62 | + |
63 | + mps_need_commit_message = False |
64 | + |
65 | + max_review_age = 0 |
66 | + |
67 | + max_bug_new_age = 7 |
68 | + |
69 | + # minimal number of reviewers before merging |
70 | + min_approve = 2 |
71 | + |
72 | + # can't be merged before n days |
73 | + days_before_merge = 5 |
74 | + |
75 | + # can be merged before days_before_merge if at least n approvals |
76 | + approvals_to_bypass = 3 |
77 | + |
78 | + |
79 | +class UTC(datetime.tzinfo): |
80 | + |
81 | + def dst(self, foo): |
82 | + return datetime.timedelta(0, 0) |
83 | + |
84 | + def utcoffset(self, foo): |
85 | + return datetime.timedelta(0, 0) |
86 | + |
87 | + |
88 | +UTC = UTC() |
89 | + |
90 | + |
91 | +class Votes(object): |
92 | + """ Holds votes of a proposal """ |
93 | + |
94 | + __states__ = { |
95 | + 'Approve': 'approve', |
96 | + 'Needs Fixing': 'needs_fixing', |
97 | + 'Needs Information': 'needs_information', |
98 | + 'Abstain': 'abstain', |
99 | + 'Disapprove': 'disapprove', |
100 | + 'Resubmit': 'resubmit', |
101 | + 'Pending': 'pending', |
102 | + } |
103 | + |
104 | + __signs__ = { |
105 | + 'approve': '+', |
106 | + 'needs_fixing': '!', |
107 | + 'needs_information': '?', |
108 | + 'abstain': '~', |
109 | + 'disapprove': '-', |
110 | + 'resubmit': 'R', |
111 | + 'pending': '*', |
112 | + } |
113 | + |
114 | + __ignore_for_approval__ = ['abstain', 'pending'] |
115 | + |
116 | + def __init__(self, votes): |
117 | + self._votes = collections.Counter( |
118 | + self.__states__[vote.comment.vote if vote.comment else 'Pending'] |
119 | + for vote in votes |
120 | + ) # no comment is a pending review, usually the team |
121 | + |
122 | + def __getattr__(self, name): |
123 | + if name in self._votes: |
124 | + return self._votes[name] |
125 | + elif name in self.__states__.values(): |
126 | + return 0 |
127 | + else: |
128 | + raise AttributeError |
129 | + |
130 | + def __str__(self): |
131 | + signs = ['%d%s' % (count, self.__signs__[state]) for |
132 | + state, count in self._votes.iteritems()] |
133 | + return ', '.join(signs) |
134 | + |
135 | + def total(self, for_approval=False): |
136 | + if for_approval: |
137 | + return sum(count for state, count in self._votes.iteritems() |
138 | + if state not in self.__ignore_for_approval__) |
139 | + return sum(self._votes.values()) |
140 | + |
141 | + @classmethod |
142 | + def legend(cls): |
143 | + return dict((state, cls.__signs__[code]) for |
144 | + state, code in cls.__states__.iteritems()) |
145 | + |
146 | + |
147 | +def gen_project_nags(lp, policy, project_name): |
148 | + # TODO: Detect project groups and redirect the nag to all projects |
149 | + # underneath that project |
150 | + # Access the project that we care about |
151 | + logging.debug("Accessing project %r", project_name) |
152 | + project = lp.projects[project_name] |
153 | + # Re-yield all the merge proposal nags |
154 | + # NOTE: change to yield from in py3k3 |
155 | + for nag in gen_merge_proposal_nags(lp, policy, project): |
156 | + yield nag |
157 | + |
158 | + |
159 | +def gen_merge_proposal_nags(lp, policy, project): |
160 | + # XXX: cannnot use utcnow as launchpad returns tz-aware objects here |
161 | + now = datetime.datetime.now(tz=UTC) |
162 | + logging.debug("Looking for merge proposals in project %r", project) |
163 | + # NOTE: Workaround for project.getMergeProposals() crashing on timeouts |
164 | + try: |
165 | + merge_proposals = project.getMergeProposals(status='Needs review') |
166 | + except AttributeError: |
167 | + logging.warning("The project %s has no code to look at", project) |
168 | + return |
169 | + for proposal in merge_proposals: |
170 | + logging.debug("Looking at merge proposal %r", proposal) |
171 | + # Skip everything that is still not requested for review |
172 | + if proposal.date_review_requested is None: |
173 | + continue |
174 | + # Skip everything that is already merged |
175 | + if proposal.date_merged is not None: |
176 | + continue |
177 | + votes = Votes(proposal.votes) |
178 | + |
179 | + # Nag about missing commit message on merge requests |
180 | + if policy.mps_need_commit_message and proposal.commit_message is None: |
181 | + yield Nag( |
182 | + person=proposal.registrant.display_name, |
183 | + action="set a commit message on merge request", |
184 | + subject=proposal.web_link, |
185 | + sort_class=SORT_CLASS_MERGE, |
186 | + sort_priority=None, # TODO: get from max(linked bugs) |
187 | + sort_age=(proposal.date_review_requested - now).days, |
188 | + project_name=project.name, |
189 | + votes=votes, |
190 | + ) |
191 | + |
192 | + age = (now - proposal.date_review_requested).days |
193 | + |
194 | + if (votes.approve == votes.total(for_approval=True) and |
195 | + (votes.approve >= policy.approvals_to_bypass or |
196 | + votes.approve >= policy.min_approve and |
197 | + age >= policy.days_before_merge)): |
198 | + yield Nag( |
199 | + person='A committer', |
200 | + action="consider to merge the proposal", |
201 | + subject=proposal.web_link, |
202 | + sort_class=SORT_CLASS_MERGE, |
203 | + sort_priority=None, # TODO: get from max(linked bugs) |
204 | + sort_age=(proposal.date_review_requested - now).days, |
205 | + project_name=project.name, |
206 | + votes=votes, |
207 | + ) |
208 | + continue |
209 | + |
210 | + # Nag about aging merge requests |
211 | + if age >= policy.max_review_age: |
212 | + yield Nag( |
213 | + person='Someone', |
214 | + action="review the merge request", |
215 | + subject=proposal.web_link, |
216 | + sort_class=SORT_CLASS_MERGE, |
217 | + sort_priority=None, # TODO: get from max(linked bugs) |
218 | + sort_age=(proposal.date_review_requested - now).days, |
219 | + project_name=project.name, |
220 | + votes=votes, |
221 | + ) |
222 | + |
223 | + |
224 | +def gen_bug_nags(lp, policy, project): |
225 | + now = datetime.datetime.now(tz=UTC) |
226 | + new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age) |
227 | + # Nag about bugs that are "New" for too long |
228 | + logging.debug("Looking at 'new' bugs that are 8 days or older") |
229 | + aging_new_bugs = project.searchTasks(status='New', |
230 | + created_before=new_threshold) |
231 | + for bug_task in aging_new_bugs: |
232 | + yield Nag( |
233 | + person='everyone', |
234 | + action='triage the bug', |
235 | + subject=bug_task.web_link, |
236 | + sort_class=SORT_CLASS_BUG, |
237 | + sort_priority=None, # TODO: convert from importance name |
238 | + sort_age=None, |
239 | + votes=None) |
240 | + |
241 | + |
242 | +def parse_projects_file(filename): |
243 | + """ Parse a file containing name of the projects |
244 | + A line per project |
245 | + """ |
246 | + with open(filename, mode='rU') as project_file: |
247 | + content = project_file.read() |
248 | + |
249 | + return [name.strip() for name in content.splitlines()] |
250 | + |
251 | + |
252 | +def launchpad_login(anonymous, consumer_name, service_root): |
253 | + if anonymous: |
254 | + return Launchpad.login_anonymously(consumer_name, service_root) |
255 | + else: |
256 | + return Launchpad.login_with(consumer_name, service_root) |
257 | |
258 | === modified file 'openerp-nag' |
259 | --- openerp-nag 2014-02-20 16:18:06 +0000 |
260 | +++ openerp-nag 2014-04-06 16:40:59 +0000 |
261 | @@ -42,234 +42,16 @@ |
262 | from __future__ import unicode_literals, print_function |
263 | |
264 | import argparse |
265 | -import collections |
266 | -import datetime |
267 | -import logging |
268 | |
269 | try: |
270 | from progressbar import ProgressBar, Bar, Percentage, ETA |
271 | except ImportError: |
272 | ProgressBar = None |
273 | -from launchpadlib.launchpad import Launchpad |
274 | - |
275 | +from lp import * |
276 | |
277 | consumer_name = 'OpenERP Community Reviewers Nagging Scripts' |
278 | |
279 | |
280 | -# Nag: |
281 | -# Indication that we want to nag the 'person'... |
282 | -# ...with the specified 'action' |
283 | -# ...about a particular 'subject' |
284 | -Nag = collections.namedtuple( |
285 | - "Nag", "person action subject sort_class sort_priority sort_age project_name votes") |
286 | - |
287 | -SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2) |
288 | - |
289 | - |
290 | -def show_lp_object(obj): |
291 | - print(obj) |
292 | - print("attributes:", obj.lp_attributes) |
293 | - for attr in obj.lp_attributes: |
294 | - print("attribute[%s]: %s" % (attr, getattr(obj, attr))) |
295 | - print("entries:", obj.lp_entries) |
296 | - for entry in obj.lp_entries: |
297 | - print("entry[%s]: %s" % (entry, getattr(obj, entry))) |
298 | - print("collections:", obj.lp_collections) |
299 | - print("operations:", obj.lp_operations) |
300 | - print("-" * 80) |
301 | - |
302 | - |
303 | -class DefaultPolicy: |
304 | - |
305 | - mps_need_commit_message = False |
306 | - |
307 | - max_review_age = 0 |
308 | - |
309 | - max_bug_new_age = 7 |
310 | - |
311 | - # minimal number of reviewers before merging |
312 | - min_approve = 2 |
313 | - |
314 | - # can't be merged before n days |
315 | - days_before_merge = 5 |
316 | - |
317 | - # can be merged before days_before_merge if at least n approvals |
318 | - approvals_to_bypass = 3 |
319 | - |
320 | - |
321 | -class UTC(datetime.tzinfo): |
322 | - |
323 | - def dst(self, foo): |
324 | - return datetime.timedelta(0, 0) |
325 | - |
326 | - def utcoffset(self, foo): |
327 | - return datetime.timedelta(0, 0) |
328 | - |
329 | - |
330 | -UTC = UTC() |
331 | - |
332 | - |
333 | -class Votes(object): |
334 | - """ Holds votes of a proposal """ |
335 | - |
336 | - __states__ = { |
337 | - 'Approve': 'approve', |
338 | - 'Needs Fixing': 'needs_fixing', |
339 | - 'Needs Information': 'needs_information', |
340 | - 'Abstain': 'abstain', |
341 | - 'Disapprove': 'disapprove', |
342 | - 'Resubmit': 'resubmit', |
343 | - 'Pending': 'pending', |
344 | - } |
345 | - |
346 | - __signs__ = { |
347 | - 'approve': '+', |
348 | - 'needs_fixing': '!', |
349 | - 'needs_information': '?', |
350 | - 'abstain': '~', |
351 | - 'disapprove': '-', |
352 | - 'resubmit': 'R', |
353 | - 'pending': '*', |
354 | - } |
355 | - |
356 | - __ignore_for_approval__ = ['abstain', 'pending'] |
357 | - |
358 | - def __init__(self, votes): |
359 | - self._votes = collections.Counter( |
360 | - self.__states__[vote.comment.vote if vote.comment else 'Pending'] |
361 | - for vote in votes |
362 | - ) # no comment is a pending review, usually the team |
363 | - |
364 | - def __getattr__(self, name): |
365 | - if name in self._votes: |
366 | - return self._votes[name] |
367 | - elif name in self.__states__.values(): |
368 | - return 0 |
369 | - else: |
370 | - raise AttributeError |
371 | - |
372 | - def __str__(self): |
373 | - signs = ['%d%s' % (count, self.__signs__[state]) for |
374 | - state, count in self._votes.iteritems()] |
375 | - return ', '.join(signs) |
376 | - |
377 | - def total(self, for_approval=False): |
378 | - if for_approval: |
379 | - return sum(count for state, count in self._votes.iteritems() |
380 | - if state not in self.__ignore_for_approval__) |
381 | - return sum(self._votes.values()) |
382 | - |
383 | - @classmethod |
384 | - def legend(cls): |
385 | - return dict((state, cls.__signs__[code]) for |
386 | - state, code in cls.__states__.iteritems()) |
387 | - |
388 | - |
389 | -def gen_project_nags(lp, policy, project_name): |
390 | - # TODO: Detect project groups and redirect the nag to all projects |
391 | - # underneath that project |
392 | - # Access the project that we care about |
393 | - logging.debug("Accessing project %r", project_name) |
394 | - project = lp.projects[project_name] |
395 | - # Re-yield all the merge proposal nags |
396 | - # NOTE: change to yield from in py3k3 |
397 | - for nag in gen_merge_proposal_nags(lp, policy, project): |
398 | - yield nag |
399 | - |
400 | - |
401 | -def gen_merge_proposal_nags(lp, policy, project): |
402 | - # XXX: cannnot use utcnow as launchpad returns tz-aware objects here |
403 | - now = datetime.datetime.now(tz=UTC) |
404 | - logging.debug("Looking for merge proposals in project %r", project) |
405 | - # NOTE: Workaround for project.getMergeProposals() crashing on timeouts |
406 | - try: |
407 | - merge_proposals = project.getMergeProposals(status='Needs review') |
408 | - except AttributeError: |
409 | - logging.warning("The project %s has no code to look at", project) |
410 | - return |
411 | - for proposal in merge_proposals: |
412 | - logging.debug("Looking at merge proposal %r", proposal) |
413 | - # Skip everything that is still not requested for review |
414 | - if proposal.date_review_requested is None: |
415 | - continue |
416 | - # Skip everything that is already merged |
417 | - if proposal.date_merged is not None: |
418 | - continue |
419 | - votes = Votes(proposal.votes) |
420 | - |
421 | - # Nag about missing commit message on merge requests |
422 | - if policy.mps_need_commit_message and proposal.commit_message is None: |
423 | - yield Nag( |
424 | - person=proposal.registrant.display_name, |
425 | - action="set a commit message on merge request", |
426 | - subject=proposal.web_link, |
427 | - sort_class=SORT_CLASS_MERGE, |
428 | - sort_priority=None, # TODO: get from max(linked bugs) |
429 | - sort_age=(proposal.date_review_requested - now).days, |
430 | - project_name=project.name, |
431 | - votes=votes, |
432 | - ) |
433 | - |
434 | - age = (now - proposal.date_review_requested).days |
435 | - |
436 | - if (votes.approve == votes.total(for_approval=True) and |
437 | - (votes.approve >= policy.approvals_to_bypass or |
438 | - votes.approve >= policy.min_approve and |
439 | - age >= policy.days_before_merge)): |
440 | - yield Nag( |
441 | - person='A committer', |
442 | - action="consider to merge the proposal", |
443 | - subject=proposal.web_link, |
444 | - sort_class=SORT_CLASS_MERGE, |
445 | - sort_priority=None, # TODO: get from max(linked bugs) |
446 | - sort_age=(proposal.date_review_requested - now).days, |
447 | - project_name=project.name, |
448 | - votes=votes, |
449 | - ) |
450 | - continue |
451 | - |
452 | - # Nag about aging merge requests |
453 | - if age >= policy.max_review_age: |
454 | - yield Nag( |
455 | - person='Someone', |
456 | - action="review the merge request", |
457 | - subject=proposal.web_link, |
458 | - sort_class=SORT_CLASS_MERGE, |
459 | - sort_priority=None, # TODO: get from max(linked bugs) |
460 | - sort_age=(proposal.date_review_requested - now).days, |
461 | - project_name=project.name, |
462 | - votes=votes, |
463 | - ) |
464 | - |
465 | - |
466 | -def gen_bug_nags(lp, policy, project): |
467 | - now = datetime.datetime.now(tz=UTC) |
468 | - new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age) |
469 | - # Nag about bugs that are "New" for too long |
470 | - logging.debug("Looking at 'new' bugs that are 8 days or older") |
471 | - aging_new_bugs = project.searchTasks(status='New', |
472 | - created_before=new_threshold) |
473 | - for bug_task in aging_new_bugs: |
474 | - yield Nag( |
475 | - person='everyone', |
476 | - action='triage the bug', |
477 | - subject=bug_task.web_link, |
478 | - sort_class=SORT_CLASS_BUG, |
479 | - sort_priority=None, # TODO: convert from importance name |
480 | - sort_age=None, |
481 | - votes=None) |
482 | - |
483 | - |
484 | -def parse_projects_file(filename): |
485 | - """ Parse a file containing name of the projects |
486 | - A line per project |
487 | - """ |
488 | - with open(filename, mode='rU') as project_file: |
489 | - content = project_file.read() |
490 | - |
491 | - return [name.strip() for name in content.splitlines()] |
492 | - |
493 | - |
494 | def main(): |
495 | parser = argparse.ArgumentParser() |
496 | group = parser.add_argument_group(title="debugging") |
497 | @@ -304,10 +86,7 @@ |
498 | if args.debug: |
499 | logging.basicConfig(level=logging.DEBUG) |
500 | # Access Lauchpad object as the current (system) user or anonymously |
501 | - if args.anonymous: |
502 | - lp = Launchpad.login_anonymously(consumer_name, args.service_root) |
503 | - else: |
504 | - lp = Launchpad.login_with(consumer_name, args.service_root) |
505 | + lp = launchpad_login(args.anonymous, consumer_name, args.service_root) |
506 | |
507 | if not args.project and not args.projects_file: |
508 | parser.print_usage() |
Sorry I didn't saw your proposal before (even if I use openerp-nag...) whereas it deserves some attention.
Thanks for your work so far.
Seems good to me even if some changes seems not really necessary to me, surely you had some reasons.
What is the rationale to change the Nag namedtuple to a class? A reason could be that you added methods, but not. On that topic, couldn't we move the big print of the end to a method on Nag() (it seems that it could even be the __str__).
Couldn't already the module be used as a lib? (as there is if __name__ == "__main__":) If the filename is an issue (it is, i we want to import it), maybe we can consider to rename it? Not that I strongly disagree with the split but it seem a bit overkill to me here. But maybe do you have great plans to extend this tool and so it makes more sense ;-)
I strongly disagree to remove the --authenticated option: the Launchpad API is limited with anonymous users and it is used now for the votes (the votes are only visible by authenticated users). BTW you may want to rebase on the master since votes have been integrated meanwhile.
What was the bug with the age?