From eb2a4911290a898597a59260c3d4965f70f56214 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Sat, 30 Aug 2014 14:53:18 -0700 Subject: [PATCH] Save draft cover messages DB migration to rename pending -> draft. Keep pending column on messages to mean "ready to upload". Indicate whether messages are drafts in change display. Update display of (draft) messages when their contents change. Also store draft votes, but don't display them until finalized (pending upload). Also remove 'drafts' flag from revisions if that revision has a pending upload. Change-Id: Iff427c0c1664351ce8e2d61be2f79f3bfa1f989d --- .../versions/3d429503a29a_add_draft_fields.py | 49 +++++++++ gertty/db.py | 49 ++++++--- gertty/palette.py | 1 + gertty/search/parser.py | 2 +- gertty/sync.py | 6 +- gertty/view/change.py | 104 +++++++++++------- gertty/view/diff.py | 10 +- 7 files changed, 155 insertions(+), 66 deletions(-) create mode 100644 gertty/alembic/versions/3d429503a29a_add_draft_fields.py diff --git a/gertty/alembic/versions/3d429503a29a_add_draft_fields.py b/gertty/alembic/versions/3d429503a29a_add_draft_fields.py new file mode 100644 index 0000000..b6b00d3 --- /dev/null +++ b/gertty/alembic/versions/3d429503a29a_add_draft_fields.py @@ -0,0 +1,49 @@ +"""add draft fields + +Revision ID: 3d429503a29a +Revises: 2a11dd14665 +Create Date: 2014-08-30 13:26:03.698902 + +""" + +# revision identifiers, used by Alembic. +revision = '3d429503a29a' +down_revision = '2a11dd14665' + +import warnings + +from alembic import op +import sqlalchemy as sa + +from gertty.dbsupport import sqlite_alter_columns, sqlite_drop_columns + +def upgrade(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + op.add_column('message', sa.Column('draft', sa.Boolean())) + op.add_column('comment', sa.Column('draft', sa.Boolean())) + op.add_column('approval', sa.Column('draft', sa.Boolean())) + + conn = op.get_bind() + res = conn.execute("update message set draft=pending") + res = conn.execute("update comment set draft=pending") + res = conn.execute("update approval set draft=pending") + + sqlite_alter_columns('message', [ + sa.Column('draft', sa.Boolean(), index=True, nullable=False), + ]) + + sqlite_alter_columns('comment', [ + sa.Column('draft', sa.Boolean(), index=True, nullable=False), + ]) + + sqlite_alter_columns('approval', [ + sa.Column('draft', sa.Boolean(), index=True, nullable=False), + ]) + + sqlite_drop_columns('comment', ['pending']) + sqlite_drop_columns('approval', ['pending']) + + +def downgrade(): + pass diff --git a/gertty/db.py b/gertty/db.py index 2263c13..e0c97ca 100644 --- a/gertty/db.py +++ b/gertty/db.py @@ -72,6 +72,7 @@ message_table = Table( Column('id', String(255), index=True), #, unique=True, nullable=False), Column('created', DateTime, index=True, nullable=False), Column('message', Text, nullable=False), + Column('draft', Boolean, index=True, nullable=False), Column('pending', Boolean, index=True, nullable=False), ) comment_table = Table( @@ -86,7 +87,7 @@ comment_table = Table( Column('parent', Boolean, nullable=False), Column('line', Integer), Column('message', Text, nullable=False), - Column('pending', Boolean, index=True, nullable=False), + Column('draft', Boolean, index=True, nullable=False), ) label_table = Table( 'label', metadata, @@ -110,7 +111,7 @@ approval_table = Table( Column('account_key', Integer, ForeignKey("account.key"), index=True), Column('category', String(255), nullable=False), Column('value', Integer, nullable=False), - Column('pending', Boolean, index=True, nullable=False), + Column('draft', Boolean, index=True, nullable=False), ) account_table = Table( 'account', metadata, @@ -257,17 +258,31 @@ class Revision(object): session.flush() return c + def getPendingMessage(self): + for m in self.messages: + if m.pending: + return m + return None + + def getDraftMessage(self): + for m in self.messages: + if m.draft: + return m + return None + + class Message(object): - def __init__(self, revision, id, author, created, message, pending=False): + def __init__(self, revision, id, author, created, message, draft=False, pending=False): self.revision_key = revision.key self.account_key = author.key self.id = id self.created = created self.message = message + self.draft = draft self.pending = pending class Comment(object): - def __init__(self, revision, id, author, in_reply_to, created, file, parent, line, message, pending=False): + def __init__(self, revision, id, author, in_reply_to, created, file, parent, line, message, draft=False): self.revision_key = revision.key self.account_key = author.key self.id = id @@ -277,7 +292,7 @@ class Comment(object): self.parent = parent self.line = line self.message = message - self.pending = pending + self.draft = draft class Label(object): def __init__(self, change, category, value, description): @@ -293,12 +308,12 @@ class PermittedLabel(object): self.value = value class Approval(object): - def __init__(self, change, reviewer, category, value, pending=False): + def __init__(self, change, reviewer, category, value, draft=False): self.change_key = change.key self.account_key = reviewer.key self.category = category self.value = value - self.pending = pending + self.draft = draft mapper(Account, account_table) mapper(Project, project_table, properties=dict( @@ -333,22 +348,22 @@ mapper(Change, change_table, properties=dict( permitted_label_table.c.value)), approvals=relationship(Approval, backref='change', order_by=(approval_table.c.category, approval_table.c.value)), - pending_approvals=relationship(Approval, - primaryjoin=and_(change_table.c.key==approval_table.c.change_key, - approval_table.c.pending==True), - order_by=(approval_table.c.category, - approval_table.c.value)) + draft_approvals=relationship(Approval, + primaryjoin=and_(change_table.c.key==approval_table.c.change_key, + approval_table.c.draft==True), + order_by=(approval_table.c.category, + approval_table.c.value)) )) mapper(Revision, revision_table, properties=dict( messages=relationship(Message, backref='revision'), comments=relationship(Comment, backref='revision', order_by=(comment_table.c.line, comment_table.c.created)), - pending_comments=relationship(Comment, - primaryjoin=and_(revision_table.c.key==comment_table.c.revision_key, - comment_table.c.pending==True), - order_by=(comment_table.c.line, - comment_table.c.created)), + draft_comments=relationship(Comment, + primaryjoin=and_(revision_table.c.key==comment_table.c.revision_key, + comment_table.c.draft==True), + order_by=(comment_table.c.line, + comment_table.c.created)), )) mapper(Message, message_table, properties=dict( author=relationship(Account))) diff --git a/gertty/palette.py b/gertty/palette.py index 570031e..1805048 100644 --- a/gertty/palette.py +++ b/gertty/palette.py @@ -56,6 +56,7 @@ DEFAULT_PALETTE={ 'focused-revision-drafts': ['dark red,standout', ''], 'change-message-name': ['yellow', ''], 'change-message-header': ['brown', ''], + 'change-message-draft': ['dark red', ''], 'revision-button': ['dark magenta', ''], 'focused-revision-button': ['light magenta', ''], 'lines-added': ['light green', ''], diff --git a/gertty/search/parser.py b/gertty/search/parser.py index f2b2562..2050902 100644 --- a/gertty/search/parser.py +++ b/gertty/search/parser.py @@ -236,7 +236,7 @@ def SearchParser(): filters = [] filters.append(gertty.db.revision_table.c.change_key == gertty.db.change_table.c.key) filters.append(gertty.db.message_table.c.revision_key == gertty.db.revision_table.c.key) - filters.append(gertty.db.message_table.c.pending == True) + filters.append(gertty.db.message_table.c.draft == True) s = select([gertty.db.change_table.c.key], correlate=False).where(and_(*filters)) p[0] = gertty.db.change_table.c.key.in_(s) else: diff --git a/gertty/sync.py b/gertty/sync.py index 87dbd03..f1719af 100644 --- a/gertty/sync.py +++ b/gertty/sync.py @@ -540,14 +540,14 @@ class UploadReviewTask(Task): strict_labels=False) if revision == current_revision: data['labels'] = {} - for approval in change.pending_approvals: + for approval in change.draft_approvals: data['labels'][approval.category] = approval.value session.delete(approval) - if revision.pending_comments: + if revision.draft_comments: data['comments'] = {} last_file = None comment_list = [] - for comment in revision.pending_comments: + for comment in revision.draft_comments: if comment.file != last_file: last_file = comment.file comment_list = [] diff --git a/gertty/view/change.py b/gertty/view/change.py index f26f19a..7f5ed6c 100644 --- a/gertty/view/change.py +++ b/gertty/view/change.py @@ -53,14 +53,14 @@ class ReviewDialog(urwid.WidgetWrap): categories.append(label.category) values[label.category] = [] values[label.category].append(label.value) - pending_approvals = {} - for approval in change.pending_approvals: - pending_approvals[approval.category] = approval + draft_approvals = {} + for approval in change.draft_approvals: + draft_approvals[approval.category] = approval for category in categories: rows.append(urwid.Text(category)) group = [] self.button_groups[category] = group - current = pending_approvals.get(category) + current = draft_approvals.get(category) if current is None: current = 0 else: @@ -75,10 +75,11 @@ class ReviewDialog(urwid.WidgetWrap): b = urwid.RadioButton(group, strvalue, state=(value == current)) rows.append(b) rows.append(urwid.Divider()) - for m in revision.messages: - if m.pending: - message = m.message - break + m = revision.getPendingMessage() + if not m: + m = revision.getDraftMessage() + if m: + message = m.message self.message = urwid.Edit("Message: \n", edit_text=message, multiline=True) rows.append(self.message) rows.append(urwid.Divider()) @@ -87,14 +88,15 @@ class ReviewDialog(urwid.WidgetWrap): fill = urwid.Filler(pile, valign='top') super(ReviewDialog, self).__init__(urwid.LineBox(fill, 'Review')) - def save(self): + def save(self, upload=False): approvals = {} for category, group in self.button_groups.items(): for button in group: if button.state: approvals[category] = int(button.get_label()) message = self.message.edit_text.strip() - self.change_view.saveReview(self.revision_row.revision_key, approvals, message) + self.change_view.saveReview(self.revision_row.revision_key, approvals, + message, upload) def keypress(self, size, key): r = super(ReviewDialog, self).keypress(size, key) @@ -122,9 +124,8 @@ class ReviewButton(mywid.FixedButton): relative_width=50, relative_height=75, min_width=60, min_height=20) - def closeReview(self, save): - if save: - message_key = self.dialog.save() + def closeReview(self, upload): + self.dialog.save(upload) self.change_view.app.backScreen() class RevisionRow(urwid.WidgetWrap): @@ -192,10 +193,12 @@ class RevisionRow(urwid.WidgetWrap): def update(self, revision): line = [('revision-name', 'Patch Set %s ' % revision.number), ('revision-commit', revision.commit)] - num_drafts = len(revision.pending_comments) + num_drafts = len(revision.draft_comments) if num_drafts: - line.append(('revision-drafts', ' (%s draft%s)' % ( - num_drafts, num_drafts>1 and 's' or ''))) + pending_message = revision.getPendingMessage() + if not pending_message: + line.append(('revision-drafts', ' (%s draft%s)' % ( + num_drafts, num_drafts>1 and 's' or ''))) num_comments = len(revision.comments) - num_drafts if num_comments: line.append(('revision-comments', ' (%s inline comment%s)' % ( @@ -260,16 +263,26 @@ class ChangeButton(mywid.FixedButton): class ChangeMessageBox(mywid.HyperText): def __init__(self, app, message): super(ChangeMessageBox, self).__init__(u'') + self.app = app + self.refresh(message) + + def refresh(self, message): + self.message_created = message.created lines = message.message.split('\n') + if message.draft: + lines.insert(0, '') + lines.insert(0, 'Patch Set %s:' % (message.revision.number,)) text = [('change-message-name', message.author.name), ('change-message-header', ': '+lines.pop(0)), ('change-message-header', message.created.strftime(' (%Y-%m-%d %H:%M:%S%z)'))] + if message.draft and not message.pending: + text.append(('change-message-draft', ' (draft)')) if lines and lines[-1]: lines.append('') comment_text = ['\n'.join(lines)] - for commentlink in app.config.commentlinks: - comment_text = commentlink.run(app, comment_text) + for commentlink in self.app.config.commentlinks: + comment_text = commentlink.run(self.app, comment_text) self.set_text(text+comment_text) class CommitMessageBox(mywid.HyperText): @@ -443,7 +456,11 @@ class ChangeView(urwid.WidgetWrap): categories.append(label.category) votes = mywid.Table(approval_headers) approvals_for_name = {} + pending_message = change.revisions[-1].getPendingMessage() for approval in change.approvals: + # Don't display draft approvals unless they are pending-upload + if approval.draft and not pending_message: + continue approvals = approvals_for_name.get(approval.reviewer.name) if not approvals: approvals = {} @@ -529,6 +546,8 @@ class ChangeView(urwid.WidgetWrap): self.message_rows[message.key] = row else: unseen_keys.remove(message.key) + if message.created != row.original_widget.message_created: + row.original_widget.refresh(message) listbox_index += 1 # Remove any messages that should not be displayed for key in unseen_keys: @@ -692,40 +711,45 @@ 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, '') + self.saveReview(row.revision_key, approvals, '', True) - def saveReview(self, revision_key, approvals, message): + def saveReview(self, revision_key, approvals, message, upload): 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 - pending_approvals = {} - for approval in change.pending_approvals: - pending_approvals[approval.category] = approval + 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 = pending_approvals.get(category) + approval = draft_approvals.get(category) if not approval: - approval = change.createApproval(account, category, 0, pending=True) - pending_approvals[category] = approval + approval = change.createApproval(account, category, 0, draft=True) + draft_approvals[category] = approval approval.value = value - pending_message = None - for m in revision.messages: - if m.pending: - pending_message = m - break - if not pending_message: - pending_message = revision.createMessage(None, account, - datetime.datetime.utcnow(), - '', pending=True) - pending_message.message = message - message_key = pending_message.key - change.reviewed = True - self.app.sync.submitTask( - sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) + 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 + # Outside of db session + if upload: + self.app.sync.submitTask( + sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) self.refresh() diff --git a/gertty/view/diff.py b/gertty/view/diff.py index 78ce11e..2d9f9cb 100644 --- a/gertty/view/diff.py +++ b/gertty/view/diff.py @@ -197,12 +197,12 @@ class BaseDiffView(urwid.WidgetWrap): key = 'old' else: key = 'new' - if comment.pending: + if comment.draft: key += 'draft' key += '-' + str(comment.line) key += '-' + str(comment.file) comment_list = comment_lists.get(key, []) - if comment.pending: + if comment.draft: message = comment.message else: message = [('comment-name', comment.author.name), @@ -214,12 +214,12 @@ class BaseDiffView(urwid.WidgetWrap): if comment.parent: continue key = 'old' - if comment.pending: + if comment.draft: key += 'draft' key += '-' + str(comment.line) key += '-' + str(comment.file) comment_list = comment_lists.get(key, []) - if comment.pending: + if comment.draft: message = comment.message else: message = [('comment-name', comment.author.name), @@ -404,7 +404,7 @@ class BaseDiffView(urwid.WidgetWrap): comment = revision.createComment(None, account, None, datetime.datetime.utcnow(), filename, parent, - line_num, text, pending=True) + line_num, text, draft=True) key = comment.key return key