~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/model/bugtask.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-10-06 01:24:37 UTC
  • mfrom: (14039.1.12 bug-759467)
  • Revision ID: launchpad@pqm.canonical.com-20111006012437-x4xn9cohnyp5ztlx
[r=gmb][bug=759467] [r=gmb][bug=759467] Store
 INCOMPLETE_WITH_RESPONSE and INCOMPLETE_WITHOUT_RESPONSE for BugTask status
 to make queries more efficient.

Show diffs side-by-side

added added

removed removed

Lines of Context:
114
114
    BugTaskSearchParams,
115
115
    BugTaskStatus,
116
116
    BugTaskStatusSearch,
 
117
    DB_INCOMPLETE_BUGTASK_STATUSES,
 
118
    DB_UNRESOLVED_BUGTASK_STATUSES,
117
119
    IBugTask,
118
120
    IBugTaskDelta,
119
121
    IBugTaskSet,
120
122
    IllegalRelatedBugTasksParams,
121
123
    IllegalTarget,
122
124
    RESOLVED_BUGTASK_STATUSES,
123
 
    UNRESOLVED_BUGTASK_STATUSES,
124
125
    UserCannotEditBugTaskAssignee,
125
126
    UserCannotEditBugTaskImportance,
126
127
    UserCannotEditBugTaskMilestone,
343
344
    if isinstance(value, PassthroughValue):
344
345
        return value.value
345
346
 
346
 
    # If this bugtask has no bug yet, then we are probably being
347
 
    # instantiated.
348
 
    if self.bug is None:
 
347
    # Check to see if the object is being instantiated.  This test is specific
 
348
    # to SQLBase.  Checking for specific attributes (like self.bug) is
 
349
    # insufficient and fragile.
 
350
    if self._SO_creating:
349
351
        return value
350
352
 
351
353
    # If this is a conjoined slave then call setattr on the master.
446
448
    _defaultOrder = ['distribution', 'product', 'productseries',
447
449
                     'distroseries', 'milestone', 'sourcepackagename']
448
450
    _CONJOINED_ATTRIBUTES = (
449
 
        "status", "importance", "assigneeID", "milestoneID",
 
451
        "_status", "importance", "assigneeID", "milestoneID",
450
452
        "date_assigned", "date_confirmed", "date_inprogress",
451
453
        "date_closed", "date_incomplete", "date_left_new",
452
454
        "date_triaged", "date_fix_committed", "date_fix_released",
475
477
        dbName='milestone', foreignKey='Milestone',
476
478
        notNull=False, default=None,
477
479
        storm_validator=validate_conjoined_attribute)
