From 94bfcd13f4445cef4083ee75db7cc5c4f941fbb5 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Sat, 23 May 2015 15:34:01 -0700 Subject: [PATCH] Add ability to review multiple changes at once Add a process mark to the change list so that multiple changes may be selected at once for further operations. Allow multiple changes to be reviwed at once by selecting them with the process mark and then pressing the usual review command key. The categories are simply taken from the first change in the list for simplicity. This should be fine most of the time, but if the changes have different categories available to them, or the user has different access levels, this may not behave as intended. Change-Id: I04a790d91b27b270cf1269c7bcb39c12d857ab32 --- gertty/app.py | 53 ++++++++++++++++++++++++++ gertty/keymap.py | 2 + gertty/palette.py | 2 + gertty/view/change.py | 77 +++++++++++--------------------------- gertty/view/change_list.py | 54 ++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 56 deletions(-) diff --git a/gertty/app.py b/gertty/app.py index a69fab1..6c35934 100644 --- a/gertty/app.py +++ b/gertty/app.py @@ -14,6 +14,7 @@ # under the License. import argparse +import datetime import dateutil import logging import os @@ -602,6 +603,58 @@ class App(object): lambda button: self.backScreen()) self.popup(dialog, min_height=min_height) + def saveReviews(self, revision_keys, approvals, message, upload, submit): + message_keys = [] + with self.db.getSession() as session: + account = session.getAccountByUsername(self.config.username) + for revision_key in revision_keys: + k = self._saveReview(session, account, revision_key, + approvals, message, upload, submit) + if k: + message_keys.append(k) + return message_keys + + def _saveReview(self, session, account, revision_key, + approvals, message, upload, submit): + message_key = None + revision = session.getRevision(revision_key) + change = revision.change + draft_approvals = {} + for approval in change.draft_approvals: + draft_approvals[approval.category] = approval + + categories = set() + for label in change.permitted_labels: + categories.add(label.category) + for category in categories: + value = approvals.get(category, 0) + approval = draft_approvals.get(category) + if not approval: + approval = change.createApproval(account, category, 0, draft=True) + draft_approvals[category] = approval + approval.value = value + draft_message = revision.getPendingMessage() + if not draft_message: + draft_message = revision.getDraftMessage() + if not draft_message: + if message or upload: + draft_message = revision.createMessage(None, account, + datetime.datetime.utcnow(), + '', draft=True) + if draft_message: + draft_message.created = datetime.datetime.utcnow() + draft_message.message = message + draft_message.pending = upload + message_key = draft_message.key + if upload: + change.reviewed = True + if submit: + change.status = 'SUBMITTED' + change.pending_status = True + change.pending_status_message = None + return message_key + + def version(): return "Gertty version: %s" % gertty.version.version_info.version_string() diff --git a/gertty/keymap.py b/gertty/keymap.py index a1bd006..61c00f0 100644 --- a/gertty/keymap.py +++ b/gertty/keymap.py @@ -41,6 +41,7 @@ TOGGLE_REVIEWED = 'toggle reviewed' TOGGLE_HIDDEN = 'toggle hidden' TOGGLE_STARRED = 'toggle starred' TOGGLE_HELD = 'toggle held' +TOGGLE_MARK = 'toggle process mark' REVIEW = 'review' DIFF = 'diff' LOCAL_CHECKOUT = 'local checkout' @@ -92,6 +93,7 @@ DEFAULT_KEYMAP = { TOGGLE_HIDDEN: 'k', TOGGLE_STARRED: '*', TOGGLE_HELD: '!', + TOGGLE_MARK: '%', REVIEW: 'r', DIFF: 'd', LOCAL_CHECKOUT: 'c', diff --git a/gertty/palette.py b/gertty/palette.py index 7a4ab11..fae8756 100644 --- a/gertty/palette.py +++ b/gertty/palette.py @@ -86,6 +86,8 @@ DEFAULT_PALETTE={ 'focused-starred-change': ['light cyan,standout', ''], 'held-change': ['light red', ''], 'focused-held-change': ['light red,standout', ''], + 'marked-change': ['dark cyan', ''], + 'focused-marked-change': ['dark cyan,standout', ''], } # A delta from the default palette diff --git a/gertty/view/change.py b/gertty/view/change.py index 659714f..7f401df 100644 --- a/gertty/view/change.py +++ b/gertty/view/change.py @@ -84,10 +84,9 @@ class CherryPickDialog(urwid.WidgetWrap): class ReviewDialog(urwid.WidgetWrap): signals = ['submit', 'save', 'cancel'] - def __init__(self, revision_row): - self.revision_row = revision_row - self.change_view = revision_row.change_view - self.app = self.change_view.app + def __init__(self, app, revision_key): + self.revision_key = revision_key + self.app = app save_button = mywid.FixedButton(u'Save') submit_button = mywid.FixedButton(u'Save and Submit') cancel_button = mywid.FixedButton(u'Cancel') @@ -98,11 +97,6 @@ class ReviewDialog(urwid.WidgetWrap): urwid.connect_signal(cancel_button, 'click', lambda button:self._emit('cancel')) - buttons = [('pack', save_button)] - if revision_row.can_submit: - buttons.append(('pack', submit_button)) - buttons.append(('pack', cancel_button)) - buttons = urwid.Columns(buttons, dividechars=2) rows = [] categories = [] values = {} @@ -110,8 +104,13 @@ class ReviewDialog(urwid.WidgetWrap): self.button_groups = {} message = '' with self.app.db.getSession() as session: - revision = session.getRevision(self.revision_row.revision_key) + revision = session.getRevision(self.revision_key) change = revision.change + buttons = [('pack', save_button)] + if revision.can_submit: + buttons.append(('pack', submit_button)) + buttons.append(('pack', cancel_button)) + buttons = urwid.Columns(buttons, dividechars=2) if revision == change.revisions[-1]: for label in change.labels: d = descriptions.setdefault(label.category, {}) @@ -174,15 +173,14 @@ class ReviewDialog(urwid.WidgetWrap): fill = urwid.Filler(pile, valign='top') super(ReviewDialog, self).__init__(urwid.LineBox(fill, 'Review')) - def save(self, upload=False, submit=False): + def getValues(self): approvals = {} for category, group in self.button_groups.items(): for button in group: if button.state: approvals[category] = button._value message = self.message.edit_text.strip() - self.change_view.saveReview(self.revision_row.revision_key, approvals, - message, upload, submit) + return (approvals, message) def keypress(self, size, key): r = super(ReviewDialog, self).keypress(size, key) @@ -201,7 +199,8 @@ class ReviewButton(mywid.FixedButton): lambda button: self.openReview()) def openReview(self): - self.dialog = ReviewDialog(self.revision_row) + self.dialog = ReviewDialog(self.change_view.app, + self.revision_row.revision_key) urwid.connect_signal(self.dialog, 'save', lambda button: self.closeReview(True, False)) urwid.connect_signal(self.dialog, 'submit', @@ -213,7 +212,9 @@ class ReviewButton(mywid.FixedButton): min_width=60, min_height=20) def closeReview(self, upload, submit): - self.dialog.save(upload, submit) + approvals, message = self.dialog.getValues() + self.change_view.saveReview(self.revision_row.revision_key, approvals, + message, upload, submit) self.change_view.app.backScreen() class RevisionRow(urwid.WidgetWrap): @@ -1037,46 +1038,10 @@ class ChangeView(urwid.WidgetWrap): self.saveReview(row.revision_key, approvals, '', True, submit) def saveReview(self, revision_key, approvals, message, upload, submit): - message_key = None - with self.app.db.getSession() as session: - account = session.getAccountByUsername(self.app.config.username) - revision = session.getRevision(revision_key) - change = revision.change - draft_approvals = {} - for approval in change.draft_approvals: - draft_approvals[approval.category] = approval - - categories = set() - for label in change.permitted_labels: - categories.add(label.category) - for category in categories: - value = approvals.get(category, 0) - approval = draft_approvals.get(category) - if not approval: - approval = change.createApproval(account, category, 0, draft=True) - draft_approvals[category] = approval - approval.value = value - draft_message = revision.getPendingMessage() - if not draft_message: - draft_message = revision.getDraftMessage() - if not draft_message: - if message or upload: - draft_message = revision.createMessage(None, account, - datetime.datetime.utcnow(), - '', draft=True) - if draft_message: - draft_message.created = datetime.datetime.utcnow() - draft_message.message = message - draft_message.pending = upload - message_key = draft_message.key - if upload: - change.reviewed = True - if submit: - change.status = 'SUBMITTED' - change.pending_status = True - change.pending_status_message = None - # Outside of db session + message_keys = self.app.saveReviews([revision_key], approvals, + message, upload, submit) if upload: - self.app.sync.submitTask( - sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) + for message_key in message_keys: + self.app.sync.submitTask( + sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) self.refresh() diff --git a/gertty/view/change_list.py b/gertty/view/change_list.py index 8475207..430465a 100644 --- a/gertty/view/change_list.py +++ b/gertty/view/change_list.py @@ -45,12 +45,14 @@ class ThreadStack(object): def countChildren(self): return [len(x[1]) for x in self.stack] + class ChangeRow(urwid.Button): change_focus_map = {None: 'focused', 'unreviewed-change': 'focused-unreviewed-change', 'reviewed-change': 'focused-reviewed-change', 'starred-change': 'focused-starred-change', 'held-change': 'focused-held-change', + 'marked-change': 'focused-marked-change', 'positive-label': 'focused-positive-label', 'negative-label': 'focused-negative-label', 'min-label': 'focused-min-label', @@ -70,6 +72,7 @@ class ChangeRow(urwid.Button): self.updated = urwid.Text(u'') self.project = urwid.Text(u'', wrap='clip') self.owner = urwid.Text(u'', wrap='clip') + self.mark = False cols = [(6, self.number), ('weight', 4, self.subject)] if project: cols.append(('weight', 1, self.project)) @@ -99,6 +102,9 @@ class ChangeRow(urwid.Button): if change.held: flag = '!' style = 'held-change' + if self.mark: + flag = '%' + style = 'marked-change' subject = flag + subject self.row_style.set_attr_map({None: style}) self.subject.set_text(subject) @@ -107,6 +113,7 @@ class ChangeRow(urwid.Button): self.owner.set_text(change.owner_name) self.project_name = change.project.name self.commit_sha = change.revisions[-1].commit + self.current_revision_key = change.revisions[-1].key today = self.app.time(datetime.datetime.utcnow()).date() updated_time = self.app.time(change.updated) if today == updated_time.date(): @@ -171,8 +178,12 @@ class ChangeListView(urwid.WidgetWrap): "Toggle the reviewed flag for the currently selected change"), (key(keymap.TOGGLE_STARRED), "Toggle the starred flag for the currently selected change"), + (key(keymap.TOGGLE_MARK), + "Toggle the process mark for the currently selected change"), (key(keymap.REFRESH), refresh_help), + (key(keymap.REVIEW), + "Leave reviews for the marked changes"), (key(keymap.SORT_BY_NUMBER), "Sort changes by number"), (key(keymap.SORT_BY_UPDATED), @@ -458,6 +469,20 @@ class ChangeListView(urwid.WidgetWrap): change = session.getChange(change_key) row.update(change, self.categories) return None + if keymap.TOGGLE_MARK in commands: + if not len(self.listbox.body): + return None + pos = self.listbox.focus_position + change_key = self.listbox.body[pos].change_key + row = self.change_rows[change_key] + row.mark = not row.mark + with self.app.db.getSession() as session: + change = session.getChange(change_key) + row.update(change, self.categories) + if pos < len(self.listbox.body)-1: + pos += 1 + self.listbox.focus_position = pos + return None if keymap.REFRESH in commands: if self.project_key: self.app.sync.submitTask( @@ -467,6 +492,11 @@ class ChangeListView(urwid.WidgetWrap): sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY)) self.app.status.update() return None + if keymap.REVIEW in commands: + marked = [row for row in self.change_rows.values() if row.mark] + if marked: + self.openReview(marked) + return None if keymap.SORT_BY_NUMBER in commands: if not len(self.listbox.body): return None @@ -513,3 +543,27 @@ class ChangeListView(urwid.WidgetWrap): self.app.changeScreen(view) except gertty.view.DisplayError as e: self.app.error(e.message) + + def openReview(self, rows): + dialog = view_change.ReviewDialog(self.app, rows[0].current_revision_key) + urwid.connect_signal(dialog, 'save', + lambda button: self.closeReview(dialog, rows, True, False)) + urwid.connect_signal(dialog, 'submit', + lambda button: self.closeReview(dialog, rows, True, True)) + urwid.connect_signal(dialog, 'cancel', + lambda button: self.closeReview(dialog, rows, False, False)) + self.app.popup(dialog, + relative_width=50, relative_height=75, + min_width=60, min_height=20) + + def closeReview(self, dialog, rows, upload, submit): + approvals, message = dialog.getValues() + revision_keys = [row.current_revision_key for row in rows] + message_keys = self.app.saveReviews(revision_keys, approvals, + message, upload, submit) + if upload: + for message_key in message_keys: + self.app.sync.submitTask( + sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) + self.refresh() + self.app.backScreen()