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