Merge lp:~gmb/launchpad/reschedule-button-qafix-bug-558415 into lp:launchpad/db-devel

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/reschedule-button-qafix-bug-558415
Merge into: lp:launchpad/db-devel
Diff against target: 475 lines (+173/-112)
8 files modified
lib/lp/bugs/browser/bugwatch.py (+2/-41)
lib/lp/bugs/browser/tests/bugwatch-views.txt (+0/-55)
lib/lp/bugs/configure.zcml (+4/-1)
lib/lp/bugs/doc/bugwatch.txt (+84/-0)
lib/lp/bugs/interfaces/bugwatch.py (+15/-0)
lib/lp/bugs/model/bugwatch.py (+55/-2)
lib/lp/bugs/stories/bugwatches/xx-edit-bugwatch.txt (+11/-11)
lib/lp/bugs/templates/bugwatch-portlet-activity.pt (+2/-2)
To merge this branch: bzr merge lp:~gmb/launchpad/reschedule-button-qafix-bug-558415
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+23698@code.launchpad.net

Commit message

Bug watches can now be rescheduled by any user rather than just administrators.

Description of the change

This branch fixes a bug with the orignal fix for bug 558415 whereby the
reschedule button would be shown on a bug watch page but nobody but an
admin could click it without it OOPSing.

To fix this I've added a setNextCheck() method to IBugWatch. This can be
called by any user. The upshot of this is that users have a way of
rescheduling a watch, which we can control, without changing next_check
directly.

== lib/lp/bugs/browser/bugwatch.py ==

 - I've modified the BugWatchActivityPortletView to use
   BugWatch.setNextCheck() as described below.

== lib/lp/bugs/browser/tests/bugwatch-views.txt ==

 - I've moved the tests from this file into doc/bugwatch.txt since the
   logic for determining whether a watch can be updated is now part of
   the watch, not the view.

== lib/lp/bugs/configure.zcml ==

 - I've updated the ZCML as appropriate.

== lib/lp/bugs/doc/bugwatch.txt ==

 - I've moved the test from bugwatch-views.txt here and updated them as
   appropriate.
 - I've added tests for BugWatch.setNextCheck()

== lib/lp/bugs/interfaces/bugwatch.py ==

 - I've added a setNextCheck() method and can_be_rescheduled and
   failed_activity properties to IBugWatch.

== lib/lp/bugs/model/bugwatch.py ==

 - I've moved the implementation of userCanReschedule() here from the
   BugWatchActivityPortletView can called it can_be_rescheduled.
 - I've added implementations of IBugWatch.failed_activity and
   IBugWatch.setNextCheck().

== lib/lp/bugs/stories/bugwatches/xx-edit-bugwatch.txt ==

 - I've updated the test to use the user_browser rather than the admin
   browser. This demonstrates that any user can use the reschedule
   button.

== lib/lp/bugs/templates/bugwatch-portlet-activity.pt ==

 - I've updated the template to use the failed_activity and activity
   properties of the BugWatch rather than using view properties.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Graham this branch looks good. You have one typo of 'wathc'.

Also I know you just copied it from the existing code, but I'd expect to see

return failure_ratio < WATCH_RESCHEDULE_THRESHOLD

So that 6/10 failures would still return true.

review: Approve (code)
Revision history for this message
Brad Crittenden (bac) wrote :

Graham I forgot to mention there were some valid lint issues with this branch, like unneeded imports.

Revision history for this message
Graham Binns (gmb) wrote :

On 20 April 2010 18:25, Brad Crittenden <email address hidden> wrote:
> Graham I forgot to mention there were some valid lint issues with this branch, like unneeded imports.

Thanks Brad, I'll take care of those now.

