Skip to content
Snippets Groups Projects
Commit 37def2aa authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Gerrit: Differentiate by approval type.


- Parse approval type and description.
- Format tooltip as HTML table, listing approvals by type
- Format the approval column similar to the web view by
  type character.
- Fix the special character issue in Qt 4.8 (query tool
  actually outputs UTF8 instead of escapes).

Change-Id: I803f289a03c5a8ede0029d972a19381b5e783d36
Reviewed-by: default avatarOrgad Shaneh <orgads@gmail.com>
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
parent d8a0b025
No related branches found
No related tags found
No related merge requests found
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include <QUrl> #include <QUrl>
#include <QTextStream> #include <QTextStream>
#include <QDesktopServices> #include <QDesktopServices>
#include <QTextStream>
#include <QDebug> #include <QDebug>
#include <QScopedPointer> #include <QScopedPointer>
#if QT_VERSION >= 0x050000 #if QT_VERSION >= 0x050000
...@@ -63,6 +64,33 @@ enum { debug = 0 }; ...@@ -63,6 +64,33 @@ enum { debug = 0 };
namespace Gerrit { namespace Gerrit {
namespace Internal { namespace Internal {
QDebug operator<<(QDebug d, const GerritApproval &a)
{
d.nospace() << a.reviewer << " :" << a.approval << " ("
<< a.type << ", " << a.description << ')';
return d;
}
// Sort approvals by type and reviewer
bool gerritApprovalLessThan(const GerritApproval &a1, const GerritApproval &a2)
{
return a1.type.compare(a2.type) < 0 || a1.reviewer.compare(a2.reviewer) < 0;
}
QDebug operator<<(QDebug d, const GerritPatchSet &p)
{
d.nospace() << " Patch set: " << p.ref << ' ' << p.patchSetNumber
<< ' ' << p.approvals;
return d;
}
QDebug operator<<(QDebug d, const GerritChange &c)
{
d.nospace() << c.title << " by " << c.email
<< ' ' << c.lastUpdated << ' ' << c.currentPatchSet;
return d;
}
// Format default Url for a change // Format default Url for a change
static inline QString defaultUrl(const QSharedPointer<GerritParameters> &p, int gerritNumber) static inline QString defaultUrl(const QSharedPointer<GerritParameters> &p, int gerritNumber)
{ {
...@@ -73,59 +101,108 @@ static inline QString defaultUrl(const QSharedPointer<GerritParameters> &p, int ...@@ -73,59 +101,108 @@ static inline QString defaultUrl(const QSharedPointer<GerritParameters> &p, int
return result; return result;
} }
// Format approvals as "John Doe: -1, ...". // Format (sorted) approvals as separate HTML table
// lines by type listing the revievers:
// "<tr><td>Code Review</td><td>John Doe: -1, ...</tr><tr>...Sanity Review: ...".
QString GerritPatchSet::approvalsToolTip() const QString GerritPatchSet::approvalsToolTip() const
{ {
if (approvals.isEmpty())
return QString();
QString result; QString result;
if (const int approvalsCount = approvals.size()) { QTextStream str(&result);
const QString sepComma = QLatin1String(", "); QString lastType;
const QString sepColon = QLatin1String(": "); foreach (const GerritApproval &a, approvals) {
for (int i = 0; i < approvalsCount; ++ i) { if (a.type != lastType) {
if (i) if (!lastType.isEmpty())
result += sepComma; str << "</tr>\n";
result += approvals.at(i).first; str << "<tr><td>"
result += sepColon; << (a.description.isEmpty() ? a.type : a.description)
result += QString::number(approvals.at(i).second); << "</td><td>";
lastType = a.type;
} else {
str << ", ";
} }
str << a.reviewer << ": " << a.approval;
}
str << "</tr>\n";
return result;
}
// Determine total approval level. Negative values take preference
// and stay.
static inline void applyApproval(int approval, int *total)
{
if (approval < *total || (*total >= 0 && approval > *total))
*total = approval;
}
// Format the approvals similar to the columns in the Web view
// by a type character followed by the approval level: "C: -2, S: 1"
QString GerritPatchSet::approvalsColumn() const
{
typedef QMap<QChar, int> TypeReviewMap;
typedef TypeReviewMap::iterator TypeReviewMapIterator;
typedef TypeReviewMap::const_iterator TypeReviewMapConstIterator;
QString result;
if (approvals.isEmpty())
return result;
TypeReviewMap reviews; // Sort approvals into a map by type character
foreach (const GerritApproval &a, approvals) {
if (a.type != QLatin1String("STGN")) { // Qt-Project specific: Ignore "STGN" (Staged)
const QChar typeChar = a.type.at(0);
TypeReviewMapIterator it = reviews.find(typeChar);
if (it == reviews.end())
it = reviews.insert(typeChar, 0);
applyApproval(a.approval, &it.value());
}
}
QTextStream str(&result);
const TypeReviewMapConstIterator cend = reviews.constEnd();
for (TypeReviewMapConstIterator it = reviews.constBegin(); it != cend; ++it) {
if (!result.isEmpty())
str << ' ';
str << it.key() << ": " << it.value();
} }
return result; return result;
} }
bool GerritPatchSet::hasApproval(const QString &userName) const bool GerritPatchSet::hasApproval(const QString &userName) const
{ {
foreach (const Approval &a, approvals) foreach (const GerritApproval &a, approvals)
if (a.first == userName) if (a.reviewer == userName)
return true; return true;
return false; return false;
} }
/* Return the approval level: Negative values take preference. */
int GerritPatchSet::approvalLevel() const int GerritPatchSet::approvalLevel() const
{ {
if (approvals.isEmpty()) int value = 0;
return 0; foreach (const GerritApproval &a, approvals)
applyApproval(a.approval, &value);
int maxLevel = -3; return value;
int minLevel = 3;
foreach (const Approval &a, approvals) {
if (a.second > maxLevel)
maxLevel = a.second;
if (a.second < minLevel)
minLevel = a.second;
}
return minLevel < 0 ? minLevel : maxLevel;
} }
QString GerritChange::toolTip() const QString GerritChange::toolTip() const
{ {
static const QString format = GerritModel::tr( static const QString format = GerritModel::tr(
"Subject: %1\nNumber: %2 Id: %3\nOwner: %4 <%5>\n" "<html><head/><body><table>"
"Project: %6 Branch: %7 Patch set: %8\nStatus: %9, %10\n" "<tr><td>Subject</td><td>%1</td></tr>"
"URL: %11\nApprovals: %12"); "<tr><td>Number</td><td>%2"
return format.arg(title).arg(number).arg(id, owner, email, project, branch) "<tr><td>Owner</td><td>%3 &lt;%4&gt;</td></tr>"
"<tr><td>Project</td><td>%5 (%6)</td></tr>"
"<tr><td>Status</td><td>%7, %8</td></tr>"
"<tr><td>Patch set</td><td>%9</td></tr>"
"%10"
"<tr><td>URL</td><td>%11</td></tr>"
"</table></body></html>");
return format.arg(title).arg(number).arg(owner, email, project, branch)
.arg(status, lastUpdated.toString(Qt::DefaultLocaleShortDate))
.arg(currentPatchSet.patchSetNumber) .arg(currentPatchSet.patchSetNumber)
.arg(status, lastUpdated.toString(Qt::DefaultLocaleShortDate), .arg(currentPatchSet.approvalsToolTip(), url);
url, currentPatchSet.approvalsToolTip());
} }
QString GerritChange::filterString() const QString GerritChange::filterString() const
...@@ -134,9 +211,9 @@ QString GerritChange::filterString() const ...@@ -134,9 +211,9 @@ QString GerritChange::filterString() const
QString result = QString::number(number) + blank + title + blank QString result = QString::number(number) + blank + title + blank
+ owner + blank + project + blank + owner + blank + project + blank
+ branch + blank + status; + branch + blank + status;
foreach (const GerritPatchSet::Approval &a, currentPatchSet.approvals) { foreach (const GerritApproval &a, currentPatchSet.approvals) {
result += blank; result += blank;
result += a.first; result += a.reviewer;
} }
return result; return result;
} }
...@@ -428,6 +505,9 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -428,6 +505,9 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
const QString approvalsByKey = QLatin1String("by"); const QString approvalsByKey = QLatin1String("by");
const QString lastUpdatedKey = QLatin1String("lastUpdated"); const QString lastUpdatedKey = QLatin1String("lastUpdated");
const QList<QByteArray> lines = output.split('\n'); const QList<QByteArray> lines = output.split('\n');
const QString approvalsTypeKey = QLatin1String("type");
const QString approvalsDescriptionKey = QLatin1String("description");
QList<GerritChangePtr> result; QList<GerritChangePtr> result;
result.reserve(lines.size()); result.reserve(lines.size());
...@@ -454,10 +534,16 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -454,10 +534,16 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
const int ac = approvalsJ.size(); const int ac = approvalsJ.size();
for (int a = 0; a < ac; ++a) { for (int a = 0; a < ac; ++a) {
const QJsonObject ao = approvalsJ.at(a).toObject(); const QJsonObject ao = approvalsJ.at(a).toObject();
const int value = ao.value(approvalsValueKey).toString().toInt(); GerritApproval a;
const QString by = ao.value(approvalsByKey).toObject().value(ownerNameKey).toString(); a.reviewer = ao.value(approvalsByKey).toObject().value(ownerNameKey).toString();
change->currentPatchSet.approvals.push_back(GerritChange::Approval(by, value)); a.approval = ao.value(approvalsValueKey).toString().toInt();
a.type = ao.value(approvalsTypeKey).toString();
a.description = ao.value(approvalsDescriptionKey).toString();
change->currentPatchSet.approvals.push_back(a);
} }
qStableSort(change->currentPatchSet.approvals.begin(),
change->currentPatchSet.approvals.end(),
gerritApprovalLessThan);
// Remaining // Remaining
change->number = object.value(numberKey).toString().toInt(); change->number = object.value(numberKey).toString().toInt();
change->url = object.value(urlKey).toString(); change->url = object.value(urlKey).toString();
...@@ -531,6 +617,8 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -531,6 +617,8 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
const QString approvalsKey = QLatin1String("approvals"); const QString approvalsKey = QLatin1String("approvals");
const QString approvalsValueKey = QLatin1String("value"); const QString approvalsValueKey = QLatin1String("value");
const QString approvalsByKey = QLatin1String("by"); const QString approvalsByKey = QLatin1String("by");
const QString approvalsTypeKey = QLatin1String("type");
const QString approvalsDescriptionKey = QLatin1String("description");
const QString lastUpdatedKey = QLatin1String("lastUpdated"); const QString lastUpdatedKey = QLatin1String("lastUpdated");
QList<GerritChangePtr> result; QList<GerritChangePtr> result;
result.reserve(lines.size()); result.reserve(lines.size());
...@@ -538,7 +626,7 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -538,7 +626,7 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
foreach (const QByteArray &line, lines) { foreach (const QByteArray &line, lines) {
if (line.isEmpty()) if (line.isEmpty())
continue; continue;
QScopedPointer<Utils::JsonValue> objectValue(Utils::JsonValue::create(QString::fromAscii(line))); QScopedPointer<Utils::JsonValue> objectValue(Utils::JsonValue::create(QString::fromUtf8(line)));
if (objectValue.isNull()) { if (objectValue.isNull()) {
qWarning("Parse error: '%s'", line.constData()); qWarning("Parse error: '%s'", line.constData());
continue; continue;
...@@ -569,8 +657,16 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -569,8 +657,16 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
QTC_ASSERT(byO, break ); QTC_ASSERT(byO, break );
by = jsonStringMember(byO, ownerNameKey); by = jsonStringMember(byO, ownerNameKey);
} }
change->currentPatchSet.approvals.push_back(GerritChange::Approval(by, value)); GerritApproval a;
a.reviewer = by;
a.approval = value;
a.type = jsonStringMember(oc, approvalsTypeKey);
a.description = jsonStringMember(oc, approvalsDescriptionKey);
change->currentPatchSet.approvals.push_back(a);
} }
qStableSort(change->currentPatchSet.approvals.begin(),
change->currentPatchSet.approvals.end(),
gerritApprovalLessThan);
} }
} // patch set } // patch set
// Remaining // Remaining
...@@ -599,6 +695,11 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters> ...@@ -599,6 +695,11 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
qWarning("%s: Parse error in line '%s'.", Q_FUNC_INFO, line.constData()); qWarning("%s: Parse error in line '%s'.", Q_FUNC_INFO, line.constData());
} }
} }
if (debug) {
qDebug() << __FUNCTION__;
foreach (const GerritChangePtr &p, result)
qDebug() << *p;
}
return result; return result;
} }
#endif // QT_VERSION < 0x050000 #endif // QT_VERSION < 0x050000
...@@ -641,13 +742,7 @@ void GerritModel::queryFinished(const QByteArray &output) ...@@ -641,13 +742,7 @@ void GerritModel::queryFinished(const QByteArray &output)
project += QLatin1String(" (") + c->branch + QLatin1Char(')'); project += QLatin1String(" (") + c->branch + QLatin1Char(')');
row[ProjectColumn]->setText(project); row[ProjectColumn]->setText(project);
row[StatusColumn]->setText(c->status); row[StatusColumn]->setText(c->status);
QString approvals; row[ApprovalsColumn]->setText(c->currentPatchSet.approvalsColumn());
foreach (const GerritChange::Approval &a, c->currentPatchSet.approvals) {
if (!approvals.isEmpty())
approvals.append(QLatin1String(", "));
approvals.append(QString::number(a.second));
}
row[ApprovalsColumn]->setText(approvals);
// Mark changes awaiting action using a bold font. // Mark changes awaiting action using a bold font.
bool bold = false; bool bold = false;
switch (m_query->currentQuery()) { switch (m_query->currentQuery()) {
......
...@@ -48,25 +48,32 @@ namespace Internal { ...@@ -48,25 +48,32 @@ namespace Internal {
class GerritParameters; class GerritParameters;
class QueryContext; class QueryContext;
class GerritPatchSet { class GerritApproval {
public: public:
typedef QPair<QString, int> Approval; GerritApproval() : approval(-1) {}
QString type; // Review type
QString description; // Type description, possibly empty
QString reviewer;
int approval;
};
class GerritPatchSet {
public:
GerritPatchSet() : patchSetNumber(1) {} GerritPatchSet() : patchSetNumber(1) {}
QString approvalsToolTip() const; QString approvalsToolTip() const;
QString approvalsColumn() const;
bool hasApproval(const QString &userName) const; bool hasApproval(const QString &userName) const;
int approvalLevel() const; int approvalLevel() const;
QString ref; QString ref;
int patchSetNumber; int patchSetNumber;
QList<Approval> approvals; QList<GerritApproval> approvals;
}; };
class GerritChange class GerritChange
{ {
public: public:
typedef QPair<QString, int> Approval;
GerritChange() : number(0) {} GerritChange() : number(0) {}
bool isValid() const { return number && !url.isEmpty() && !project.isEmpty(); } bool isValid() const { return number && !url.isEmpty() && !project.isEmpty(); }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment