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
This commit is contained in:
James E. Blair 2014-08-30 14:53:18 -07:00
parent fe8c123407
commit eb2a491129
7 changed files with 155 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = []

View File

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

View File

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