114
114
BugTaskSearchParams,
116
116
BugTaskStatusSearch,
117
DB_INCOMPLETE_BUGTASK_STATUSES,
118
DB_UNRESOLVED_BUGTASK_STATUSES,
120
122
IllegalRelatedBugTasksParams,
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
346
# If this bugtask has no bug yet, then we are probably being
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:
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)
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)
534
if self._status in DB_INCOMPLETE_BUGTASK_STATUSES:
535
return BugTaskStatus.INCOMPLETE
532
540
"""See `IBugTask`."""
533
541
return 'Bug #%s in %s: "%s"' % (
609
616
Note that this should be kept in sync with the completeness_clause
612
return self.status in RESOLVED_BUGTASK_STATUSES
619
return self._status in RESOLVED_BUGTASK_STATUSES
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,))
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
895
new_status = BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE
897
if self._status == new_status:
882
898
# No change in the status, so nothing to do.
885
901
old_status = self.status
886
self.status = new_status
902
self._status = new_status
888
904
if new_status == BugTaskStatus.UNKNOWN:
889
905
# Ensure that all status-related dates are cleared,
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
963
978
self.date_incomplete = None
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
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
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
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)
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
1725
1746
if with_response or without_response:
1727
'(BugTask.status = %s) ' %
1728
sqlvalues(BugTaskStatus.INCOMPLETE))
1729
1747
if with_response:
1730
status_clause += ("""
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)
1753
Bug.date_last_message)))
1755
BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
1756
BugTaskStatus.INCOMPLETE)
1735
1757
elif without_response:
1736
status_clause += ("""
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)
1742
assert with_response != without_response
1743
return status_clause
1763
Bug.date_last_message)))
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))
1745
1776
return '(BugTask.status = %s)' % sqlvalues(status)
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)]
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)))),
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)]
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]
2719
2752
validate_new_target(bug, target)
2721
2754
target_key = bug_target_to_key(target)
2723
2755
if not bug.private and bug.security_related:
2724
2756
product = target_key['product']
2725
2757
distribution = target_key['distribution']
2738
2770
create_params = non_target_create_params.copy()
2739
2771
create_params.update(target_key)
2740
2772
bugtask = BugTask(**create_params)
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'],
2888
2921
statuses_not_preventing_expiration = [
2889
2922
BugTaskStatus.INVALID, BugTaskStatus.INCOMPLETE,
2923
BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
2890
2924
BugTaskStatus.WONTFIX]
2892
2926
unexpirable_status_list = [
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))
3070
BugTask._status.is_in(DB_UNRESOLVED_BUGTASK_STATUSES),
3071
Bug.duplicateof == None,
3072
BugTask.productID.is_in(product_ids))
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,