The branch should be merged into db-devel. Here's the correct diff: === modified file 'database/schema/security.cfg' --- database/schema/security.cfg 2010-02-26 11:31:12 +0000 +++ database/schema/security.cfg 2010-03-01 17:35:00 +0000 @@ -1494,12 +1494,14 @@ public.archive = SELECT public.archivearch = SELECT public.component = SELECT -public.distribution = SELECT +public.distribution = SELECT, UPDATE +public.distributionsourcepackage = SELECT, INSERT, UPDATE public.distrocomponentuploader = SELECT +public.distroseries = SELECT public.archivepermission = SELECT public.distroseries = SELECT -public.project = SELECT -public.product = SELECT +public.project = SELECT, UPDATE +public.product = SELECT, UPDATE public.productseries = SELECT public.packagebugsupervisor = SELECT public.sourcepackagename = SELECT @@ -1566,7 +1568,6 @@ public.codereviewmessage = SELECT, INSERT public.codereviewvote = SELECT, INSERT, UPDATE public.diff = SELECT, INSERT, UPDATE -public.distribution = SELECT public.distroseries = SELECT public.job = SELECT, INSERT, UPDATE public.mergedirectivejob = SELECT, INSERT === modified file 'lib/lp/bugs/browser/bugtask.py' --- lib/lp/bugs/browser/bugtask.py 2010-02-28 10:00:45 +0000 +++ lib/lp/bugs/browser/bugtask.py 2010-03-01 11:27:18 +0000 @@ -1090,6 +1090,8 @@ """Calculate the number of heat 'flames' to display.""" heat = float(heat) max_bug_heat = float(max_bug_heat) + if max_bug_heat == 0: + return 0 if heat / max_bug_heat < 0.33333: return 0 if heat / max_bug_heat < 0.66666 or max_bug_heat < 2: === modified file 'lib/lp/bugs/doc/bug-heat.txt' --- lib/lp/bugs/doc/bug-heat.txt 2010-02-27 14:45:01 +0000 +++ lib/lp/bugs/doc/bug-heat.txt 2010-03-01 11:27:18 +0000 @@ -60,3 +60,59 @@ >>> bug_1.heat > 0 True + + +Caculating the maximum heat for a target +---------------------------------------- + +When we update the heat value for a bug, the maximum heat value for the targets +for all of its tasks is calculated and cached. + + >>> product = factory.makeProduct() + >>> bug = factory.makeBug(product=product) + >>> print product.max_bug_heat + None + >>> bug.setHeat(123) + >>> print product.max_bug_heat + 123 + +The maximum heat for a project is the value for tasks on all its products. + + >>> project = factory.makeProject() + >>> product.project = project + >>> bug.setHeat(123) + >>> print project.max_bug_heat + 123 + +A DistributionSourcePackage has its own maximum heat. + + >>> dsp = factory.makeDistributionSourcePackage() + >>> dsp_task = bug.addTask(bug.owner, dsp) + >>> print dsp.max_bug_heat + 123 + +Transitioning from one target to another, calculates the value for the new +target. + + >>> another_product = factory.makeProduct() + >>> bug.bugtasks[0].transitionToTarget(another_product) + >>> print another_product.max_bug_heat + 123 + +ProductSeries and DistroSeries simply delegate to their corresponding Product +or Distribution. + + >>> product_series = factory.makeProductSeries() + >>> ps_task = bug.addTask(bug.owner, product_series) + >>> print product_series.max_bug_heat + 123 + >>> print product_series.product.max_bug_heat + 123 + + >>> distro_series = factory.makeDistroSeries() + >>> ds_task = bug.addTask(bug.owner, distro_series) + >>> print distro_series.max_bug_heat + 123 + >>> print distro_series.distribution.max_bug_heat + 123 + === modified file 'lib/lp/bugs/interfaces/bugtarget.py' --- lib/lp/bugs/interfaces/bugtarget.py 2010-02-27 21:18:10 +0000 +++ lib/lp/bugs/interfaces/bugtarget.py 2010-03-01 11:27:18 +0000 @@ -207,6 +207,10 @@ def setMaxBugHeat(heat): """Set the max_bug_heat for this context.""" + def recalculateMaxBugHeat(): + """Recalculate and set the max_bug_heat for this context.""" + + class IBugTarget(IHasBugs): """An entity on which a bug can be reported. === modified file 'lib/lp/bugs/model/bug.py' --- lib/lp/bugs/model/bug.py 2010-02-26 05:13:11 +0000 +++ lib/lp/bugs/model/bug.py 2010-02-28 13:33:34 +0000 @@ -100,6 +100,7 @@ from lp.registry.interfaces.person import validate_public_person from lp.registry.interfaces.product import IProduct from lp.registry.interfaces.productseries import IProductSeries +from lp.registry.interfaces.projectgroup import IProjectGroup from lp.registry.interfaces.sourcepackage import ISourcePackage from lp.registry.model.mentoringoffer import MentoringOffer from lp.registry.model.person import Person, ValidPersonCache @@ -876,6 +877,10 @@ distroseries=distro_series, sourcepackagename=source_package_name) + # When a new task is added the bug's heat becomes relevant to the + # target's max_bug_heat. + target.recalculateMaxBugHeat() + return new_task def addWatch(self, bugtracker, remotebug, owner): @@ -1529,6 +1534,8 @@ def setHeat(self, heat): """See `IBug`.""" self.heat = heat + for task in self.bugtasks: + task.target.recalculateMaxBugHeat() class BugSet: === modified file 'lib/lp/bugs/model/bugtarget.py' --- lib/lp/bugs/model/bugtarget.py 2010-02-26 05:45:47 +0000 +++ lib/lp/bugs/model/bugtarget.py 2010-03-01 17:25:50 +0000 @@ -25,10 +25,13 @@ from canonical.launchpad.webapp.interfaces import ILaunchBag from lp.bugs.interfaces.bugtarget import IOfficialBugTag from lp.registry.interfaces.distribution import IDistribution +from lp.registry.interfaces.distroseries import IDistroSeries from lp.registry.interfaces.distributionsourcepackage import ( IDistributionSourcePackage) from lp.registry.interfaces.product import IProduct +from lp.registry.interfaces.productseries import IProductSeries from lp.registry.interfaces.projectgroup import IProjectGroup +from lp.registry.interfaces.sourcepackage import ISourcePackage from lp.bugs.interfaces.bugtask import ( BugTagsSearchCombinator, BugTaskImportance, BugTaskSearchParams, BugTaskStatus, RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES) @@ -170,6 +173,59 @@ else: raise NotImplementedError + def recalculateMaxBugHeat(self): + """See `IHasBugs`.""" + if IProductSeries.providedBy(self): + return self.product.recalculateMaxBugHeat() + if IDistroSeries.providedBy(self): + return self.distribution.recalculateMaxBugHeat() + if ISourcePackage.providedBy(self): + # Should only happen for nominations, so we can safely skip + # recalculating max_heat. + return + + if IDistribution.providedBy(self): + sql = """SELECT Bug.heat + FROM Bug, Bugtask, DistroSeries + WHERE Bugtask.bug = Bug.id + AND Bugtask.distroseries = DistroSeries.id + OR Bugtask.distribution = DistroSeries.distribution + AND Bugtask.distribution = %s + ORDER BY Bug.heat DESC LIMIT 1""" % sqlvalues(self) + elif IProduct.providedBy(self): + sql = """SELECT Bug.heat + FROM Bug, Bugtask, ProductSeries + WHERE Bugtask.bug = Bug.id + AND Bugtask.productseries = ProductSeries.id + OR Bugtask.product = ProductSeries.product + AND Bugtask.product = %s + ORDER BY Bug.heat DESC LIMIT 1""" % sqlvalues(self) + elif IProjectGroup.providedBy(self): + sql = """SELECT MAX(heat) + FROM Bug, Bugtask, Product + WHERE Bugtask.bug = Bug.id AND + Bugtask.product = Product.id AND + Product.project = %s""" % sqlvalues(self) + elif IDistributionSourcePackage.providedBy(self): + sql = """SELECT MAX(heat) + FROM Bug, Bugtask + WHERE Bugtask.bug = Bug.id AND + Bugtask.distribution = %s AND + Bugtask.sourcepackagename = %s""" % sqlvalues( + self.distribution, self.sourcepackagename) + else: + raise NotImplementedError + + cur = cursor() + cur.execute(sql) + self.setMaxBugHeat(cur.fetchone()[0]) + + # If the product is part of a project group we calculate the maximum + # heat for the project group too. + if IProduct.providedBy(self) and self.project is not None: + self.project.recalculateMaxBugHeat() + + def getBugCounts(self, user, statuses=None): """See `IHasBugs`.""" if statuses is None: === modified file 'lib/lp/bugs/model/bugtask.py' --- lib/lp/bugs/model/bugtask.py 2010-02-27 20:20:03 +0000 +++ lib/lp/bugs/model/bugtask.py 2010-03-01 11:27:18 +0000 @@ -969,6 +969,9 @@ enforced implicitly by the code in lib/canonical/launchpad/browser/bugtask.py#BugTaskEditView. """ + + target_before_change = self.target + if (self.milestone is not None and self.milestone.target != target): # If the milestone for this bugtask is set, we @@ -993,6 +996,12 @@ "Distribution bug tasks may only be re-targeted " "to a package in the same distribution.") + # After the target has changed, we need to recalculate the maximum bug + # heat for the new and old targets. + if self.target != target_before_change: + target_before_change.recalculateMaxBugHeat() + self.target.recalculateMaxBugHeat() + def updateTargetNameCache(self, newtarget=None): """See `IBugTask`.""" if newtarget is None: === modified file 'lib/lp/registry/configure.zcml' --- lib/lp/registry/configure.zcml 2010-02-27 20:20:03 +0000 +++ lib/lp/registry/configure.zcml 2010-03-01 11:27:18 +0000 @@ -408,7 +408,8 @@ findRelatedArchives findRelatedArchivePublications userHasBugSubscriptions - max_bug_heat"/> + max_bug_heat + recalculateMaxBugHeat"/>