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
This commit is contained in:
James E. Blair 2015-05-23 15:34:01 -07:00
parent ce1fccbccb
commit 94bfcd13f4
5 changed files with 132 additions and 56 deletions

View File

@ -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()

View File

@ -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',

View File

@ -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

View File

@ -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()

View File

@ -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()