Add submit functionality

Add a schema change to store whether the user can submit a revision.
Offer the submit button on that revision, as well as on the review
dialog if applicable.  Allow the submit action to be bound to
reviewkeys.  Also bind it to a key command.

Change-Id: I341663ac84d2ac09f9b1ef8c9e0dff45d2658e1d
This commit is contained in:
James E. Blair 2014-09-18 17:28:50 -07:00
parent c245f846bf
commit e47cc2f607
8 changed files with 127 additions and 18 deletions

View File

@ -47,8 +47,10 @@ dashboards:
# change screen. Any pending comments or review messages will be
# attached to the review; otherwise an empty review will be left. The
# approvals list is exhaustive, so if you specify an empty list,
# Gertty will submit a review that clears any previous approvals.
# They will appear in the help text for the change screen.
# Gertty will submit a review that clears any previous approvals. To
# submit the change with the review, include 'submit: True' with the
# reviewkey. Reviewkeys appear in the help text for the change
# screen.
reviewkeys:
- key: 'meta 0'
approvals: []
@ -60,3 +62,8 @@ reviewkeys:
approvals:
- category: 'Code-Review'
value: 2
- key: 'meta 3'
approvals:
- category: 'Code-Review'
value: 2
submit: True

View File

@ -157,8 +157,10 @@ dashboards:
# change screen. Any pending comments or review messages will be
# attached to the review; otherwise an empty review will be left. The
# approvals list is exhaustive, so if you specify an empty list,
# Gertty will submit a review that clears any previous approvals.
# They will appear in the help text for the change screen.
# Gertty will submit a review that clears any previous approvals. To
# submit the change with the review, include 'submit: True' with the
# reviewkey. Reviewkeys appear in the help text for the change
# screen.
reviewkeys:
- key: 'meta 0'
approvals: []
@ -170,3 +172,8 @@ reviewkeys:
approvals:
- category: 'Code-Review'
value: 2
- key: 'meta 3'
approvals:
- category: 'Code-Review'
value: 2
submit: True

View File

@ -0,0 +1,36 @@
"""add can_submit column
Revision ID: 312cd5a9f878
Revises: 46b175bfa277
Create Date: 2014-09-18 16:37:13.149729
"""
# revision identifiers, used by Alembic.
revision = '312cd5a9f878'
down_revision = '46b175bfa277'
import warnings
from alembic import op
import sqlalchemy as sa
from gertty.dbsupport import sqlite_alter_columns
def upgrade():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
op.add_column('revision', sa.Column('can_submit', sa.Boolean()))
conn = op.get_bind()
q = sa.text('update revision set can_submit=:submit')
res = conn.execute(q, submit=False)
sqlite_alter_columns('revision', [
sa.Column('can_submit', sa.Boolean(), nullable=False),
])
def downgrade():
pass

View File

@ -83,6 +83,7 @@ class ConfigSchema(object):
v.Required('value'): int}
reviewkey = {v.Required('approvals'): [reviewkey_approval],
'submit': bool,
v.Required('key'): str}
reviewkeys = [reviewkey]

View File

@ -74,6 +74,7 @@ revision_table = Table(
Column('fetch_auth', Boolean, nullable=False),
Column('fetch_ref', String(255), nullable=False),
Column('pending_message', Boolean, index=True, nullable=False),
Column('can_submit', Boolean, nullable=False),
)
message_table = Table(
'message', metadata,
@ -268,7 +269,8 @@ class Change(object):
class Revision(object):
def __init__(self, change, number, message, commit, parent,
fetch_auth, fetch_ref, pending_message=False):
fetch_auth, fetch_ref, pending_message=False,
can_submit=False):
self.change_key = change.key
self.number = number
self.message = message
@ -277,6 +279,7 @@ class Revision(object):
self.fetch_auth = fetch_auth
self.fetch_ref = fetch_ref
self.pending_message = pending_message
self.can_submit = can_submit
def createMessage(self, *args, **kw):
session = Session.object_session(self)

View File

