Plugin API for label score updates
Plugin may use following API to receive saved label updates: ``` js plugin.changeMetadata().onLabelsChanged( labels => console.log(labels)); ``` Feature: Issue 820117 Change-Id: Id428606db25086cfdf5a5b420e34777cc8b27303
This commit is contained in:
parent
e137acc455
commit
b6db19e072
16
Documentation/pg-plugin-change-metadata-api.txt
Normal file
16
Documentation/pg-plugin-change-metadata-api.txt
Normal file
@ -0,0 +1,16 @@
|
||||
= Gerrit Code Review - Change metadata plugin API
|
||||
|
||||
This API is provided by
|
||||
link:pg-plugin-dev.html#change-metadata[plugin.changeMetadata()] and provides
|
||||
interface for customization and data updates of change metadata.
|
||||
|
||||
== onLabelsChanged
|
||||
`changeMetadataApi.onLabelsChanged(callback)`
|
||||
|
||||
.Params
|
||||
- *callback* function that's executed when labels changed on the server.
|
||||
Callback receives labels with scores applied to the change, map of the label
|
||||
names to link:rest-api-changes.html#label-info[LabelInfo] entries
|
||||
|
||||
.Returns
|
||||
- `GrChangeMetadataApi` for chaining.
|
@ -341,6 +341,15 @@ screen.
|
||||
|
||||
Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
|
||||
|
||||
=== changeMetadata
|
||||
`plugin.changeMetadata()`
|
||||
|
||||
.Params:
|
||||
- none
|
||||
|
||||
.Returns:
|
||||
- Instance of link:pg-plugin-change-metadata-api.html[GrChangeMetadataApi].
|
||||
|
||||
=== theme
|
||||
`plugin.theme()`
|
||||
|
||||
|
@ -69,6 +69,11 @@ link:rest-api-changes.html#change-info[ChangeInfo]
|
||||
current revision displayed, an instance of
|
||||
link:rest-api-changes.html#revision-info[RevisionInfo]
|
||||
|
||||
* `labels`
|
||||
+
|
||||
labels with scores applied to the change, map of the label names to
|
||||
link:rest-api-changes.html#label-info[LabelInfo] entries
|
||||
|
||||
=== robot-comment-controls
|
||||
The `robot-comment-controls` extension point is located inside each comment
|
||||
rendered on the diff page, and is only visible when the comment is a robot
|
||||
@ -118,4 +123,4 @@ This endpoint wraps the textarea in the reply dialog.
|
||||
This endpoint decorator wraps the voting buttons in the reply dialog.
|
||||
|
||||
=== header-title
|
||||
This endpoint wraps the title-text in the application header.
|
||||
This endpoint wraps the title-text in the application header.
|
||||
|
@ -29,7 +29,7 @@ limitations under the License.
|
||||
|
||||
<test-fixture id="element">
|
||||
<template>
|
||||
<gr-change-metadata></gr-change-metadata>
|
||||
<gr-change-metadata mutable="true"></gr-change-metadata>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
@ -51,20 +51,42 @@ limitations under the License.
|
||||
'section.topic',
|
||||
];
|
||||
|
||||
const labels = {
|
||||
CI: {
|
||||
all: [
|
||||
{value: 1, name: 'user 2', _account_id: 1},
|
||||
{value: 2, name: 'user '},
|
||||
],
|
||||
values: {
|
||||
' 0': 'Don\'t submit as-is',
|
||||
'+1': 'No score',
|
||||
'+2': 'Looks good to me',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const getStyle = function(selector, name) {
|
||||
return window.getComputedStyle(
|
||||
Polymer.dom(element.root).querySelector(selector))[name];
|
||||
};
|
||||
|
||||
function createElement() {
|
||||
const element = fixture('element');
|
||||
element.change = {labels, status: 'NEW'};
|
||||
element.revision = {};
|
||||
return element;
|
||||
}
|
||||
|
||||
setup(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getConfig() { return Promise.resolve({}); },
|
||||
getLoggedIn() { return Promise.resolve(false); },
|
||||
deleteVote() { return Promise.resolve({ok: true}); },
|
||||
});
|
||||
stub('gr-change-metadata', {
|
||||
_computeShowLabelStatus() { return true; },
|
||||
_computeShowReviewersByState() { return true; },
|
||||
ready() {
|
||||
this.change = {labels: [], status: 'NEW'};
|
||||
this.serverConfig = {};
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -76,7 +98,7 @@ limitations under the License.
|
||||
|
||||
suite('by default', () => {
|
||||
setup(done => {
|
||||
element = fixture('element');
|
||||
element = createElement();
|
||||
flush(done);
|
||||
});
|
||||
|
||||
@ -99,7 +121,7 @@ limitations under the License.
|
||||
],
|
||||
},
|
||||
};
|
||||
element = fixture('element');
|
||||
element = createElement();
|
||||
const importSpy = sandbox.spy(element.$.externalStyle, '_import');
|
||||
Gerrit.awaitPluginsLoaded().then(() => {
|
||||
Promise.all(importSpy.returnValues).then(() => {
|
||||
@ -114,5 +136,38 @@ limitations under the License.
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
suite('label updates', () => {
|
||||
let plugin;
|
||||
let labelChangeStub;
|
||||
|
||||
setup(done => {
|
||||
Gerrit.install(p => plugin = p, '0.1',
|
||||
new URL('test/plugin.html?' + Math.random(),
|
||||
window.location.href).toString());
|
||||
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
|
||||
Gerrit._resolveAllPluginsLoaded();
|
||||
element = createElement();
|
||||
sandbox.stub(element, '_computeCanDeleteVote').returns(true);
|
||||
|
||||
labelChangeStub = sandbox.stub();
|
||||
plugin.changeMetadata().onLabelsChanged(labelChangeStub);
|
||||
flush(done);
|
||||
});
|
||||
|
||||
test('labels changed callback', done => {
|
||||
assert.equal(labelChangeStub.callCount, 1);
|
||||
assert.isTrue(labelChangeStub.calledWithExactly(labels));
|
||||
assert.equal(labelChangeStub.args[0][0]['CI'].all.length, 2);
|
||||
MockInteractions.tap(Polymer.dom(element.root).querySelector(
|
||||
'gr-account-chip').$.remove);
|
||||
// Wait for fake rest API response.
|
||||
flush(() => {
|
||||
assert.equal(labelChangeStub.callCount, 2);
|
||||
assert.equal(labelChangeStub.args[1][0]['CI'].all.length, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@ -316,12 +316,12 @@ limitations under the License.
|
||||
</section>
|
||||
</template>
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeLabelNames(change.labels)]]" as="labelName">
|
||||
items="[[_computeLabelNames(labels)]]" as="labelName">
|
||||
<section>
|
||||
<span class="title">[[labelName]]</span>
|
||||
<span class="value">
|
||||
<template is="dom-repeat"
|
||||
items="[[_computeLabelValues(labelName, change.labels.*)]]"
|
||||
items="[[_computeLabelValues(labelName, labels.*)]]"
|
||||
as="label">
|
||||
<div class="labelValueContainer">
|
||||
<span>
|
||||
@ -379,6 +379,7 @@ limitations under the License.
|
||||
</span>
|
||||
</section>
|
||||
<gr-endpoint-decorator name="change-metadata-item">
|
||||
<gr-endpoint-param name="labels" value="[[labels]]"></gr-endpoint-param>
|
||||
<gr-endpoint-param name="change" value="[[change]]"></gr-endpoint-param>
|
||||
<gr-endpoint-param name="revision" value="[[revision]]"></gr-endpoint-param>
|
||||
</gr-endpoint-decorator>
|
||||
|
@ -42,6 +42,10 @@
|
||||
properties: {
|
||||
/** @type {?} */
|
||||
change: Object,
|
||||
labels: {
|
||||
type: Object,
|
||||
notify: true,
|
||||
},
|
||||
/** @type {?} */
|
||||
revision: Object,
|
||||
commitInfo: Object,
|
||||
@ -98,9 +102,14 @@
|
||||
|
||||
observers: [
|
||||
'_changeChanged(change)',
|
||||
'_labelsChanged(change.labels)',
|
||||
'_assigneeChanged(_assignee.*)',
|
||||
],
|
||||
|
||||
_labelsChanged(labels) {
|
||||
this.labels = Object.assign({}, labels) || null;
|
||||
},
|
||||
|
||||
_changeChanged(change) {
|
||||
this._assignee = change.assignee ? [change.assignee] : [];
|
||||
},
|
||||
@ -243,17 +252,22 @@
|
||||
},
|
||||
|
||||
_computeTopicReadOnly(mutable, change) {
|
||||
return !mutable || !change.actions.topic || !change.actions.topic.enabled;
|
||||
return !mutable ||
|
||||
!change.actions ||
|
||||
!change.actions.topic ||
|
||||
!change.actions.topic.enabled;
|
||||
},
|
||||
|
||||
_computeHashtagReadOnly(mutable, change) {
|
||||
return !mutable ||
|
||||
!change.actions ||
|
||||
!change.actions.hashtags ||
|
||||
!change.actions.hashtags.enabled;
|
||||
},
|
||||
|
||||
_computeAssigneeReadOnly(mutable, change) {
|
||||
return !mutable ||
|
||||
!change.actions ||
|
||||
!change.actions.assignee ||
|
||||
!change.actions.assignee.enabled;
|
||||
},
|
||||
@ -283,7 +297,9 @@
|
||||
* change-metadata section is modifiable by the current user.
|
||||
*/
|
||||
_computeCanDeleteVote(reviewer, mutable) {
|
||||
if (!mutable) { return false; }
|
||||
if (!mutable || !this.change || !this.change.removable_reviewers) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.change.removable_reviewers.length; i++) {
|
||||
if (this.change.removable_reviewers[i]._account_id ===
|
||||
reviewer._account_id) {
|
||||
@ -313,6 +329,7 @@
|
||||
if (!response.ok) { return response; }
|
||||
const label = this.change.labels[labelName];
|
||||
const labels = label.all || [];
|
||||
let wasChanged = false;
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
if (labels[i]._account_id === accountID) {
|
||||
for (const key in label) {
|
||||
@ -320,13 +337,18 @@
|
||||
label[key]._account_id === accountID) {
|
||||
// Remove special label field, keeping change label values
|
||||
// in sync with the backend.
|
||||
this.set(['change.labels', labelName, key], null);
|
||||
this.change.labels[labelName][key] = null;
|
||||
wasChanged = true;
|
||||
}
|
||||
}
|
||||
this.splice(['change.labels', labelName, 'all'], i, 1);
|
||||
this.change.labels[labelName].all.splice(i, 1);
|
||||
wasChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (wasChanged) {
|
||||
this.notifyPath('change.labels');
|
||||
}
|
||||
}).catch(err => {
|
||||
target.disabled = false;
|
||||
return;
|
||||
|
@ -523,36 +523,26 @@ limitations under the License.
|
||||
assert.isFalse(button.hasAttribute('hidden'));
|
||||
});
|
||||
|
||||
test('deletes votes', done => {
|
||||
const deleteStub = sandbox.stub(element.$.restAPI, 'deleteVote')
|
||||
.returns(Promise.resolve({ok: true}));
|
||||
test('deletes votes', () => {
|
||||
const deleteResponse = Promise.resolve({ok: true});
|
||||
const deleteStub = sandbox.stub(
|
||||
element.$.restAPI, 'deleteVote').returns(deleteResponse);
|
||||
|
||||
element.change.removable_reviewers = [
|
||||
{
|
||||
_account_id: 1,
|
||||
name: 'bojack',
|
||||
},
|
||||
];
|
||||
element.change.removable_reviewers = [{
|
||||
_account_id: 1,
|
||||
name: 'bojack',
|
||||
}];
|
||||
element.change.labels.test.recommended = {_account_id: 1};
|
||||
element.mutable = true;
|
||||
flushAsynchronousOperations();
|
||||
const chip = element.$$('gr-account-chip');
|
||||
const button = chip.$$('gr-button');
|
||||
|
||||
const spliceStub = sandbox.stub(element, 'splice', (path, index,
|
||||
length) => {
|
||||
assert.isFalse(chip.disabled);
|
||||
assert.deepEqual(path, ['change.labels', 'test', 'all']);
|
||||
assert.equal(index, 0);
|
||||
assert.equal(length, 1);
|
||||
assert.notOk(element.change.labels.test.recommended);
|
||||
assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
|
||||
spliceStub.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
MockInteractions.tap(button);
|
||||
assert.isTrue(chip.disabled);
|
||||
return deleteResponse.then(() => {
|
||||
assert.isFalse(chip.disabled);
|
||||
assert.notOk(element.change.labels.test.recommended);
|
||||
assert.isTrue(deleteStub.calledWithExactly(42, 1, 'test'));
|
||||
});
|
||||
});
|
||||
|
||||
test('changing topic', () => {
|
||||
|
@ -0,0 +1,22 @@
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2018 The Android Open Source Project
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<link rel="import" href="../../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="gr-change-metadata-api">
|
||||
<script src="gr-change-metadata-api.js"></script>
|
||||
</dom-module>
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
function GrChangeMetadataApi(plugin) {
|
||||
this._hook = null;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
GrChangeMetadataApi.prototype._createHook = function() {
|
||||
this._hook = this.plugin.hook('change-metadata-item');
|
||||
};
|
||||
|
||||
GrChangeMetadataApi.prototype.onLabelsChanged = function(callback) {
|
||||
if (!this._hook) {
|
||||
this._createHook();
|
||||
}
|
||||
this._hook.onAttached(element =>
|
||||
this.plugin.attributeHelper(element).bind('labels', callback));
|
||||
return this;
|
||||
};
|
||||
|
||||
window.GrChangeMetadataApi = GrChangeMetadataApi;
|
||||
})(window);
|
@ -20,6 +20,7 @@ limitations under the License.
|
||||
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
|
||||
<link rel="import" href="../../plugins/gr-admin-api/gr-admin-api.html">
|
||||
<link rel="import" href="../../plugins/gr-attribute-helper/gr-attribute-helper.html">
|
||||
<link rel="import" href="../../plugins/gr-change-metadata-api/gr-change-metadata-api.html">
|
||||
<link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
|
||||
<link rel="import" href="../../plugins/gr-event-helper/gr-event-helper.html">
|
||||
<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
|
||||
|
@ -223,6 +223,10 @@
|
||||
return new GrRepoApi(this);
|
||||
};
|
||||
|
||||
Plugin.prototype.changeMetadata = function() {
|
||||
return new GrChangeMetadataApi(this);
|
||||
};
|
||||
|
||||
Plugin.prototype.admin = function() {
|
||||
return new GrAdminApi(this);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user