22
22
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
23
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
24
# DEALINGS IN THE SOFTWARE.
25
from mazo.extensionlib import ExtensionType
26
from mazo.missionlib import CreateMissionJob, DoMissionJob, FormatDataJob, ModifyMissionJob
27
from mazo.cmdlib import Command
31
from mazo.extensionlib import ExtensionType
32
from mazo.missionlib import CreateMissionJob, DoMissionJob, FormatDataJob, ModifyMissionJob
33
from mazo.cmdlib import Command
40
38
from launchpadlib.launchpad import Launchpad
42
class EntryNotFound(Exception): pass
40
# -----------------------------------------------------------------------
42
# -----------------------------------------------------------------------
44
43
LP_VALIDATE_BUGTASK_STATUS=("New",
68
class DoLPQueryJob(DoMissionJob):
74
super(DoLPQueryJob, self).do_prejob()
78
system = os.getenv('LPSYSTEM') or 'production'
79
self.logger.info("Connecting Launchpad {} server".format(system))
80
cachedir = os.path.expanduser("~/.launchpadlib/cache")
81
self.lp = Launchpad.login_with('lp-cli', system, cachedir)
84
def get_lp_entry(self, entry_type, entry_id, strict=True):
85
if entry_type != 'people':
86
entry_type = entry_type+'s'
70
def download_bug_attachments(bug, output_dir):
71
# write title and desc
72
with open(os.path.join(output_dir, bug.title.encode('utf-8')), 'w') as descfd:
73
descfd.write(bug.description)
74
for attachment in bug.attachments:
75
filename = os.path.join(output_dir, attachment.title)
88
return getattr(self.lp, entry_type)[entry_id]
91
raise EntryNotFound('Can not find {0} in Launchpad {1}'.format(entry_id, entry_type))
95
class ExtensionGet(object):
96
__metaclass__ = ExtensionType
97
responsible_for = CreateMissionJob
100
def custom_arguments(self):
101
self.add_argument('entry_type',
103
help='sepecify entry type that search bugs in')
104
self.add_argument('entry_id',
106
help='sepcify entry id that search bugs in')
108
def maintask(self, args):
109
for entry_id in args.entry_id:
110
ret = self.get_lp_entry(args.entry_type, entry_id, False)
114
class ExtensionSearchbugtasks(object):
115
__metaclass__ = ExtensionType
116
responsible_for = CreateMissionJob
119
def custom_arguments(self):
120
self.add_argument('entry_type',
121
choices=('people','project'),
122
help='sepecify entry type that search bugs in')
123
self.add_argument('entry_id',
124
help='sepcify entry id that search bugs in')
125
self.add_argument('--tag', dest='tags', action='append',
126
help='sepecify a bug tag')
127
self.add_argument('--tags-combinator', dest='tags_combinator',
128
help='Search for any or all of the tags specified.',
129
choices=('Any', 'All'), default='Any')
130
self.add_argument('--status', dest='status',
131
choices=LP_VALIDATE_BUGTASK_STATUS,
133
help='sepecify bugtask status')
134
self.add_argument('--importance',
135
choices=LP_VALIDATE_BUGTASK_IMPORTANCE,
138
help='sepecify bugtask importance')
139
self.add_argument('--assignee', dest='assignee',
140
help='sepecify bug assignee')
141
self.add_argument('--search-text', dest='search_text',
143
self.add_argument('--milestone', dest='milestone', help='milestone name')
144
self.add_argument('--package', dest='package', help='source package name (only for project)', action='append')
147
if self.cmd_args.package and self.cmd_args.entry_type != 'project':
148
self.logger.error("package option is only for project entry")
150
def maintask(self, args):
151
entry = self.get_lp_entry(args.entry_type, args.entry_id)
152
kwargs = self.transform_args(args, ('entry_id', 'entry_type', 'package'))
153
if kwargs.get('assignee'):
154
kwargs['assignee'] = self.lp.people[kwargs['assignee']]
155
if kwargs.get('milestone'):
156
kwargs['milestone'] = entry.getMilestone(name=kwargs['milestone'])
158
if args.package and args.entry_type == 'project':
160
for pkgname in args.package:
161
srcpkg = entry.getSourcePackage(name=pkgname)
162
ret.extend([e for e in srcpkg.searchTasks(**kwargs)])
165
return entry.searchTasks(**kwargs)
77
data = attachment.data.open()
78
with open(filename, 'w') as f:
84
"Unable to download file {0}".format(attachment.title))
86
def m_extract_bugs(result):
87
#@FIXME: check entry type
88
if type(result).__name__ != 'generator':
89
return [bt.bug for bt in result]
168
93
# userEditSTring and EditableData codes is wrote by James Ferguson <james.ferguson@canonical.com>, modified by hychen
169
94
def userEditString(suffix = '',
191
116
return result.lstrip()
118
# -----------------------------------------------------------------------
119
# GUI Helper Functions
120
# -----------------------------------------------------------------------
121
__DLG = dialog.Dialog()
123
def ask_which_bugtasks(sel_msg, confirm_msg, origbugtasks):
124
return ask_which_entries(sel_msg, confirm_msg, origbugtasks,
125
tag=lambda bugtask: str(bugtask.bug.id),
126
transform_cb=lambda bugtask: (str(bugtask.bug.id), bugtask.bug.title, 1),
127
result_format_cb=lambda bugtask: u"{0}-{1}".format(bugtask.bug.id, bugtask.bug.title))
129
def ask_which_bugs(sel_msg, confirm_msg, origbugs):
130
return ask_which_entries(sel_msg, confirm_msg, origbugs,
131
tag=lambda bug: str(bug.id),
132
transform_cb=lambda bug: (str(bug.id), bug.title, 1),
133
result_format_cb=lambda bug: u"{0}-{1}".format(bug.id, bug.title))
135
def exit_if_user_cancelled(msg, lines):
136
if not ask_confirmation(msg, lines):
139
def ask_which_entries(selection_msg, confirmation_msg, pooldata,
140
tag=None, transform_cb=None, result_format_cb=None):
141
selected_tags = ask_selection(selection_msg, pooldata, transform_cb)
142
if not selected_tags:
143
print u"Canceld because no selected bug"
147
selected_data = filter(lambda e: tag(e) in selected_tags, pooldata)
149
selected_data = filter(lambda e: e in selected_tags, pooldata)
151
# if selected data is empty means logic error
154
# prepare confirmation message
155
confirmation_msg += '\n'
157
confirmation_msg += '\n'.join(map(result_format_cb, selected_data))
159
confirmation_msg += '\n'.join(selected_data)
160
confirmation_msg += '\n'
161
exit_if_user_cancelled(confirmation_msg, len(selected_data))
164
def ask_confirmation(msg, lines=40):
165
return __DLG.yesno(msg, 40+lines*2, 74, defaultno=True) == 0
167
def ask_selection(msg, choices, transform_cb=None):
169
choices = map(transform_cb, choices)
170
count_choices = len(choices)
171
(code, tags) = __DLG.checklist(msg, 20+count_choices*2, 74,
172
10+count_choices*2, choices)
178
# -----------------------------------------------------------------------
180
# -----------------------------------------------------------------------
181
class EntryNotFound(Exception):
184
# -----------------------------------------------------------------------
186
# -----------------------------------------------------------------------
193
187
class EditableData():
194
188
"""Class to manage editable data of some text and some NVPs. Set
195
189
baseText and namedValues, call userEdit, then examine them again."""
247
# -----------------------------------------------------------------------
249
# -----------------------------------------------------------------------
251
## \breif A DoMissionJob to connect Launchpad before fetching data
253
class DoLPQueryJob(DoMissionJob):
259
super(DoLPQueryJob, self).do_prejob()
261
def connect_lp(self):
263
system = os.getenv('LPSYSTEM') or 'production'
264
self.logger.info("Connecting Launchpad {} server".format(system))
265
cachedir = os.path.expanduser("~/.launchpadlib/cache")
266
self.lp = Launchpad.login_with('lp-cli', system, cachedir)
269
def get_lp_entry(self, entry_type, entry_id, strict=True):
270
if entry_type != 'people':
271
entry_type = entry_type+'s'
273
return getattr(self.lp, entry_type)[entry_id]
276
raise EntryNotFound('Can not find {0} in Launchpad {1}'.format(entry_id, entry_type))
280
# -----------------------------------------------------------------------
282
# -----------------------------------------------------------------------
284
## \breif A Extension to create a mission message that maintask is to get
285
# a entry from Launchpad.
288
# STDOUT: Encoded MessionMessage String
289
class ExtensionGet(object):
290
__metaclass__ = ExtensionType
291
responsible_for = CreateMissionJob
294
def custom_arguments(self):
295
self.add_argument('entry_type',
297
help='sepecify entry type that search bugs in')
298
self.add_argument('entry_id',
300
help='sepcify entry id that search bugs in')
302
def maintask(self, args):
303
for entry_id in args.entry_id:
304
ret = self.get_lp_entry(args.entry_type, entry_id, False)
308
## \breif A Extension to create a mission message that maintask is to
309
# search bugtasks in a entry from Launchpad
312
# STDOUT: Encoded MessionMessage String
313
class ExtensionSearchbugtasks(object):
314
__metaclass__ = ExtensionType
315
responsible_for = CreateMissionJob
318
def custom_arguments(self):
319
self.add_argument('entry_type',
320
choices=('people','project'),
321
help='sepecify entry type that search bugs in')
322
self.add_argument('entry_id',
323
help='sepcify entry id that search bugs in')
324
self.add_argument('--tag', dest='tags', action='append',
325
help='sepecify a bug tag')
326
self.add_argument('--tags-combinator', dest='tags_combinator',
327
help='Search for any or all of the tags specified.',
328
choices=('Any', 'All'), default='Any')
329
self.add_argument('--status', dest='status',
330
choices=LP_VALIDATE_BUGTASK_STATUS,
332
help='sepecify bugtask status')
333
self.add_argument('--importance',
334
choices=LP_VALIDATE_BUGTASK_IMPORTANCE,
337
help='sepecify bugtask importance')
338
self.add_argument('--assignee', dest='assignee',
339
help='sepecify bug assignee')
340
self.add_argument('--search-text', dest='search_text',
342
self.add_argument('--milestone', dest='milestone', help='milestone name')
343
self.add_argument('--package', dest='package', help='source package name (only for project)', action='append')
346
if self.cmd_args.package and self.cmd_args.entry_type != 'project':
347
self.logger.error("package option is only for project entry")
349
def maintask(self, args):
350
entry = self.get_lp_entry(args.entry_type, args.entry_id)
351
kwargs = self.transform_args(args, ('entry_id', 'entry_type', 'package'))
352
if kwargs.get('assignee'):
353
kwargs['assignee'] = self.get_lp_entry('people', [kwargs['assignee']])
354
if kwargs.get('milestone'):
355
kwargs['milestone'] = entry.getMilestone(name=kwargs['milestone'])
357
if args.package and args.entry_type == 'project':
359
for pkgname in args.package:
360
srcpkg = entry.getSourcePackage(name=pkgname)
361
ret.extend([e for e in srcpkg.searchTasks(**kwargs)])
364
return entry.searchTasks(**kwargs)
366
## \breif A Extension to create a mission message that modified mantask
369
# STDIN : Encoded MissionMessage String
370
# STDOUT: Encoded MessionMessage String
253
371
class ExtensionApplyconds(object):
254
372
__metaclass__ = ExtensionType
255
373
responsible_for = ModifyMissionJob
273
391
return cond.items()
393
## \breif A Extension to edit bugtasks collected from Launchpad
395
# STDIN : Encoded MissionMessage String
275
397
class ExtensionEditbugtasks(object):
276
398
__metaclass__ = ExtensionType
277
399
responsible_for = DoLPQueryJob
278
400
interface = Command
280
def do_postjob(self):
282
dlg = dialog.Dialog()
284
bugtasks = self.result
285
kwargs = self.transform_args(args, ['yes'])
286
for bugtask in bugtasks:
287
for attr, attrval in kwargs.items():
294
confirm_msg = "Change {} {} to {}?".format(bugtask.title, attr, attrval)
295
confirm = dlg.yesno(confirm_msg, 10, 74, defaultno=True) == 0
298
# skip if new attribute value is empty
301
# need special way to modify attribute
303
if attr == 'assignee':
304
attrval = self.get_lp_entry('people', attrval)
305
setattr(bugtask, attr, attrval)
307
# inform user the change is applied
310
dlg.msgbox('Done', 10, 74)
312
402
def custom_arguments(self):
313
403
self.add_argument('--status',
314
404
choices=LP_VALIDATE_BUGTASK_STATUS,
315
405
help='change status')
316
406
self.add_argument('--assignee',
317
407
help='assigne to people')
408
self.add_argument('--milestone',
409
help='milestone name')
318
410
self.add_argument('-y', '--yes',
320
help='auto anwser yes to question')
412
help='auto anwser yes to question')
413
self.add_argument('-m', '--message',
414
help='add a comment')
416
def do_postjob(self):
418
kwargs = self.transform_args(self.cmd_args, ['yes', 'message'])
420
# ask user to select bugtasks
421
if self.cmd_args.yes:
422
selected_bugtasks = self.result
424
substr_update_attributes = []
425
for k,v in kwargs.items():
428
substr_update_attributes.append(u"{0}:{1}".format(k,v))
430
attr_msg = u"New attribute vlaues of each bugtasks:\n"
431
attr_msg += u'\n'.join(substr_update_attributes)
433
if self.cmd_args.message:
434
attr_msg += "\nComment:{0}".format(self.cmd_args.message)
436
sel_msg = u"Please select which bugtasks need to change attrbiutes\n\n"
439
confirmation_msg = u"Are you sure to modify these bugtasks?\n\n" + attr_msg
440
selected_bugtasks = ask_which_bugtasks(sel_msg, confirmation_msg, self.result)
442
for bugtask in selected_bugtasks:
443
if self.cmd_args.message:
444
print u"Add new comment"
445
bugtask.bug.newMessage(content=self.cmd_args.message)
447
self.editbugtaskattrs(bugtask, kwargs)
451
def editbugtaskattrs(self, bugtask, kwargs):
452
print u"Editing bugtask {0}".format(bugtask.title)
453
for attr, attrval in kwargs.items():
454
# skip if new attribute value is empty
458
# convert bug_target to a project
459
target = self.lp.load(bugtask.target.self_link)
462
orig_attr_val = getattr(bugtask, attr)
465
if attr == 'assignee':
466
attrval = self.get_lp_entry('people', attrval)
467
if attr == 'milestone':
468
milestone_name = attrval
469
attrval = target.getMilestone(name=attrval)
470
# Skip because milestone is not exists
472
print u"Skip to update {0}: Milestone {0} is not exists in {1}".format(milestone_name, target.name)
475
# skip if old and new attribute is same
476
# otherwise update it
477
if orig_attr_val == attrval:
478
print u"Skip to update {0}: move {1} to {2}".format(attr, orig_attr_val, attrval)
480
print u"Update {0}: move {1} to {2}".format(attr, orig_attr_val, attrval)
481
setattr(bugtask, attr, attrval)
483
## \breif A Abstract Extension Class to format collected data
322
484
class ExtensionFormatdata(object):
323
485
__metaclass__ = ExtensionType
324
486
responsible_for = DoLPQueryJob, FormatDataJob
411
586
self.target_assignee = None
413
#@FIXME: extract dlg to super class
414
self.dlg = dialog.Dialog()
415
self.logger.info(u"Result type is Bugtasks, convert to Bugs")
416
origbugs = [bugtask.bug for bugtask in self.result]
588
origbugs = m_extract_bugs(self.result)
417
589
# ask user to decide which bugs need to be duplicated
418
selected_bugids = self._ask_selection(origbugs)
419
selected_bugs = filter(lambda e: str(e.id) in selected_bugids, origbugs)
421
# exit if not selected bugs
422
if not selected_bugs:
423
self.logger.info(u"Canceld because no selected bug")
426
# ask for confirmation
427
msg = u"You will duplicate theses bugs to {0}\n".format(self.target_project.name)
428
msg += '\n'.join([u"{0}-{1}".format(bug.id, bug.title) for bug in selected_bugs])
430
self._ask_confirmation(msg)
590
sel_msg = u'Do you want to duplicate these bug to {0}'.format(self.target_project.name)
591
confirm_msg = u"You will duplicate theses bugs to {0}\n".format(self.target_project.name)
592
selected_bugs = ask_which_bugs(sel_msg, confirm_msg, origbugs)
432
594
# create bugtasks
433
595
created_bugtasks = []
526
679
editableData.userEdit()
527
680
return editableData
529
def _ask_confirmation(self, msg):
530
if self.dlg.yesno(msg, 10, 74, defaultno=True) != 0:
533
def download_bug_attachments(bug, output_dir):
534
# write title and desc
535
with open(os.path.join(output_dir, bug.title.encode('utf-8')), 'w') as descfd:
536
descfd.write(bug.description)
537
for attachment in bug.attachments:
538
filename = os.path.join(output_dir, attachment.title)
540
data = attachment.data.open()
541
with open(filename, 'w') as f:
547
"Unable to download file {0}".format(attachment.title))
682
## \breif A Extension to download attachments of bugtasks collected from Launchpad
684
# STDIN : Encoded MissionMessage String
549
686
class ExtensionGrabbugattachments(object):
550
687
__metaclass__ = ExtensionType
551
688
responsible_for = DoLPQueryJob