# Copyright 2014 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import urwid import gitrepo import mywid import sync import view.diff class ReviewDialog(urwid.WidgetWrap): signals = ['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') cancel_button = mywid.FixedButton(u'Cancel') urwid.connect_signal(save_button, 'click', lambda button:self._emit('save')) urwid.connect_signal(cancel_button, 'click', lambda button:self._emit('cancel')) buttons = urwid.Columns([('pack', save_button), ('pack', cancel_button)], dividechars=2) rows = [] categories = [] values = {} self.button_groups = {} message = '' with self.app.db.getSession() as session: revision = session.getRevision(self.revision_row.revision_key) change = revision.change if revision == change.revisions[-1]: for label in change.permitted_labels: if label.category not in categories: 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 for category in categories: rows.append(urwid.Text(category)) group = [] self.button_groups[category] = group current = pending_approvals.get(category) if current is None: current = 0 else: current = current.value for value in values[category]: if value > 0: strvalue = '+%s' % value elif value == 0: strvalue = ' 0' else: strvalue = str(value) 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 self.message = urwid.Edit("Message: \n", edit_text=message, multiline=True) rows.append(self.message) rows.append(urwid.Divider()) rows.append(buttons) pile = urwid.Pile(rows) fill = urwid.Filler(pile, valign='top') super(ReviewDialog, self).__init__(urwid.LineBox(fill, 'Review')) def save(self): message_key = None with self.app.db.getSession() as session: revision = session.getRevision(self.revision_row.revision_key) change = revision.change pending_approvals = {} for approval in change.pending_approvals: pending_approvals[approval.category] = approval for category, group in self.button_groups.items(): approval = pending_approvals.get(category) if not approval: approval = change.createApproval(u'(draft)', category, 0, pending=True) pending_approvals[category] = approval for button in group: if button.state: approval.value = int(button.get_label()) message = None for m in revision.messages: if m.pending: message = m break if not message: message = revision.createMessage(None, datetime.datetime.utcnow(), u'(draft)', '', pending=True) message.message = self.message.edit_text.strip() message_key = message.key change.reviewed = True return message_key def keypress(self, size, key): r = super(ReviewDialog, self).keypress(size, key) if r=='esc': self._emit('cancel') return None return r class ReviewButton(mywid.FixedButton): def __init__(self, revision_row): super(ReviewButton, self).__init__(u'Review') self.revision_row = revision_row self.change_view = revision_row.change_view urwid.connect_signal(self, 'click', lambda button: self.openReview()) def openReview(self): self.dialog = ReviewDialog(self.revision_row) urwid.connect_signal(self.dialog, 'save', lambda button: self.closeReview(True)) urwid.connect_signal(self.dialog, 'cancel', lambda button: self.closeReview(False)) self.change_view.app.popup(self.dialog, relative_width=50, relative_height=75, min_width=60, min_height=20) def closeReview(self, save): if save: message_key = self.dialog.save() self.change_view.app.sync.submitTask( sync.UploadReviewTask(message_key, sync.HIGH_PRIORITY)) self.change_view.refresh() self.change_view.app.backScreen() class RevisionRow(urwid.WidgetWrap): revision_focus_map = { 'revision-name': 'reversed-revision-name', 'revision-commit': 'reversed-revision-commit', 'revision-comments': 'reversed-revision-comments', 'revision-drafts': 'reversed-revision-drafts', } def __init__(self, app, change_view, repo, revision, expanded=False): super(RevisionRow, self).__init__(urwid.Pile([])) self.app = app self.change_view = change_view self.revision_key = revision.key self.project_name = revision.change.project.name self.commit_sha = revision.commit line = [('revision-name', 'Patch Set %s ' % revision.number), ('revision-commit', revision.commit)] if len(revision.pending_comments): line.append(('revision-drafts', ' (%s drafts)' % len(revision.pending_comments))) if len(revision.comments): line.append(('revision-comments', ' (%s inline comments)' % len(revision.comments))) self.title = mywid.TextButton(line, on_press = self.expandContract) stats = repo.diffstat(revision.parent, revision.commit) rows = [] total_added = 0 total_removed = 0 for added, removed, filename in stats: try: added = int(added) except ValueError: added = 0 try: removed = int(removed) except ValueError: removed = 0 total_added += added total_removed += removed rows.append(urwid.Columns([urwid.Text(filename), (10, urwid.Text('+%s, -%s' % (added, removed))), ])) rows.append(urwid.Columns([urwid.Text(''), (10, urwid.Text('+%s, -%s' % (total_added, total_removed))), ])) table = urwid.Pile(rows) buttons = urwid.Columns([('pack', ReviewButton(self)), ('pack', mywid.FixedButton("Diff", on_press=self.diff)), ('pack', mywid.FixedButton("Checkout", on_press=self.checkout)), urwid.Text(''), ], dividechars=2) self.more = urwid.Pile([table, buttons]) self.pile = urwid.Pile([self.title]) self._w = urwid.AttrMap(self.pile, None, focus_map=self.revision_focus_map) self.expanded = False if expanded: self.expandContract(None) def expandContract(self, button): if self.expanded: self.pile.contents.pop() self.expanded = False else: self.pile.contents.append((self.more, ('pack', None))) self.expanded = True def diff(self, button): self.change_view.diff(self.revision_key) def checkout(self, button): repo = self.app.getRepo(self.project_name) try: repo.checkout(self.commit_sha) dialog = mywid.MessageDialog('Checkout', 'Change checked out in %s' % repo.path) min_height=8 except gitrepo.GitCheckoutError as e: dialog = mywid.MessageDialog('Error', e.msg) min_height=12 urwid.connect_signal(dialog, 'close', lambda button: self.app.backScreen()) self.app.popup(dialog, min_height=min_height) class ChangeMessageBox(urwid.Text): def __init__(self, message): super(ChangeMessageBox, self).__init__(u'') lines = message.message.split('\n') text = [('change-message-name', message.name), ('change-message-header', ': '+lines.pop(0))] if lines and lines[-1]: lines.append('') text += '\n'.join(lines) self.set_text(text) class ChangeView(urwid.WidgetWrap): help = mywid.GLOBAL_HELP + """ This Screen =========== Toggle the reviewed flag for the current change. """ def __init__(self, app, change_key): super(ChangeView, self).__init__(urwid.Pile([])) self.app = app self.change_key = change_key self.revision_rows = {} self.message_rows = {} self.change_id_label = urwid.Text(u'', wrap='clip') self.owner_label = urwid.Text(u'', wrap='clip') self.project_label = urwid.Text(u'', wrap='clip') self.branch_label = urwid.Text(u'', wrap='clip') self.topic_label = urwid.Text(u'', wrap='clip') self.created_label = urwid.Text(u'', wrap='clip') self.updated_label = urwid.Text(u'', wrap='clip') self.status_label = urwid.Text(u'', wrap='clip') change_info = [] for l, v in [("Change-Id", self.change_id_label), ("Owner", self.owner_label), ("Project", self.project_label), ("Branch", self.branch_label), ("Topic", self.topic_label), ("Created", self.created_label), ("Updated", self.updated_label), ("Status", self.status_label), ]: row = urwid.Columns([(12, urwid.Text(('change-header', l), wrap='clip')), v]) change_info.append(row) change_info = urwid.Pile(change_info) self.commit_message = urwid.Text(u'') votes = mywid.Table([]) self.left_column = urwid.Pile([('pack', change_info), ('pack', urwid.Divider()), ('pack', votes)]) top = urwid.Columns([self.left_column, ('weight', 1, self.commit_message)]) self.listbox = urwid.ListBox(urwid.SimpleFocusListWalker([])) self._w.contents.append((self.app.header, ('pack', 1))) self._w.contents.append((urwid.Divider(), ('pack', 1))) self._w.contents.append((self.listbox, ('weight', 1))) self._w.set_focus(2) self.listbox.body.append(top) self.listbox.body.append(urwid.Divider()) self.listbox_patchset_start = len(self.listbox.body) self.refresh() def refresh(self): change_info = [] with self.app.db.getSession() as session: change = session.getChange(self.change_key) if change.reviewed: reviewed = ' (reviewed)' else: reviewed = '' self.title = 'Change %s%s' % (change.number, reviewed) self.app.status.update(title=self.title) self.project_key = change.project.key self.change_id_label.set_text(('change-data', change.change_id)) self.owner_label.set_text(('change-data', change.owner)) self.project_label.set_text(('change-data', change.project.name)) self.branch_label.set_text(('change-data', change.branch)) self.topic_label.set_text(('change-data', change.topic or '')) self.created_label.set_text(('change-data', str(change.created))) self.updated_label.set_text(('change-data', str(change.updated))) self.status_label.set_text(('change-data', change.status)) self.commit_message.set_text(change.revisions[-1].message) categories = [] approval_headers = [urwid.Text(('table-header', 'Name'))] for label in change.labels: if label.category in categories: continue approval_headers.append(urwid.Text(('table-header', label.category))) categories.append(label.category) votes = mywid.Table(approval_headers) approvals_for_name = {} for approval in change.approvals: approvals = approvals_for_name.get(approval.name) if not approvals: approvals = {} row = [] row.append(urwid.Text(approval.name)) for i, category in enumerate(categories): w = urwid.Text(u'') approvals[category] = w row.append(w) approvals_for_name[approval.name] = approvals votes.addRow(row) if str(approval.value) != '0': approvals[approval.category].set_text(str(approval.value)) votes = urwid.Padding(votes, width='pack') # TODO: update the existing table rather than replacing it # wholesale. It will become more important if the table # gets selectable items (like clickable names). self.left_column.contents[2] = (votes, ('pack', None)) repo = self.app.getRepo(change.project.name) # The listbox has both revisions and messages in it (and # may later contain the vote table and change header), so # keep track of the index separate from the loop. listbox_index = self.listbox_patchset_start for revno, revision in enumerate(change.revisions): row = self.revision_rows.get(revision.key) if not row: row = RevisionRow(self.app, self, repo, revision, expanded=(revno==len(change.revisions)-1)) self.listbox.body.insert(listbox_index, row) self.revision_rows[revision.key] = row # Revisions are extremely unlikely to be deleted, skip # that case. listbox_index += 1 if len(self.listbox.body) == listbox_index: self.listbox.body.insert(listbox_index, urwid.Divider()) listbox_index += 1 for message in change.messages: row = self.message_rows.get(message.key) if not row: row = ChangeMessageBox(message) self.listbox.body.insert(listbox_index, row) self.message_rows[message.key] = row # Messages are extremely unlikely to be deleted, skip # that case. listbox_index += 1 def toggleReviewed(self): with self.app.db.getSession() as session: change = session.getChange(self.change_key) change.reviewed = not change.reviewed def keypress(self, size, key): r = super(ChangeView, self).keypress(size, key) if r=='r': self.toggleReviewed() self.refresh() return None return r def diff(self, revision_key): self.app.changeScreen(view.diff.DiffView(self.app, revision_key))