# HG changeset patch
# User Matt Harbison <
matt_h...@yahoo.com>
# Date 1547357754 18000
# Sun Jan 13 00:35:54 2019 -0500
# Node ID 36bedfbf08551c60a35e6ec4309d6abd7c74449c
# Parent 5a17076b32504a821c77a7473b89765f337dafc1
phabricator: add controls to specify the reviewers
Asking the server for a list of users seems like an easy way to avoid typos, not
knowing a reviewer's registered name, etc. I thought about also adding a text
edit field to directly add the name in case something goes wrong. But the
reviewers don't need to be specified to post the review, so let's keep the UI
simple.
This should be functionally complete. I think the debugcallconduit call should
probably be farmed out to commandserver to not block the GUI. But I can't
figure out how to do that. It looks like the 'I' channel has a TODO to
implement if needed. Maybe the existing 'L' channel can be used somehow for
such a simple query? The other aspect of this is the cursor following. I'm
thinking about adding a '--follow' to debugcallconduit to optionally do this,
printing a JSON list of the individual JSON blobs, so only one command is
needed. I *think* the progressbar shows when a command starts, and hides when
it ends. So doing a series of commands might look weird.
The other strange behavior I see is the list of available reviewers isn't sorted
when querying phab.m-s.o, even though the setDynamicSortFilter property is on.
diff --git a/tortoisehg/hgqt/phabreview.py b/tortoisehg/hgqt/phabreview.py
--- a/tortoisehg/hgqt/phabreview.py
+++ b/tortoisehg/hgqt/phabreview.py
@@ -9,13 +9,18 @@
from .qtcore import (
QSettings,
+ QSortFilterProxyModel,
Qt,
pyqtSlot,
)
from .qtgui import (
QDialog,
QKeySequence,
+ QListWidgetItem,
QShortcut,
+ QStandardItem,
+ QStandardItemModel,
+ QStyledItemDelegate,
)
from ..util import hglib
@@ -42,10 +47,29 @@
self.setWindowFlags(Qt.Window)
self._repoagent = repoagent
self._cmdsession = cmdcore.nullCmdSession()
+ self._rescansession = cmdcore.nullCmdSession()
self._qui = Ui_PhabReviewDialog()
self._qui.setupUi(self)
+ proxymodel = QSortFilterProxyModel(self._qui.available_reviewer_list)
+ proxymodel.setDynamicSortFilter(True)
+ proxymodel.setSourceModel(QStandardItemModel())
+ proxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive)
+ self.availablereviewersmodel = proxymodel
+
+ reviewerlist = self._qui.available_reviewer_list
+ reviewerlist.setModel(proxymodel)
+ reviewerlist.selectionModel().selectionChanged.connect(
+ self.on_available_reviewer_selection_changed)
+
+ reviewerfilter = self._qui.reviewer_filter
+ reviewerfilter.textChanged.connect(proxymodel.setFilterFixedString)
+
+ selectedreviewerlist = self._qui.selected_reviewers_list
+ selectedreviewerlist.selectionModel().selectionChanged.connect(
+ self.on_reviewer_selection_changed)
+
callsign = self._ui.config(b'phabricator', b'callsign')
url = self._ui.config(b'phabricator', b'url')
@@ -109,6 +133,10 @@
"""Generate opts for phabsend by form values"""
opts['rev'] = hglib.compactrevs(self._revs)
+ reviewerlist = self._qui.selected_reviewers_list
+ opts['reviewer'] = [reviewerlist.item(i).data(Qt.UserRole + 1)[0]
+ for i in xrange(reviewerlist.count())]
+
return opts
def _isvalid(self):
@@ -181,9 +209,18 @@
if not url:
url = b'Not Configured!'
+ reviewerlist = self._qui.selected_reviewers_list
+ tounicode = hglib.tounicode
+ reviewers = [tounicode(reviewerlist.item(i).data(Qt.UserRole + 1)[0])
+ for i in xrange(reviewerlist.count())]
+ if reviewers:
+ reviewers = u', '.join(reviewers)
+ else:
+ reviewers = u'None'
+
preview.append(u'Server: %s\n' % hglib.tounicode(url))
preview.append(u'Callsign: %s\n' % hglib.tounicode(callsign))
- preview.append(u'Reviewers: None\n')
+ preview.append(u'Reviewers: %s\n' % reviewers)
preview.append(u'\n\n')
preview.append(exported)
@@ -193,6 +230,121 @@
return self._qui.main_tabs.indexOf(self._qui.preview_tab)
@pyqtSlot()
+ def _updateavailablereviewers(self):
+ self._qui.rescan_button.enable()
+ msg = hglib.tounicode(str(self._rescansession.readAll()))
+
+ def _appendavailablereviewers(self, conduitresult):
+ """Process the result of a conduit call, and add reviewers to the
+ available reviewers list.
+ """
+ availablereviewers = self.availablereviewersmodel.sourceModel()
+
+ for user in conduitresult.get('data', {}):
+ fields = user.get('fields', {})
+ realname = fields.get('realName')
+ username = fields.get('username')
+
+ if not realname or not username:
+ continue
+
+ # TODO: filter out deactivated users?
+ # 'roles' contains 'list' for mailing lists
+## "fields": {
+## "dateCreated": 1545361461,
+## "dateModified": 1546812194,
+## "policy": {
+## "edit": "no-one",
+## "view": "public"
+## },
+## "realName": "MattH stduser",
+## "roles": [
+## "disabled", <-- normally "activated"
+## "approved"
+## ],
+## "username": "mharbison"
+
+ item = QStandardItem(u'%s (%s)' % (hglib.tounicode(realname),
+ hglib.tounicode(username)))
+ item.setData((username, realname))
+
+ # Must add to source model since setDynamicSortFilter() is True.
+ availablereviewers.appendRow(item)
+
+ @pyqtSlot()
+ def on_rescan_button_clicked(self):
+ availablereviewers = self.availablereviewersmodel.sourceModel()
+
+ availablereviewers.clear()
+ #self._qui.rescan_button.disable()
+
+ #cmdline = hglib.buildcmdargs(b'debugcallconduit', b'user.search')
+ #self._rescansession = sess = self._repoagent.runCommand(cmdline)
+ #sess.setCaptureOutput(True)
+ #sess.commandFinished.connect(self._updateavailablereviewers)
+ from hgext import phabricator
+ import json
+
+
+ params = json.loads(u'''{
+ "constraints": {
+ "isBot": false
+ }
+ }''')
+
+ # The default behavior is to batch 100 users at a time, and requires a
+ # second call with the 'after' value specified in the cursor of the
+ # response to pick up the rest. 'after' is None at the end of the
+ # sequence. To test the continuation logic, `"limit": 1` can be added
+ # to the request parameters.
+ while True:
+
+ result = phabricator.callconduit(self._repo, b'user.search', params)
+
+ self._appendavailablereviewers(result)
+
+ cursor = result.get('cursor')
+ after = cursor.get('after')
+ if after:
+ params['after'] = after
+ else:
+ break
+
+ @pyqtSlot()
+ def on_available_reviewer_selection_changed(self):
+ view = self._qui.available_reviewer_list
+ self._qui.addreviewer_button.setEnabled(bool(view.selectedIndexes()))
+
+ @pyqtSlot()
+ def on_reviewer_selection_changed(self):
+ view = self._qui.selected_reviewers_list
+ self._qui.removereviewer_button.setEnabled(bool(view.selectedIndexes()))
+
+ @pyqtSlot()
+ def on_addreviewer_button_clicked(self):
+ """Populates the selected reviewers list when the ">" button is clicked.
+ """
+ reviewers = self._qui.selected_reviewers_list
+ proxymodel = self.availablereviewersmodel
+ model = proxymodel.sourceModel()
+
+ for i in self._qui.available_reviewer_list.selectedIndexes():
+ item = model.item(proxymodel.mapToSource(i).row())
+ if not reviewers.findItems(item.text(), Qt.MatchExactly):
+ witem = QListWidgetItem(item.text())
+ witem.setData(Qt.UserRole + 1, item.data())
+ reviewers.addItem(witem)
+
+ @pyqtSlot()
+ def on_removereviewer_button_clicked(self):
+ """Removes items from the selected reviewers list when the "<" button is
+ clicked.
+ """
+ reviewers = self._qui.selected_reviewers_list
+ for i in reviewers.selectedItems():
+ reviewers.takeItem(reviewers.row(i))
+
+ @pyqtSlot()
def on_selectall_button_clicked(self):
self._changesets.selectAll()
diff --git a/tortoisehg/hgqt/phabreview.ui b/tortoisehg/hgqt/phabreview.ui
--- a/tortoisehg/hgqt/phabreview.ui
+++ b/tortoisehg/hgqt/phabreview.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>660</width>
- <height>519</height>
+ <width>818</width>
+ <height>604</height>
</rect>
</property>
<property name="windowTitle">
@@ -37,6 +37,227 @@
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
+ <widget class="QGroupBox" name="reviewers_box">
+ <property name="title">
+ <string>Reviewers</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QGroupBox" name="available_reviewers">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="title">
+ <string>Available</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="2" column="2">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="rescan_button">
+ <property name="toolTip">
+ <string>Fetch the reviewer list from the server</string>
+ </property>
+ <property name="text">
+ <string>Rescan</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0" colspan="3">
+ <widget class="QLineEdit" name="reviewer_filter">
+ <property name="toolTip">
+ <string extracomment="tooltip">Filter the available reviewers</string>
+ </property>
+ <property name="statusTip">
+ <string extracomment="status tip"/>
+ </property>
+ <property name="whatsThis">
+ <string extracomment="whats this?"/>
+ </property>
+ <property name="accessibleName">
+ <string extracomment="accessible name"/>
+ </property>
+ <property name="placeholderText">
+ <string>Reviewer Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="3">
+ <widget class="QListView" name="available_reviewer_list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Reviewers available on the server</string>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="addreviewer_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Chose the selected available reviewers</string>
+ </property>
+ <property name="text">
+ <string>></string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removereviewer_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Remove the selected reviewers</string>
+ </property>
+ <property name="text">
+ <string><</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="selected_reviewers">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string/>
+ </property>
+ <property name="title">
+ <string>Selected</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QListWidget" name="selected_reviewers_list">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>These users will be notified of the review</string>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
<widget class="QGroupBox" name="changesets_box">
<property name="title">
<string>Changesets</string>
@@ -107,6 +328,9 @@
<layout class="QHBoxLayout" name="dialogbuttons_layout">
<item>
<widget class="QPushButton" name="settings_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="toolTip">
<string extracomment="Configure Phabricator settings"/>
</property>
@@ -116,9 +340,6 @@
<property name="default">
<bool>false</bool>
</property>
- <property name="enabled">
- <bool>false</bool>
- </property>
</widget>
</item>
<item>
@@ -169,6 +390,7 @@
<class>QsciScintilla</class>
<extends>QFrame</extends>
<header>Qsci/qsciscintilla.h</header>
+ <container>1</container>
</customwidget>
</customwidgets>
<tabstops>