478
 
    status = EnumCol(
 
480
    _status = EnumCol(
479
481
        dbName='status', notNull=True,
480
 
        schema=BugTaskStatus,
 
482
        schema=(BugTaskStatus, BugTaskStatusSearch),
481
483
        default=BugTaskStatus.NEW,
482
484
        storm_validator=validate_status)
483
485
    importance = EnumCol(
528
530
        dbName='targetnamecache', notNull=False, default=None)
529
531
 
530
532
    @property
 
533
    def status(self):
 
534
        if self._status in DB_INCOMPLETE_BUGTASK_STATUSES:
 
535
            return BugTaskStatus.INCOMPLETE
 
536
        return self._status
 
537
 
 
538
    @property
531
539
    def title(self):
532
540
        """See `IBugTask`."""
533
541
        return 'Bug #%s in %s: "%s"' % (
584
592
    @property
585
593
    def age(self):
586
594
        """See `IBugTask`."""
587
 
        UTC = pytz.timezone('UTC')
588
 
        now = datetime.datetime.now(UTC)
 
595
        now = datetime.datetime.now(pytz.UTC)
589
596
 
590
597
        return now - self.datecreated
591
598
 
609
616
        Note that this should be kept in sync with the completeness_clause
610
617
        above.
611
618
        """
612
 
        return self.status in RESOLVED_BUGTASK_STATUSES
 
619
        return self._status in RESOLVED_BUGTASK_STATUSES
613
620
 
614
621
    def findSimilarBugs(self, user, limit=10):
615
622
        """See `IBugTask`."""
878
885
                "Only Bug Supervisors may change status to %s." % (
879
886
                    new_status.title,))
880
887
 
881
 
        if self.status == new_status:
 
888
        if new_status == BugTaskStatus.INCOMPLETE:
 
889
            # We store INCOMPLETE as INCOMPLETE_WITHOUT_RESPONSE so that it
 
890
            # can be queried on efficiently.
 
891
            if (when is None or self.bug.date_last_message is None or
 
892
                when > self.bug.date_last_message):
 
893
                new_status = BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE
 
894
            else:
 
895
                new_status = BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE
 
896
 
 
897
        if self._status == new_status:
882
898
            # No change in the status, so nothing to do.
883
899
            return
884
900
 
885
901
        old_status = self.status
886
 
        self.status = new_status
 
902
        self._status = new_status
887
903
 
888
904
        if new_status == BugTaskStatus.UNKNOWN:
889
905
            # Ensure that all status-related dates are cleared,
901
917
            return
902
918
 
903
919
        if when is None:
904
 
            UTC = pytz.timezone('UTC')
905
 
            when = datetime.datetime.now(UTC)
 
920
            when = datetime.datetime.now(pytz.UTC)
906
921
 
907
922
        # Record the date of the particular kinds of transitions into
908
923
        # certain states.
957
972
        # Bugs can jump in and out of 'incomplete' status
958
973
        # and for just as long as they're marked incomplete
959
974
        # we keep a date_incomplete recorded for them.
960
 
        if new_status == BugTaskStatus.INCOMPLETE:
 
975
        if new_status in DB_INCOMPLETE_BUGTASK_STATUSES:
961
976
            self.date_incomplete = when
962
977
        else:
963
978
            self.date_incomplete = None
964
979
 
965
 
        if ((old_status in UNRESOLVED_BUGTASK_STATUSES) and
 
980
        if ((old_status in DB_UNRESOLVED_BUGTASK_STATUSES) and
966
981
            (new_status in RESOLVED_BUGTASK_STATUSES)):
967
982
            self.date_closed = when
968
983
 
969
984
        if ((old_status in RESOLVED_BUGTASK_STATUSES) and
970
 
            (new_status in UNRESOLVED_BUGTASK_STATUSES)):
 
985
            (new_status in DB_UNRESOLVED_BUGTASK_STATUSES)):
971
986
            self.date_left_closed = when
972
987
 
973
988
        # Ensure that we don't have dates recorded for state
975
990
        # workflow state. We want to ensure that, for example, a
976
991
        # bugtask that went New => Confirmed => New
977
992
        # has a dateconfirmed value of None.
978
 
        if new_status in UNRESOLVED_BUGTASK_STATUSES:
 
993
        if new_status in DB_UNRESOLVED_BUGTASK_STATUSES:
979
994
            self.date_closed = None
980
995
 
981
996
        if new_status < BugTaskStatus.CONFIRMED:
1591
1606
        """See `IBugTaskSet`."""
1592
1607
        return BugTaskSearchParams(
1593
1608
            user=getUtility(ILaunchBag).user,
1594
 
            status=any(*UNRESOLVED_BUGTASK_STATUSES),
 
1609
            status=any(*DB_UNRESOLVED_BUGTASK_STATUSES),
1595
1610
            omit_dupes=True)
1596
1611
 
1597
1612
    def get(self, task_id):
1718
1733
        elif zope_isinstance(status, not_equals):
1719
1734
            return '(NOT %s)' % self._buildStatusClause(status.value)
1720
1735
        elif zope_isinstance(status, BaseItem):
 
1736
            incomplete_response = (
 
1737
                status == BugTaskStatus.INCOMPLETE)
1721
1738
            with_response = (
1722
1739
                status == BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE)
1723
1740
            without_response = (
1724
1741
                status == BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE)
 
1742
            # TODO: bug 759467 tracks the migration of INCOMPLETE in the db to
 
1743
            # INCOMPLETE_WITH_RESPONSE and INCOMPLETE_WITHOUT_RESPONSE. When
 
1744
            # the migration is complete, we can convert status lookups to a
 
1745
            # simple IN clause.
1725
1746
            if with_response or without_response:
1726
 
                status_clause = (
1727
 
                    '(BugTask.status = %s) ' %
1728
 
                    sqlvalues(BugTaskStatus.INCOMPLETE))
1729
1747
                if with_response:
1730
 
                    status_clause += ("""
 
1748
                    return """(
 
1749
                        BugTask.status = %s OR
 
1750
                        (BugTask.status = %s
1731
1751
                        AND (Bug.date_last_message IS NOT NULL
1732
1752
                             AND BugTask.date_incomplete <=
1733
 
                                 Bug.date_last_message)
1734
 
                        """)
 
1753
                                 Bug.date_last_message)))
 
