~launchpad-pqm/launchpad/devel

11235.5.4 by Curtis Hovey
hush lint.
1
Questions Expiration
2
====================
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
3
3881.1.1 by Francis J. Lacoste
Renamed reference to support tracker in doctests.
4
It is not productive to have questions lying around forever in
5
the Answer Tracker. That's why we have a script which runs daily to
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
6
expire old questions on which there was no activity for the past two
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
7
weeks.
8
3691.197.52 by Francis J. Lacoste
Typos, grammar and other cleanup
9
The expiration period is set using the
3691.398.24 by Francis J. Lacoste
Rename config section.
10
config.answertracker.days_before_expiration configuration variable. It
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
11
defaults to 15 days.
12
13
    >>> from canonical.config import config
3691.398.24 by Francis J. Lacoste
Rename config section.
14
    >>> config.answertracker.days_before_expiration
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
15
    15
16
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
17
Only questions in the OPEN or NEEDSINFO state which aren't assigned to
3691.264.1 by Francis J. Lacoste
Do not expire old assigned tickets.
18
somebody are subject to expiration.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
19
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
20
    # Sanity check in case somebody modifies the question sampledata and
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
21
    # forget to update this script.
12915.5.1 by Curtis Hovey
Rename questionenums.py to the new standard or enums.py
22
    >>> from lp.answers.enums import QuestionStatus
8971.22.2 by Guilherme Salgado
Fix a bunch of tests to import registry model classes from lp.registry.model instead of from canonical.launchpad.database
23
    >>> from lp.answers.model.question import Question
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
24
    >>> Question.select('status IN (%i,%i)' % (
11235.5.4 by Curtis Hovey
hush lint.
25
    ...     QuestionStatus.OPEN.value,
26
    ...     QuestionStatus.NEEDSINFO.value)).count()
4443.1.6 by Curtis Hovey
Added an arabic question and a test that it displays right-to-left.
27
    9
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
28
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
29
    # By default, all open and needs info question should expire. Make
30
    # sure that no new questions were recently added and will make this
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
31
    # test fails in the future.
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
32
    >>> Question.select(
3691.197.72 by Francis J. Lacoste
Make sure future sample data changes do not make expiration test fails with a delay
33
    ...     "datelastresponse >= current_timestamp - interval '15 days' OR "
34
    ...    "datelastquery >= current_timestamp - interval '15 days'").count()
35
    0
36
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
37
    # We need to massage sample data a little. Since all expiration
38
    # candidates in sample data would expire, do a little activity on
39
    # some of these.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
40
    >>> from datetime import datetime, timedelta
41
    >>> from pytz import UTC
42
    >>> now = datetime.now(UTC)
43
    >>> two_weeks_ago = now - timedelta(days=14)
44
    >>> a_month_ago = now - timedelta(days=31)
10409.5.38 by Curtis Hovey
Removed glob imports for answers.
45
    >>> from canonical.launchpad.webapp.interfaces import ILaunchBag
11716.1.12 by Curtis Hovey
Sorted imports in doctests.
46
    >>> from lp.answers.interfaces.questioncollection import IQuestionSet
10409.5.38 by Curtis Hovey
Removed glob imports for answers.
47
    >>> from lp.registry.interfaces.person import IPersonSet
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
48
    >>> login('no-priv@canonical.com')
49
    >>> no_priv = getUtility(ILaunchBag).user
50
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
51
    >>> questionset = getUtility(IQuestionSet)
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
52
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
53
    # An old question in NEEDSINFO the state.
54
    >>> old_needs_info_question = questionset.get(7)
55
    >>> print old_needs_info_question.status.title
3691.264.1 by Francis J. Lacoste
Do not expire old assigned tickets.
56
    Needs information
57
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
58
    # An open question assigned to somebody.
3691.264.1 by Francis J. Lacoste
Do not expire old assigned tickets.
59
    >>> login('foo.bar@canonical.com')
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
60
    >>> old_assigned_open_question = questionset.get(1)
61
    >>> old_assigned_open_question.assignee = getUtility(ILaunchBag).user
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
62
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
63
    # This one got an update from its owner recently.
