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:
parent
ce1fccbccb
commit
94bfcd13f4
@ -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()
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user