1754
                        """ % sqlvalues(
 
1755
                            BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
 
1756
                            BugTaskStatus.INCOMPLETE)
1735
1757
                elif without_response:
1736
 
                    status_clause += ("""
 
1758
                    return """(
 
1759
                        BugTask.status = %s OR
 
1760
                        (BugTask.status = %s
1737
1761
                        AND (Bug.date_last_message IS NULL
1738
1762
                             OR BugTask.date_incomplete >
1739
 
                                Bug.date_last_message)
1740
 
                        """)
1741
 
                else:
1742
 
                    assert with_response != without_response
1743
 
                return status_clause
 
1763
                                Bug.date_last_message)))
 
1764
                        """ % sqlvalues(
 
1765
                            BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
 
1766
                            BugTaskStatus.INCOMPLETE)
 
1767
                assert with_response != without_response
 
1768
            elif incomplete_response:
 
1769
                # search for any of INCOMPLETE (being migrated from),
 
1770
                # INCOMPLETE_WITH_RESPONSE or INCOMPLETE_WITHOUT_RESPONSE
 
1771
                return 'BugTask.status %s' % search_value_to_where_condition(
 
1772
                    any(BugTaskStatus.INCOMPLETE,
 
1773
                        BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
 
1774
                        BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE))
1744
1775
            else:
1745
1776
                return '(BugTask.status = %s)' % sqlvalues(status)
1746
1777
        else:
1777
1808
                And(ConjoinedMaster.bugID == BugTask.bugID,
1778
1809
                    BugTask.distributionID == milestone.distribution.id,
1779
1810
                    ConjoinedMaster.distroseriesID == current_series.id,
1780
 
                    Not(ConjoinedMaster.status.is_in(
 
1811
                    Not(ConjoinedMaster._status.is_in(
1781
1812
                            BugTask._NON_CONJOINED_STATUSES))))
1782
1813
            join_tables = [(ConjoinedMaster, join)]
1783
1814
        else:
1797
1828
                        And(ConjoinedMaster.bugID == BugTask.bugID,
1798
1829
                            ConjoinedMaster.productseriesID
1799
1830
                                == Product.development_focusID,
1800
 
                            Not(ConjoinedMaster.status.is_in(
 
1831
                            Not(ConjoinedMaster._status.is_in(
1801
1832
                                    BugTask._NON_CONJOINED_STATUSES)))),
1802
1833
                    ]
1803
1834
                # join.right is the table name.
1810
1841
                    And(ConjoinedMaster.bugID == BugTask.bugID,
1811
1842
                        BugTask.productID == milestone.product.id,
1812
1843
                        ConjoinedMaster.productseriesID == dev_focus_id,
1813
 
                        Not(ConjoinedMaster.status.is_in(
 
1844
                        Not(ConjoinedMaster._status.is_in(
1814
1845
                                BugTask._NON_CONJOINED_STATUSES))))
1815
1846
                join_tables = [(ConjoinedMaster, join)]
1816
1847
            else:
2302
2333
            statuses_for_open_tasks = [
2303
2334
                BugTaskStatus.NEW,
2304
2335
                BugTaskStatus.INCOMPLETE,
 
2336
                BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
 
2337
                BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
2305
2338
                BugTaskStatus.CONFIRMED,
2306
2339
                BugTaskStatus.INPROGRESS,
2307
2340
                BugTaskStatus.UNKNOWN]
2636
2669
        conditions = []
2637
2670
        # Open bug statuses
2638
2671
        conditions.append(
2639
 
            BugSummary.status.is_in(UNRESOLVED_BUGTASK_STATUSES))
 
2672
            BugSummary.status.is_in(DB_UNRESOLVED_BUGTASK_STATUSES))
2640
2673
        # BugSummary does not include duplicates so no need to exclude.
2641
2674
        context_conditions = []
2642
2675
        for context in contexts:
2719
2752
        validate_new_target(bug, target)
2720
2753
 
2721
2754
        target_key = bug_target_to_key(target)
2722
 
 
2723
2755
        if not bug.private and bug.security_related:
2724
2756
            product = target_key['product']
2725
2757
            distribution = target_key['distribution']
2730
2762
 
2731
2763
        non_target_create_params = dict(
2732
2764
            bug=bug,
2733
 
            status=status,
 
2765
            _status=status,
2734
2766
            importance=importance,
2735
2767
            assignee=assignee,
2736
2768
            owner=owner,
2738
2770
        create_params = non_target_create_params.copy()
2739
2771
        create_params.update(target_key)
2740
2772
        bugtask = BugTask(**create_params)
2741
 
 
2742
2773
        if target_key['distribution']:
2743
2774
            # Create tasks for accepted nominations if this is a source
2744
2775
            # package addition.
2862
2893
                """ + target_clause + """
2863
2894
                """ + bug_clause + """
2864
2895
                """ + bug_privacy_filter + """
2865
 
                    AND BugTask.status = %s
 
2896
                    AND BugTask.status in (%s, %s, %s)
2866
2897
                    AND BugTask.assignee IS NULL
2867
2898
                    AND BugTask.milestone IS NULL
2868
2899
                    AND Bug.duplicateof IS NULL
2869
2900
                    AND Bug.date_last_updated < CURRENT_TIMESTAMP
2870
2901
                        AT TIME ZONE 'UTC' - interval '%s days'
2871
2902
                    AND BugWatch.id IS NULL
2872
 
            )""" % sqlvalues(BugTaskStatus.INCOMPLETE, min_days_old)
 