3691.197.109 by Francis J. Lacoste
- Add launchpad.Owner permission.
64
    >>> login('test@canonical.com')
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
65
    >>> recent_open_question = questionset.get(2)
66
    >>> recent_open_question.giveInfo(
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
67
    ...     'SVG works better now, but is still broken')
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
68
    <QuestionMessage...>
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
69
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
70
    # This one was put in the NEEDSINFO state recently.
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
71
    >>> recent_needsinfo_question = questionset.get(4)
3881.1.2 by Francis J. Lacoste
Fix spurious replace.
72
    >>> recent_needsinfo_question.requestInfo(
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
73
    ...     no_priv, 'What URL were you visiting?')
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
74
    <QuestionMessage...>
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
75
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
76
    # Old open questions.
77
    >>> old_open_question = questionset.get(5)
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
78
3881.1.1 by Francis J. Lacoste
Renamed reference to support tracker in doctests.
79
    # Subscribe a team to that question, and a answer contact,
3691.261.1 by Francis J. Lacoste
Fix bug 74941 (Database permission exception in ticket expiration) by granting permission to personlanguage to the script.
80
    # to make sure that DB permissions are correct.
3691.69.12 by Francis J. Lacoste
Grant permission to teammembership for tickettracker user.
81
    >>> admin_team = getUtility(IPersonSet).getByName('admins')
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
82
    >>> old_open_question.subscribe(admin_team)
3691.398.19 by Francis J. Lacoste
Rename most remaining classes (views, database, authorizations, notifications, scripts.)
83
    <QuestionSubscription...>
3691.261.1 by Francis J. Lacoste
Fix bug 74941 (Database permission exception in ticket expiration) by granting permission to personlanguage to the script.
84
    >>> salgado = getUtility(IPersonSet).getByName('salgado')
12959.4.21 by Curtis Hovey
Alway pass the subscribed_by argument to addAnswerContact.
85
    >>> old_open_question.target.addAnswerContact(salgado, salgado)
3691.261.1 by Francis J. Lacoste
Fix bug 74941 (Database permission exception in ticket expiration) by granting permission to personlanguage to the script.
86
    True
3691.69.12 by Francis J. Lacoste
Grant permission to teammembership for tickettracker user.
87
5686.1.1 by Francis J. Lacoste
Fix missing security declaration for FAQ.
88
    # Link it to a FAQ item for the same reason. We are setting the
89
    # attribute directly, because using the linkFAQ API would update
90
    # the last updates date of the question and remove it from the expiration
91
    # set.
92
    >>> from zope.security.proxy import removeSecurityProxy
93
    >>> login('foo.bar@canonical.com')
94
    >>> faq = old_open_question.target.newFAQ(
95
    ...     salgado, 'Why everyone think this is weird.',
96
    ...     "That's an easy one. It's because it is!")
97
    >>> removeSecurityProxy(old_open_question).faq = faq
98
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
99
    # A question linked to an non-Invalid bug is not expirable.
10409.5.38 by Curtis Hovey
Removed glob imports for answers.
100
    >>> from lp.bugs.interfaces.bug import IBugSet
101
    >>> from lp.bugs.interfaces.bugtask import BugTaskStatus
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
102
    >>> fixed_bug = getUtility(IBugSet).get(9)
103
    >>> bugtasks = fixed_bug.bugtasks
104
    >>> bugtasks[1].transitionToStatus(BugTaskStatus.INVALID, no_priv)
105
    >>> [bugtask.status.title for bugtask in bugtasks]
106
    ['Unknown', 'Invalid']
107
    >>> bug_link_question = questionset.get(11)
4813.9.2 by Curtis Hovey
Changes pre review.
108
    >>> bug_link_question.linkBug(fixed_bug)
109
    <QuestionBug at ...>
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
110
111
    # A question linked to an Invalid bug; it is expirable.
112
    >>> invalid_bug = getUtility(IBugSet).get(10)
113
    >>> bugtask = invalid_bug.bugtasks[0]
114
    >>> bugtask.transitionToStatus(BugTaskStatus.INVALID, no_priv)
115
    >>> bugtask.status.title
116
    'Invalid'
117
    >>> invalid_bug_question = questionset.get(12)