@ -52,6 +52,7 @@ CHERRY_PICK_CHANGE = 'cherry pick change'
REFRESH = 'refresh'
EDIT_TOPIC = 'edit topic'
EDIT_COMMIT_MESSAGE = 'edit commit message'
SUBMIT_CHANGE = 'submit change'
# Project list screen:
TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
@ -95,6 +96,7 @@ DEFAULT_KEYMAP = {
REFRESH: 'ctrl r',
EDIT_TOPIC: 'ctrl t',
EDIT_COMMIT_MESSAGE: 'ctrl d',
SUBMIT_CHANGE: 'ctrl u',
TOGGLE_LIST_REVIEWED: 'l',
TOGGLE_LIST_SUBSCRIBED: 'L',

View File

@ -306,7 +306,7 @@ class SyncChangeTask(Task):
def run(self, sync):
app = sync.app
remote_change = sync.get('changes/%s?o=DETAILED_LABELS&o=ALL_REVISIONS&o=ALL_COMMITS&o=MESSAGES&o=DETAILED_ACCOUNTS' % self.change_id)
remote_change = sync.get('changes/%s?o=DETAILED_LABELS&o=ALL_REVISIONS&o=ALL_COMMITS&o=MESSAGES&o=DETAILED_ACCOUNTS&o=CURRENT_ACTIONS' % self.change_id)
# Perform subqueries this task will need outside of the db session
for remote_commit, remote_revision in remote_change.get('revisions', {}).items():
remote_comments_data = sync.get('changes/%s/revisions/%s/comments' % (self.change_id, remote_commit))
@ -360,6 +360,8 @@ class SyncChangeTask(Task):
revision.message = remote_revision['commit']['message']
# TODO: handle multiple parents
parent_revision = session.getRevisionByCommit(revision.parent)
actions = remote_revision.get('actions', {})
revision.can_submit = 'submit' in actions
# TODO: use a singleton list of closed states
if not parent_revision and change.status not in ['MERGED', 'ABANDONED']:
sync.submitTask(SyncChangeByCommitTask(revision.parent, self.priority))
@ -667,6 +669,8 @@ class ChangeStatusTask(Task):
elif change.status == 'NEW':
sync.post('changes/%s/restore' % (change.id,),
data)
elif change.status == 'SUBMITTED':
sync.post('changes/%s/submit' % (change.id,), {})
sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
class SendCherryPickTask(Task):
@ -722,11 +726,16 @@ class UploadReviewTask(Task):
def run(self, sync):
app = sync.app
submit = False
change_id = None
with app.db.getSession() as session:
message = session.getMessage(self.message_key)
revision = message.revision
change = message.revision.change
change_id = change.id
current_revision = change.revisions[-1]
if change.pending_status and change.status == 'SUBMITTED':
submit = True
data = dict(message=message.message,
strict_labels=False)
if revision == current_revision:
@ -753,7 +762,15 @@ class UploadReviewTask(Task):
# Inside db session for rollback
sync.post('changes/%s/revisions/%s/review' % (change.id, revision.commit),
data)
sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
if submit:
# In another db session in case submit fails after posting
# the message succeeds
with app.db.getSession() as session:
change = session.getChangeByID(change_id)
change.pending_status = False
change.pending_status_message = None
sync.post('changes/%s/submit' % (change_id,), {})
sync.submitTask(SyncChangeTask(change_id, priority=self.priority))
class Sync(object):
def __init__(self, app):

View File

@ -81,19 +81,26 @@ class CherryPickDialog(urwid.WidgetWrap):
'Propose Change to Branch'))
class ReviewDialog(urwid.WidgetWrap):
signals = ['save', 'cancel']
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
save_button = mywid.FixedButton(u'Save')
submit_button = mywid.FixedButton(u'Save and Submit')
cancel_button = mywid.FixedButton(u'Cancel')
urwid.connect_signal(save_button, 'click',
lambda button:self._emit('save'))
urwid.connect_signal(submit_button, 'click',
lambda button:self._emit('submit'))
urwid.connect_signal(cancel_button, 'click',
lambda button:self._emit('cancel'))
buttons = urwid.Columns([('pack', save_button), ('pack', cancel_button)],
dividechars=2)
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 = {}
@ -143,7 +150,7 @@ class ReviewDialog(urwid.WidgetWrap):
fill = urwid.Filler(pile, valign='top')
super(ReviewDialog, self).__init__(urwid.LineBox(fill, 'Review'))
def save(self, upload=False):
def save(self, upload=False, submit=False):
approvals = {}
for category, group in self.button_groups.items():
for button in group:
@ -151,7 +158,7 @@ class ReviewDialog(urwid.WidgetWrap):
approvals[category] = int(button.get_label())
message = self.message.edit_text.strip()
self.change_view.saveReview(self.revision_row.revision_key, approvals,
message, upload)
message, upload, submit)
def keypress(self, size, key):
r = super(ReviewDialog, self).keypress(size, key)
@ -172,15 +179,17 @@ class ReviewButton(mywid.FixedButton):
def openReview(self):
self.dialog = ReviewDialog(self.revision_row)
urwid.connect_signal(self.dialog, 'save',
lambda button: self.closeReview(True))
lambda button: self.closeReview(True, False))
urwid.connect_signal(self.dialog, 'submit',
lambda button: self.closeReview(True, True))
urwid.connect_signal(self.dialog, 'cancel',
lambda button: self.closeReview(False))
lambda button: self.closeReview(False, False))
self.change_view.app.popup(self.dialog,
relative_width=50, relative_height=75,
min_width=60, min_height=20)
def closeReview(self, upload):
self.dialog.save(upload)
def closeReview(self, upload, submit):
self.dialog.save(upload, submit)
self.change_view.app.backScreen()
class RevisionRow(urwid.WidgetWrap):
@ -198,6 +207,7 @@ class RevisionRow(urwid.WidgetWrap):
self.revision_key = revision.key
self.project_name = revision.change.project.name
self.commit_sha = revision.commit
self.can_submit = revision.can_submit
self.title = mywid.TextButton(u'', on_press = self.expandContract)
stats = repo.diffstat(revision.parent, revision.commit)
table = mywid.Table(columns=3)
@ -233,6 +243,10 @@ class RevisionRow(urwid.WidgetWrap):
on_press=self.checkout),
mywid.FixedButton(('revision-button', "Local Cherry-Pick"),
on_press=self.cherryPick)]
if self.can_submit:
buttons.append(mywid.FixedButton(('revision-button', "Submit"),
on_press=lambda x: self.change_view.doSubmitChange()))
buttons = [('pack', urwid.AttrMap(b, None, focus_map=focus_map)) for b in buttons]
buttons = urwid.Columns(buttons + [urwid.Text('')], dividechars=2)
buttons = urwid.AttrMap(buttons, 'revision-button')
@ -387,6 +401,8 @@ class ChangeView(urwid.WidgetWrap):
"Refresh this change"),
(key(keymap.EDIT_TOPIC),
"Edit the topic of this change"),
(key(keymap.SUBMIT_CHANGE),
"Submit this change"),
(key(keymap.CHERRY_PICK_CHANGE),
"Propose this change to another branch"),
]
@ -774,6 +790,9 @@ class ChangeView(urwid.WidgetWrap):
sync.SyncChangeTask(self.change_rest_id, priority=sync.HIGH_PRIORITY))
self.app.status.update()
return None
if keymap.SUBMIT_CHANGE in commands:
self.doSubmitChange()
return None
if keymap.EDIT_TOPIC in commands:
self.editTopic()
return None
@ -896,6 +915,18 @@ class ChangeView(urwid.WidgetWrap):
self.app.backScreen()
self.refresh()
def doSubmitChange(self):
change_key = None
with self.app.db.getSession() as session:
change = session.getChange(self.change_key)
change.status = 'SUBMITTED'
change.pending_status = True
change.pending_status_message = None
change_key = change.key
self.app.sync.submitTask(
sync.ChangeStatusTask(change_key, sync.HIGH_PRIORITY))
self.refresh()
def editTopic(self):
dialog = EditTopicDialog(self.app, self.topic)
urwid.connect_signal(dialog, 'save',
@ -924,9 +955,10 @@ class ChangeView(urwid.WidgetWrap):
self.app.log.debug("Reviewkey %s with approvals %s" %
(reviewkey['key'], approvals))
row = self.revision_rows[self.last_revision_key]
self.saveReview(row.revision_key, approvals, '', True)
submit = reviewkey.get('submit', False)
self.saveReview(row.revision_key, approvals, '', True, submit)
def saveReview(self, revision_key, approvals, message, upload):
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)
@ -961,6 +993,10 @@ class ChangeView(urwid.WidgetWrap):
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
if upload:
self.app.sync.submitTask(