2903
            )""" % sqlvalues(BugTaskStatus.INCOMPLETE,
 
2904
                BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
 
2905
                BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE, min_days_old)
2873
2906
        expirable_bugtasks = BugTask.select(
2874
2907
            query + unconfirmed_bug_condition,
2875
2908
            clauseTables=['Bug'],
2887
2920
        """
2888
2921
        statuses_not_preventing_expiration = [
2889
2922
            BugTaskStatus.INVALID, BugTaskStatus.INCOMPLETE,
 
2923
            BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
2890
2924
            BugTaskStatus.WONTFIX]
2891
2925
 
2892
2926
        unexpirable_status_list = [
3032
3066
            ]
3033
3067
 
3034
3068
        product_ids = [product.id for product in products]
3035
 
        conditions = And(BugTask.status.is_in(UNRESOLVED_BUGTASK_STATUSES),
3036
 
                         Bug.duplicateof == None,
3037
 
                         BugTask.productID.is_in(product_ids))
 
3069
        conditions = And(
 
3070
            BugTask._status.is_in(DB_UNRESOLVED_BUGTASK_STATUSES),
 
3071
            Bug.duplicateof == None,
 
3072
            BugTask.productID.is_in(product_ids))
3038
3073
 
3039
3074
        privacy_filter = get_bug_privacy_filter(user)
3040
3075
        if privacy_filter != '':
3060
3095
                # TODO: sort by their name?
3061
3096
                "assignee": BugTask.assigneeID,
3062
3097
                "targetname": BugTask.targetnamecache,
3063
 
                "status": BugTask.status,
 
3098
                "status": BugTask._status,
3064
3099
                "title": Bug.title,
3065
3100
                "milestone": BugTask.milestoneID,
3066
3101
                "dateassigned": BugTask.date_assigned,
3167
3202
 
3168
3203
        open_bugs_cond = (
3169
3204
            'BugTask.status %s' % search_value_to_where_condition(
3170
 
                any(*UNRESOLVED_BUGTASK_STATUSES)))
 
3205
                any(*DB_UNRESOLVED_BUGTASK_STATUSES)))
3171
3206
 
3172
3207
        sum_template = "SUM(CASE WHEN %s THEN 1 ELSE 0 END) AS %s"
3173
3208
        sums = [