4813.9.2 by Curtis Hovey
Changes pre review.
118
    >>> invalid_bug_question.linkBug(invalid_bug)
119
    <QuestionBug at ...>
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
120
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
121
    # Commit the current transaction because the script will run in
122
    # another transaction and thus it won't see the changes done on this
123
    # test unless we commit.
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
124
    # XXX flacoste 2006-10-03 bug=3989: Unecessary flush_database_updates
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
125
    # required.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
126
    >>> from canonical.database.sqlbase import flush_database_updates
127
    >>> flush_database_updates()
128
    >>> import transaction
129
    >>> transaction.commit()
130
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
131
    # Run the script.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
132
    >>> import subprocess
133
    >>> process = subprocess.Popen(
3691.398.13 by Francis J. Lacoste
Rename answer tracker py and ZCML files.
134
    ...     'cronscripts/expire-questions.py', shell=True,
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
135
    ...     stdin=subprocess.PIPE, stdout=subprocess.PIPE,
136
    ...     stderr=subprocess.PIPE)
137
    >>> (out, err) = process.communicate()
138
    >>> print err
7675.624.73 by Tim Penhey
Some lock files, some job runs.
139
    INFO    Creating lockfile: /var/lock/launchpad-expire-questions.lock
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
140
    INFO    Expiring OPEN and NEEDSINFO questions without activity for the
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
141
            last 15 days.
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
142
    INFO    Found 5 questions to expire.
143
    INFO    Expired 5 questions.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
144
    INFO    Finished expiration run.
145
    <BLANKLINE>
146
    >>> print out
147
    <BLANKLINE>
148
    >>> process.returncode
149
    0
150
3691.69.11 by Francis J. Lacoste
Convert () comments to regular python code comments.
151
    # Now we flush the caches, so that the above defined objects gets
152
    # their content from the modified DB.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
153
    >>> from canonical.database.sqlbase import flush_database_caches
154
    >>> flush_database_caches()
155
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
156
The status of the OPEN and NEEDSINFO questions that had recent activity
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
157
wasn't modified by the script:
158
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
159
    >>> print recent_open_question.status.title
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
160
    Open
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
161
    >>> print recent_needsinfo_question.status.title
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
162
    Needs information
163
3691.264.1 by Francis J. Lacoste
Do not expire old assigned tickets.
164
Neither the old one which was assigned to Foo Bar:
165
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
166
    >>> print old_assigned_open_question.status.title
3691.264.1 by Francis J. Lacoste
Do not expire old assigned tickets.
167
    Open
168
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
169
The old question with non-Invalid bug link is still Open status:
170
171
    >>> print bug_link_question.status.title
172
    Open
173
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
174
But the other ones status was changed to 'Expired':
175
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
176
    >>> print old_needs_info_question.status.title
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
177
    Expired
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
178
    >>> print old_open_question.status.title
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
179
    Expired
4813.9.1 by Curtis Hovey
Added rule to never expire questions that are linked to valid bugs
180
    >>> print invalid_bug_question.status.title
181
    Expired
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
182
183
The message explaining the reason for the expiration was posted by the
4813.9.5 by Curtis Hovey
Reverted expiredQuestions query to use BugTask.status IS NULL because
184
Launchpad Janitor celebrity:
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
185
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
186
    >>> expiration_message = old_needs_info_question.messages[-1]
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
187
    >>> print expiration_message.action.name
188
    EXPIRE
3691.197.55 by Francis J. Lacoste
Rename newstatus to new_status as per new naming convention. Renamed foreign key to ticket__answer__fk as requested by stub.
189
    >>> print expiration_message.new_status.title
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
190
    Expired
191
    >>> print expiration_message.owner.name
4621.5.26 by Curtis Hovey
Renamed launchpad-janitor to janitor per review.
192
    janitor
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
193
194
    >>> print expiration_message.text_contents
3691.398.21 by Francis J. Lacoste
Rename all attributes and variables.
195
    This question was expired because it remained in the
3691.197.96 by Francis J. Lacoste
- Comment after every expiration to workaround bug #29744.
196
    'Needs information' state without activity for the last 15 days.
3691.197.49 by Francis J. Lacoste
Add expire-tickets.py script
197
198