--
Graham Binns | PGP Key: EC66FA7D

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bugwatch.py'
--- lib/lp/bugs/browser/bugwatch.py 2010-04-15 15:14:21 +0000
+++ lib/lp/bugs/browser/bugwatch.py 2010-04-20 20:31:31 +0000
@@ -10,13 +10,10 @@
10 'BugWatchEditView',10 'BugWatchEditView',
11 'BugWatchView']11 'BugWatchView']
1212
13from datetime import datetime
14from pytz import utc
1513
16from zope.component import getUtility14from zope.component import getUtility
17from zope.interface import Interface15from zope.interface import Interface
1816
19from canonical.cachedproperty import cachedproperty
20from canonical.database.constants import UTC_NOW17from canonical.database.constants import UTC_NOW
21from canonical.widgets.textwidgets import URIWidget18from canonical.widgets.textwidgets import URIWidget
2219
@@ -35,9 +32,6 @@
35from canonical.launchpad.webapp.menu import structured32from canonical.launchpad.webapp.menu import structured
3633
3734
38WATCH_RESCHEDULE_THRESHOLD = 0.6
39
40
41class BugWatchSetNavigation(GetitemNavigation):35class BugWatchSetNavigation(GetitemNavigation):
4236
43 usedfor = IBugWatchSet37 usedfor = IBugWatchSet
@@ -158,48 +152,15 @@
158152
159 schema = BugWatchEditForm153 schema = BugWatchEditForm
160154
161 @cachedproperty
162 def total_watch_activity_count(self):
163 return self.context.activity.count()
164
165 @cachedproperty
166 def failed_watch_activity_count(self):
167 failed_activity_count = len([
168 activity for activity in self.context.activity if
169 activity.result not in BUG_WATCH_ACTIVITY_SUCCESS_STATUSES])
170 return failed_activity_count
171
172 def userCanReschedule(self, action=None):155 def userCanReschedule(self, action=None):
173 """Return True if the current user can reschedule the bug watch."""156 """Return True if the current user can reschedule the bug watch."""
174 if (self.context.next_check is not None and157 return self.context.can_be_rescheduled
175 self.context.next_check <= datetime.now(utc)):
176 # If the watch is already scheduled for a time in the past
177 # (or for right now) it can't be rescheduled, since it
178 # should be be checked by the next checkwatches run anyway.
179 return False
180
181 if self.total_watch_activity_count == 0:
182 # Don't show the reschedule button if the watch has never
183 # been checked.
184 return False
185
186 if self.failed_watch_activity_count == 0:
187 # Don't show the reschedule button if the watch has never
188 # failed.
189 return False
190
191 # If the ratio is lower than the reschedule threshold, we
192 # can show the button.
193 failure_ratio = (
194 float(self.failed_watch_activity_count) /
195 self.total_watch_activity_count)
196 return failure_ratio <= WATCH_RESCHEDULE_THRESHOLD
197158
198 @action('Update Now', name='reschedule', condition=userCanReschedule)159 @action('Update Now', name='reschedule', condition=userCanReschedule)
199 def reschedule_action(self, action, data):160 def reschedule_action(self, action, data):
200 """Schedule the current bug watch for immediate checking."""161 """Schedule the current bug watch for immediate checking."""
201 bugwatch = self.context162 bugwatch = self.context
202 bugwatch.next_check = UTC_NOW163 bugwatch.setNextCheck(UTC_NOW)
203 self.request.response.addInfoNotification(164 self.request.response.addInfoNotification(
204 structured(165 structured(
205 'The <a href="%(url)s">%(bugtracker)s #%(remote_bug)s</a> '166 'The <a href="%(url)s">%(bugtracker)s #%(remote_bug)s</a> '
206167
=== modified file 'lib/lp/bugs/browser/tests/bugwatch-views.txt'
--- lib/lp/bugs/browser/tests/bugwatch-views.txt 2010-04-15 15:14:21 +0000
+++ lib/lp/bugs/browser/tests/bugwatch-views.txt 2010-04-20 20:31:31 +0000
@@ -110,58 +110,3 @@
110 'result_text': 'Synchronisation succeeded'}110 'result_text': 'Synchronisation succeeded'}
111111
112112
113Rescheduling a watch
114--------------------
115
116The BugWatch activity portlet provides a method, userCanReschedule(),
117which indicates whether or not a user can reschedule a bug watch for
118checking. For a new bug watch this will be False.
119
120 >>> from pytz import utc
121 >>> from datetime import datetime
122 >>> schedulable_watch = factory.makeBugWatch()
123 >>> schedulable_watch.next_check = None
124 >>> scheduling_view = create_initialized_view(
125 ... schedulable_watch, '+portlet-activity')
126
127 >>> scheduling_view.userCanReschedule()
128 False
129
130If there's been activity on the watch but it's always been successful,
131userCanReschedule() will return False.
132
133 >>> schedulable_watch.addActivity()
134 >>> scheduling_view.userCanReschedule()
135 False
136
137If the watch's updates have failed less than 60% of the time,
138userCanReschedule() will return True
139
140 >>> scheduling_view = create_initialized_view(
141 ... schedulable_watch, '+portlet-activity')
142 >>> schedulable_watch.addActivity(
143 ... result=BugWatchActivityStatus.BUG_NOT_FOUND)
144 >>> scheduling_view.userCanReschedule()
145 True
146
147If the watch is rescheduled, the button will disappear, since the
148next_check time for the watch will be in the past (or in this case is
149now) and therefore it will be checked with the next checkwatches run.
150
151 >>> schedulable_watch.next_check = datetime.now(utc)
152 >>> scheduling_view = create_initialized_view(
153 ... schedulable_watch, '+portlet-activity')
154 >>> scheduling_view.userCanReschedule()
155 False
156
157However, if the watch has failed more than 60% of the time the button
158will disappear again, since it's assumed that the watch needs attention
159in order for it to be able to work again.
160
161 >>> schedulable_watch.next_check = None
162 >>> schedulable_watch.addActivity(
163 ... result=BugWatchActivityStatus.BUG_NOT_FOUND)
164 >>> scheduling_view = create_initialized_view(
165 ... schedulable_watch, '+portlet-activity')
166 >>> scheduling_view.userCanReschedule()
167 False
168113
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-04-14 12:55:44 +0000
+++ lib/lp/bugs/configure.zcml 2010-04-20 20:31:31 +0000
@@ -849,7 +849,9 @@
849 bug849 bug
850 bugtasks850 bugtasks
851 bugtracker851 bugtracker
852 can_be_rescheduled
852 datecreated853 datecreated
854 failed_activity
853 getLastErrorMessage855 getLastErrorMessage
854 hasComment856 hasComment
855 unpushed_comments857 unpushed_comments
@@ -870,7 +872,8 @@
870 permission="launchpad.AnyPerson"872 permission="launchpad.AnyPerson"
871 attributes="873 attributes="
872 destroySelf874 destroySelf
873 addActivity"875 addActivity
876 setNextCheck"
874 set_attributes="bugtracker remotebug"/>877 set_attributes="bugtracker remotebug"/>
875 <require878 <require
876 permission="launchpad.Admin"879 permission="launchpad.Admin"
877880
=== modified file 'lib/lp/bugs/doc/bugwatch.txt'
--- lib/lp/bugs/doc/bugwatch.txt 2010-03-26 10:39:53 +0000
+++ lib/lp/bugs/doc/bugwatch.txt 2010-04-20 20:31:31 +0000
@@ -513,3 +513,87 @@
513 >>> bug.removeWatch(bug_watch, factory.makePerson())513 >>> bug.removeWatch(bug_watch, factory.makePerson())
514 >>> [bug_watch.remotebug for bug_watch in bug.watches]514 >>> [bug_watch.remotebug for bug_watch in bug.watches]
515 []515 []
516
517
518Checking if a watch can be rescheduled
519--------------------------------------
520
521IBugWatch provides an attribute, can_be_rescheduled, which indicates
522whether or not the watch can be rescheduled. For a new bug watch this
523will be False.
524
525 >>> schedulable_watch = factory.makeBugWatch()
526 >>> schedulable_watch.next_check = None
527 >>> schedulable_watch.can_be_rescheduled
528 False
529
530If there's been activity on the watch but it's always been successful,
531can_be_rescheduled will be False.
532
533 >>> schedulable_watch.addActivity()
534 >>> schedulable_watch.can_be_rescheduled
535 False
536
537If the watch's updates have failed less than 60% of the time,
538can_be_rescheduled will be True
539
540 >>> from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
541 >>> schedulable_watch.addActivity(
542 ... result=BugWatchActivityStatus.BUG_NOT_FOUND)
543 >>> schedulable_watch.can_be_rescheduled
544 True
545
546If the watch is rescheduled, can_be_rescheduled will be False, since the
547next_check time for the watch will be in the past (or in this case is
548now) and therefore it will be checked with the next checkwatches run.
549
550 >>> from pytz import utc
551 >>> from datetime import datetime
552 >>> schedulable_watch.next_check = datetime.now(utc)
553 >>> schedulable_watch.can_be_rescheduled
554 False
555
556However, if the watch has failed more than 60% of the time
557can_be_rescheduled will be False, since it's assumed that the watch
558needs attention in order for it to be able to work again.
559
560 >>> schedulable_watch.next_check = None
561 >>> schedulable_watch.addActivity(
562 ... result=BugWatchActivityStatus.BUG_NOT_FOUND)
563 >>> schedulable_watch.can_be_rescheduled
564 False
565
566
567Rescheduling a watch
568--------------------
569
570The rescheduling of a watch is done via IBugWatch.setNextCheck(). This
571is to ensure that watches are only rescheduled when can_be_rescheduled
572is True (note that the BugWatch Scheduler bypasses setNextCheck() and
573sets next_check directly because it has admin privileges).
574
575The schedulable_watch that we used in the previous test cannot currently
576be rescheduled.
577
578 >>> schedulable_watch.can_be_rescheduled
579 False
580
581Calling setNextCheck() on this watch will cause an Exception,
582BugWatchCannotBeRescheduled, to be raised.
583
584 >>> schedulable_watch.setNextCheck(datetime.now(utc))
585 Traceback (most recent call last):
586 ...
587 BugWatchCannotBeRescheduled...
588
589If we add some activity to the watch, to make its can_be_rescheduled
590property become True, setNextCheck() will succeed.
591
592 >>> schedulable_watch.addActivity()
593 >>> schedulable_watch.can_be_rescheduled
594 True
595
596 >>> next_check = datetime.now(utc)
597 >>> schedulable_watch.setNextCheck(next_check)
598 >>> schedulable_watch.next_check == next_check
599 True
516600
=== modified file 'lib/lp/bugs/interfaces/bugwatch.py'
--- lib/lp/bugs/interfaces/bugwatch.py 2010-04-16 14:50:46 +0000
+++ lib/lp/bugs/interfaces/bugwatch.py 2010-04-20 20:31:31 +0000
@@ -10,6 +10,7 @@
10__all__ = [10__all__ = [
11 'BUG_WATCH_ACTIVITY_SUCCESS_STATUSES',11 'BUG_WATCH_ACTIVITY_SUCCESS_STATUSES',
12 'BugWatchActivityStatus',12 'BugWatchActivityStatus',
13 'BugWatchCannotBeRescheduled',
13 'IBugWatch',14 'IBugWatch',
14 'IBugWatchActivity',15 'IBugWatchActivity',
15 'IBugWatchSet',16 'IBugWatchSet',
@@ -210,6 +211,10 @@
210 Text(title=_('The URL at which to view the remote bug.'),211 Text(title=_('The URL at which to view the remote bug.'),
211 readonly=True))212 readonly=True))
212213
214 can_be_rescheduled = Attribute(
215 "A True or False indicator of whether or not this watch can be "
216 "rescheduled.")
217
213 def updateImportance(remote_importance, malone_importance):218 def updateImportance(remote_importance, malone_importance):
214 """Update the importance of the bug watch and any linked bug task.219 """Update the importance of the bug watch and any linked bug task.
215220
@@ -250,6 +255,13 @@
250 def addActivity(result=None, message=None, oops_id=None):255 def addActivity(result=None, message=None, oops_id=None):
251 """Add an `IBugWatchActivity` record for this BugWatch."""256 """Add an `IBugWatchActivity` record for this BugWatch."""
252257
258 def setNextCheck(next_check):
259 """Set the next_check time of the watch.
260
261 :raises: `BugWatchCannotBeRescheduled` if
262 `IBugWatch.can_be_rescheduled` is False.
263 """
264
253265
254# Defined here because of circular imports.266# Defined here because of circular imports.
255IBugTracker['watches'].value_type.schema = IBugWatch267IBugTracker['watches'].value_type.schema = IBugWatch
@@ -363,3 +375,6 @@
363 title=_('OOPS ID'), readonly=True,375 title=_('OOPS ID'), readonly=True,
364 description=_("The OOPS ID associated with this activity."))376 description=_("The OOPS ID associated with this activity."))
365377
378
379class BugWatchCannotBeRescheduled(Exception):
380 """The current `IBugWatch` can't be rescheduled."""
366381
=== modified file 'lib/lp/bugs/model/bugwatch.py'
--- lib/lp/bugs/model/bugwatch.py 2010-04-16 14:50:46 +0000
+++ lib/lp/bugs/model/bugwatch.py 2010-04-20 20:31:31 +0000
@@ -12,6 +12,9 @@
1212
13import re13import re
14import urllib14import urllib
15
16from datetime import datetime
17from pytz import utc
15from urlparse import urlunsplit18from urlparse import urlunsplit
1619
17from zope.event import notify20from zope.event import notify
@@ -44,8 +47,9 @@
4447
45from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet48from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet
46from lp.bugs.interfaces.bugwatch import (49from lp.bugs.interfaces.bugwatch import (
47 BugWatchActivityStatus, IBugWatch, IBugWatchActivity, IBugWatchSet,50 BUG_WATCH_ACTIVITY_SUCCESS_STATUSES, BugWatchActivityStatus,
48 NoBugTrackerFound, UnrecognizedBugTrackerURL)51 BugWatchCannotBeRescheduled, IBugWatch, IBugWatchActivity,
52 IBugWatchSet, NoBugTrackerFound, UnrecognizedBugTrackerURL)
49from lp.bugs.model.bugmessage import BugMessage53from lp.bugs.model.bugmessage import BugMessage
50from lp.bugs.model.bugset import BugSetBase54from lp.bugs.model.bugset import BugSetBase
51from lp.bugs.model.bugtask import BugTask55from lp.bugs.model.bugtask import BugTask
@@ -66,6 +70,9 @@
66 }70 }
6771
6872
73WATCH_RESCHEDULE_THRESHOLD = 0.6
74
75
69class BugWatch(SQLBase):76class BugWatch(SQLBase):
70 """See `IBugWatch`."""77 """See `IBugWatch`."""
71 implements(IBugWatch)78 implements(IBugWatch)
@@ -307,6 +314,52 @@
307 BugWatchActivity.bug_watch == self).order_by(314 BugWatchActivity.bug_watch == self).order_by(
308 Desc('activity_date'))315 Desc('activity_date'))
309316
317 @property
318 def can_be_rescheduled(self):
319 """See `IBugWatch`."""
320 if (self.next_check is not None and
321 self.next_check <= datetime.now(utc)):
322 # If the watch is already scheduled for a time in the past
323 # (or for right now) it can't be rescheduled, since it
324 # should be be checked by the next checkwatches run anyway.
325 return False
326
327 if self.activity.is_empty():
328 # Don't show the reschedule button if the watch has never
329 # been checked.
330 return False
331
332 if self.failed_activity.is_empty():
333 # Don't show the reschedule button if the watch has never
334 # failed.
335 return False
336
337 # If the ratio is lower than the reschedule threshold, we
338 # can show the button.
339 failure_ratio = (
340 float(self.failed_activity.count()) /
341 self.activity.count())
342 return failure_ratio <= WATCH_RESCHEDULE_THRESHOLD
343
344 @property
345 def failed_activity(self):
346 store = Store.of(self)
347 success_status_ids = [
348 status.value for status in BUG_WATCH_ACTIVITY_SUCCESS_STATUSES]
349
350 return store.find(
351 BugWatchActivity,
352 BugWatchActivity.bug_watch == self,
353 Not(In(BugWatchActivity.result, success_status_ids))).order_by(
354 Desc('activity_date'))
355
356 def setNextCheck(self, next_check):
357 """See `IBugWatch`."""
358 if not self.can_be_rescheduled:
359 raise BugWatchCannotBeRescheduled()
360
361 self.next_check = next_check
362
310363
311class BugWatchSet(BugSetBase):364class BugWatchSet(BugSetBase):
312 """A set for BugWatch"""365 """A set for BugWatch"""
313366
=== modified file 'lib/lp/bugs/stories/bugwatches/xx-edit-bugwatch.txt'
--- lib/lp/bugs/stories/bugwatches/xx-edit-bugwatch.txt 2010-04-15 15:14:21 +0000
+++ lib/lp/bugs/stories/bugwatches/xx-edit-bugwatch.txt 2010-04-20 20:31:31 +0000
@@ -140,8 +140,8 @@
140 ... (bug_watch.bug.id, bug_watch.id))140 ... (bug_watch.bug.id, bug_watch.id))
141 >>> logout()141 >>> logout()
142142
143 >>> admin_browser.open(watch_url)143 >>> user_browser.open(watch_url)
144 >>> admin_browser.getControl('Update Now')144 >>> user_browser.getControl('Update Now')
145 Traceback (most recent call last):145 Traceback (most recent call last):
146 ...146 ...
147 LookupError: label 'Update Now'147 LookupError: label 'Update Now'
@@ -153,8 +153,8 @@
153 >>> bug_watch.addActivity()153 >>> bug_watch.addActivity()
154 >>> logout()154 >>> logout()
155155
156 >>> admin_browser.open(watch_url)156 >>> user_browser.open(watch_url)
157 >>> admin_browser.getControl('Update Now')157 >>> user_browser.getControl('Update Now')
158 Traceback (most recent call last):158 Traceback (most recent call last):
159 ...159 ...
160 LookupError: label 'Update Now'160 LookupError: label 'Update Now'
@@ -166,11 +166,11 @@
166 >>> bug_watch.addActivity(result=BugWatchActivityStatus.BUG_NOT_FOUND)166 >>> bug_watch.addActivity(result=BugWatchActivityStatus.BUG_NOT_FOUND)
167 >>> logout()167 >>> logout()
168168
169 >>> admin_browser.open(watch_url)169 >>> user_browser.open(watch_url)
170 >>> reschedule_button = admin_browser.getControl('Update Now')170 >>> reschedule_button = user_browser.getControl('Update Now')
171171
172 >>> data_tag = find_tag_by_id(172 >>> data_tag = find_tag_by_id(
173 ... admin_browser.contents, 'bugwatch-next_check')173 ... user_browser.contents, 'bugwatch-next_check')
174 >>> print extract_text(data_tag.renderContents())174 >>> print extract_text(data_tag.renderContents())
175 Next check: Not yet scheduled175 Next check: Not yet scheduled
176176
@@ -180,22 +180,22 @@
180 >>> reschedule_button.click()180 >>> reschedule_button.click()
181181
182 >>> for message in find_tags_by_class(182 >>> for message in find_tags_by_class(
183 ... admin_browser.contents, 'informational message'):183 ... user_browser.contents, 'informational message'):
184 ... print extract_text(message)184 ... print extract_text(message)
185 The ... bug watch has been scheduled for immediate checking.185 The ... bug watch has been scheduled for immediate checking.
186186
187Looking at the watch +edit page again, we can see that the watch has187Looking at the watch +edit page again, we can see that the watch has
188been scheduled.188been scheduled.
189189
190 >>> admin_browser.open(watch_url)190 >>> user_browser.open(watch_url)
191 >>> data_tag = find_tag_by_id(191 >>> data_tag = find_tag_by_id(
192 ... admin_browser.contents, 'bugwatch-next_check')192 ... user_browser.contents, 'bugwatch-next_check')
193 >>> print extract_text(data_tag.renderContents())193 >>> print extract_text(data_tag.renderContents())
194 Next check: 2...194 Next check: 2...
195195
196The button will no longer be shown on the page.196The button will no longer be shown on the page.
197197
198 >>> reschedule_button = admin_browser.getControl('Update Now')198 >>> reschedule_button = user_browser.getControl('Update Now')
199 Traceback (most recent call last):199 Traceback (most recent call last):
200 ...200 ...
201 LookupError: label 'Update Now'201 LookupError: label 'Update Now'
202202
=== modified file 'lib/lp/bugs/templates/bugwatch-portlet-activity.pt'
--- lib/lp/bugs/templates/bugwatch-portlet-activity.pt 2010-04-15 15:14:21 +0000
+++ lib/lp/bugs/templates/bugwatch-portlet-activity.pt 2010-04-20 20:31:31 +0000
@@ -17,10 +17,10 @@
17 <div>17 <div>
18 This watch has failed to update at18 This watch has failed to update at
19 <tal:fail-count19 <tal:fail-count
20 replace="view/failed_watch_activity_count" />20 replace="context/failed_activity/count" />
21 out of the last21 out of the last
22 <tal:fail-count22 <tal:fail-count
23 replace="view/total_watch_activity_count" />23 replace="context/activity/count" />
24 attempts.24 attempts.
25 </div>25 </div>
26 <div>26 <div>

Subscribers

People subscribed via source and target branches

to status/vote changes: