Merge lp:~intellectronica/launchpad/heat-decay-complete into lp:launchpad

Proposed by Eleanor Berger on 2010-02-26
Status: Merged
Approved by: Edwin Grubbs on 2010-02-26
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~intellectronica/launchpad/heat-decay-complete
Merge into: lp:launchpad
Diff against target: 130 lines (+58/-2)
3 files modified
lib/lp/bugs/doc/bug-heat.txt (+8/-0)
lib/lp/bugs/scripts/bugheat.py (+18/-0)
lib/lp/bugs/scripts/tests/test_bugheat.py (+32/-2)
To merge this branch: bzr merge lp:~intellectronica/launchpad/heat-decay-complete
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code 2010-02-26 Approve on 2010-02-26
Review via email: mp+20250@code.launchpad.net
To post a comment you must log in.
Eleanor Berger (intellectronica) wrote :

This branch fixes two small bug heat related issues:

1. Bugs with a resolved status for all their tasks don't have any heat at all.
2. Bug heat decays over time. For every month that passes without the bug being touched it loses 10% of its heat.

I've added unit tests.

Edwin Grubbs (edwin-grubbs) wrote :
Download full text (4.8 KiB)

Hi Tom,

This is a nice branch. I have a few comments below, and there are some
lint errors.

merge-conditional

-Edwin

== Pyflakes notices ==

lib/lp/bugs/scripts/bugheat.py
    13: 'getUtility' imported but unused
    14: 'implements' imported but unused
    16: 'ITunableLoop' imported but unused
    17: 'DBLoopTuner' imported but unused

lib/lp/bugs/scripts/tests/test_bugheat.py
    10: 'datetime' imported but unused

>=== modified file 'lib/lp/bugs/scripts/bugheat.py'
>--- lib/lp/bugs/scripts/bugheat.py 2010-01-26 20:31:13 +0000
>+++ lib/lp/bugs/scripts/bugheat.py 2010-02-26 21:30:36 +0000
>@@ -8,6 +8,7 @@
> 'BugHeatCalculator',
> ]
>
>+from datetime import datetime
>
> from zope.component import getUtility
> from zope.interface import implements
>@@ -15,6 +16,8 @@
> from canonical.launchpad.interfaces.looptuner import ITunableLoop
> from canonical.launchpad.utilities.looptuner import DBLoopTuner
>
>+from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
>+
>
> class BugHeatConstants:
>
>@@ -63,8 +66,16 @@
> len(direct_subscribers) + len(subscribers_from_dupes))
> return subscriber_count * BugHeatConstants.SUBSCRIBER
>
>+ def _bugIsComplete(self):
>+ """Are all the tasks for this bug resolved?"""
>+ return all([(task.status in RESOLVED_BUGTASK_STATUSES)
>+ for task in self.bug.bugtasks])
>+
> def getBugHeat(self):
> """Return the total heat for the current bug."""
>+ if self._bugIsComplete():
>+ return 0
>+
> total_heat = sum([
> self._getHeatFromAffectedUsers(),
> self._getHeatFromDuplicates(),
>@@ -73,5 +84,15 @@
> self._getHeatFromSubscribers(),
> ])
>
>+ # Bugs decay over time. Every month the bug isn't touched its heat
>+ # decreeses by 5%.

s/decreeses/decreases/

The comment says 5%, but the test says 10%. Which is it?

>+ months = (
>+ datetime.utcnow() -
>+ self.bug.date_last_updated.replace(tzinfo=None)).days / 30
>+ for i in range(months):
>+ total_heat = total_heat * 0.95

This could be simplified as:
    total_heat *= 0.95 ** months

>+
>+ total_heat = int(total_heat)
>+
> return total_heat
>
>
>=== modified file 'lib/lp/bugs/scripts/tests/test_bugheat.py'
>--- lib/lp/bugs/scripts/tests/test_bugheat.py 2010-01-12 16:41:23 +0000
>+++ lib/lp/bugs/scripts/tests/test_bugheat.py 2010-02-26 21:30:36 +0000
>@@ -1,4 +1,3 @@
>-
> # Copyright 2010 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
>@@ -8,12 +7,14 @@
>
> import unittest
>
>+from datetime import datetime, timedelta
>+
> from canonical.testing import LaunchpadZopelessLayer
>
>+from lp.bugs.interfaces.bugtask import BugTaskStatus
> from lp.bugs.scripts.bugheat import BugHeatCalculator, BugHeatConstants
> from lp.testing import TestCaseWithFactory
>
>-
> class TestBugHeatCalculator(TestCaseWithFactory):
> """Tests for the BugHeatCalculator class."""
>
>@@ -177,6 +178,35 @@
> "Expected bug heat did not match actual bug heat. "
> "E...

Read more...

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/doc/bug-heat.txt'
2--- lib/lp/bugs/doc/bug-heat.txt 2010-01-12 16:41:23 +0000
3+++ lib/lp/bugs/doc/bug-heat.txt 2010-02-27 14:48:24 +0000
4@@ -44,6 +44,14 @@
5 >>> bug_1.heat
6 0
7
8+We touch bug 1 to make sure its date_last_updated is recent enough (bug heat
9+decays over time).
10+
11+ >>> new_comment = bug_1.newMessage(
12+ ... owner=bug_1.owner, subject="...", content="...")
13+ >>> import transaction ; transaction.commit()
14+ >>> bug_1 = getUtility(IBugSet).get(1)
15+
16 >>> update_bug_heat(chunk_size=1)
17 DEBUG Updating 1 Bugs (starting id: ...)
18 ...
19
20=== modified file 'lib/lp/bugs/scripts/bugheat.py'
21--- lib/lp/bugs/scripts/bugheat.py 2010-01-26 20:31:13 +0000
22+++ lib/lp/bugs/scripts/bugheat.py 2010-02-27 14:48:24 +0000
23@@ -8,6 +8,7 @@
24 'BugHeatCalculator',
25 ]
26
27+from datetime import datetime
28
29 from zope.component import getUtility
30 from zope.interface import implements
31@@ -15,6 +16,8 @@
32 from canonical.launchpad.interfaces.looptuner import ITunableLoop
33 from canonical.launchpad.utilities.looptuner import DBLoopTuner
34
35+from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
36+
37
38 class BugHeatConstants:
39
40@@ -63,8 +66,16 @@
41 len(direct_subscribers) + len(subscribers_from_dupes))
42 return subscriber_count * BugHeatConstants.SUBSCRIBER
43
44+ def _bugIsComplete(self):
45+ """Are all the tasks for this bug resolved?"""
46+ return all([(task.status in RESOLVED_BUGTASK_STATUSES)
47+ for task in self.bug.bugtasks])
48+
49 def getBugHeat(self):
50 """Return the total heat for the current bug."""
51+ if self._bugIsComplete():
52+ return 0
53+
54 total_heat = sum([
55 self._getHeatFromAffectedUsers(),
56 self._getHeatFromDuplicates(),
57@@ -73,5 +84,12 @@
58 self._getHeatFromSubscribers(),
59 ])
60
61+ # Bugs decay over time. Every month the bug isn't touched its heat
62+ # decreases by 10%.
63+ months = (
64+ datetime.utcnow() -
65+ self.bug.date_last_updated.replace(tzinfo=None)).days / 30
66+ total_heat = int(total_heat * (0.9 ** months))
67+
68 return total_heat
69
70
71=== modified file 'lib/lp/bugs/scripts/tests/test_bugheat.py'
72--- lib/lp/bugs/scripts/tests/test_bugheat.py 2010-01-12 16:41:23 +0000
73+++ lib/lp/bugs/scripts/tests/test_bugheat.py 2010-02-27 14:48:24 +0000
74@@ -1,4 +1,3 @@
75-
76 # Copyright 2010 Canonical Ltd. This software is licensed under the
77 # GNU Affero General Public License version 3 (see the file LICENSE).
78
79@@ -8,12 +7,14 @@
80
81 import unittest
82
83+from datetime import datetime, timedelta
84+
85 from canonical.testing import LaunchpadZopelessLayer
86
87+from lp.bugs.interfaces.bugtask import BugTaskStatus
88 from lp.bugs.scripts.bugheat import BugHeatCalculator, BugHeatConstants
89 from lp.testing import TestCaseWithFactory
90
91-
92 class TestBugHeatCalculator(TestCaseWithFactory):
93 """Tests for the BugHeatCalculator class."""
94
95@@ -177,6 +178,35 @@
96 "Expected bug heat did not match actual bug heat. "
97 "Expected %s, got %s" % (expected_heat, actual_heat))
98
99+ def test_getBugHeat_complete_bugs(self):
100+ # Bug which are in a resolved status don't have heat at all.
101+ complete_bug = self.factory.makeBug()
102+ heat = BugHeatCalculator(complete_bug).getBugHeat()
103+ self.assertNotEqual(
104+ 0, heat,
105+ "Expected bug heat did not match actual bug heat. "
106+ "Expected a positive value, got 0")
107+ complete_bug.bugtasks[0].transitionToStatus(
108+ BugTaskStatus.INVALID, complete_bug.owner)
109+ heat = BugHeatCalculator(complete_bug).getBugHeat()
110+ self.assertEqual(
111+ 0, heat,
112+ "Expected bug heat did not match actual bug heat. "
113+ "Expected %s, got %s" % (0, heat))
114+
115+ def test_getBugHeat_decay(self):
116+ # Every month, a bug that wasn't touched has its heat reduced by 10%.
117+ aging_bug = self.factory.makeBug()
118+ fresh_heat = BugHeatCalculator(aging_bug).getBugHeat()
119+ aging_bug.date_last_updated = (
120+ aging_bug.date_last_updated - timedelta(days=32))
121+ expected = int(fresh_heat * 0.9)
122+ heat = BugHeatCalculator(aging_bug).getBugHeat()
123+ self.assertEqual(
124+ expected, heat,
125+ "Expected bug heat did not match actual bug heat. "
126+ "Expected %s, got %s" % (expected, heat))
127+
128
129 def test_suite():
130 return unittest.TestLoader().loadTestsFromName(__name__)