
Plugin may use following API to receive saved label updates: ``` js plugin.changeMetadata().onLabelsChanged( labels => console.log(labels)); ``` Feature: Issue 820117 Change-Id: Id428606db25086cfdf5a5b420e34777cc8b27303
470 lines
14 KiB
JavaScript
470 lines
14 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright (C) 2016 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() {
|
|
'use strict';
|
|
|
|
const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
|
|
|
|
const SubmitTypeLabel = {
|
|
FAST_FORWARD_ONLY: 'Fast Forward Only',
|
|
MERGE_IF_NECESSARY: 'Merge if Necessary',
|
|
REBASE_IF_NECESSARY: 'Rebase if Necessary',
|
|
MERGE_ALWAYS: 'Always Merge',
|
|
REBASE_ALWAYS: 'Rebase Always',
|
|
CHERRY_PICK: 'Cherry Pick',
|
|
};
|
|
|
|
const NOT_CURRENT_MESSAGE = 'Not current - rebase possible';
|
|
|
|
Polymer({
|
|
is: 'gr-change-metadata',
|
|
|
|
/**
|
|
* Fired when the change topic is changed.
|
|
*
|
|
* @event topic-changed
|
|
*/
|
|
|
|
properties: {
|
|
/** @type {?} */
|
|
change: Object,
|
|
labels: {
|
|
type: Object,
|
|
notify: true,
|
|
},
|
|
/** @type {?} */
|
|
revision: Object,
|
|
commitInfo: Object,
|
|
missingLabels: Array,
|
|
mutable: Boolean,
|
|
/**
|
|
* @type {{ note_db_enabled: string }}
|
|
*/
|
|
serverConfig: Object,
|
|
parentIsCurrent: Boolean,
|
|
_notCurrentMessage: {
|
|
type: String,
|
|
value: NOT_CURRENT_MESSAGE,
|
|
readOnly: true,
|
|
},
|
|
_topicReadOnly: {
|
|
type: Boolean,
|
|
computed: '_computeTopicReadOnly(mutable, change)',
|
|
},
|
|
_hashtagReadOnly: {
|
|
type: Boolean,
|
|
computed: '_computeHashtagReadOnly(mutable, change)',
|
|
},
|
|
_showReviewersByState: {
|
|
type: Boolean,
|
|
computed: '_computeShowReviewersByState(serverConfig)',
|
|
},
|
|
_showLabelStatus: {
|
|
type: Boolean,
|
|
computed: '_computeShowLabelStatus(change)',
|
|
},
|
|
|
|
_assignee: Array,
|
|
_isWip: {
|
|
type: Boolean,
|
|
computed: '_computeIsWip(change)',
|
|
},
|
|
_newHashtag: String,
|
|
|
|
_settingTopic: {
|
|
type: Boolean,
|
|
value: false,
|
|
},
|
|
|
|
_currentParents: {
|
|
type: Array,
|
|
computed: '_computeParents(change)',
|
|
},
|
|
},
|
|
|
|
behaviors: [
|
|
Gerrit.RESTClientBehavior,
|
|
],
|
|
|
|
observers: [
|
|
'_changeChanged(change)',
|
|
'_labelsChanged(change.labels)',
|
|
'_assigneeChanged(_assignee.*)',
|
|
],
|
|
|
|
_labelsChanged(labels) {
|
|
this.labels = Object.assign({}, labels) || null;
|
|
},
|
|
|
|
_changeChanged(change) {
|
|
this._assignee = change.assignee ? [change.assignee] : [];
|
|
},
|
|
|
|
_assigneeChanged(assigneeRecord) {
|
|
if (!this.change) { return; }
|
|
const assignee = assigneeRecord.base;
|
|
if (assignee.length) {
|
|
const acct = assignee[0];
|
|
if (this.change.assignee &&
|
|
acct._account_id === this.change.assignee._account_id) { return; }
|
|
this.set(['change', 'assignee'], acct);
|
|
this.$.restAPI.setAssignee(this.change._number, acct._account_id);
|
|
} else {
|
|
if (!this.change.assignee) { return; }
|
|
this.set(['change', 'assignee'], undefined);
|
|
this.$.restAPI.deleteAssignee(this.change._number);
|
|
}
|
|
},
|
|
|
|
_computeHideStrategy(change) {
|
|
return !this.changeIsOpen(change.status);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} commitInfo
|
|
* @return {?Array} If array is empty, returns null instead so
|
|
* an existential check can be used to hide or show the webLinks
|
|
* section.
|
|
*/
|
|
_computeWebLinks(commitInfo) {
|
|
if (!commitInfo) { return null; }
|
|
const weblinks = Gerrit.Nav.getChangeWeblinks(
|
|
this.change ? this.change.repo : '',
|
|
commitInfo.commit,
|
|
{weblinks: commitInfo.web_links});
|
|
return weblinks.length ? weblinks : null;
|
|
},
|
|
|
|
_computeStrategy(change) {
|
|
return SubmitTypeLabel[change.submit_type];
|
|
},
|
|
|
|
_computeLabelNames(labels) {
|
|
return Object.keys(labels).sort();
|
|
},
|
|
|
|
_computeLabelValues(labelName, _labels) {
|
|
const result = [];
|
|
const labels = _labels.base;
|
|
const labelInfo = labels[labelName];
|
|
if (!labelInfo) { return result; }
|
|
if (!labelInfo.values) {
|
|
if (labelInfo.rejected || labelInfo.approved) {
|
|
const ok = labelInfo.approved || !labelInfo.rejected;
|
|
return [{
|
|
value: ok ? '👍️' : '👎️',
|
|
className: ok ? 'positive' : 'negative',
|
|
account: ok ? labelInfo.approved : labelInfo.rejected,
|
|
}];
|
|
}
|
|
return result;
|
|
}
|
|
const approvals = labelInfo.all || [];
|
|
const values = Object.keys(labelInfo.values);
|
|
for (const label of approvals) {
|
|
if (label.value && label.value != labels[labelName].default_value) {
|
|
let labelClassName;
|
|
let labelValPrefix = '';
|
|
if (label.value > 0) {
|
|
labelValPrefix = '+';
|
|
if (parseInt(label.value, 10) ===
|
|
parseInt(values[values.length - 1], 10)) {
|
|
labelClassName = 'max';
|
|
} else {
|
|
labelClassName = 'positive';
|
|
}
|
|
} else if (label.value < 0) {
|
|
if (parseInt(label.value, 10) === parseInt(values[0], 10)) {
|
|
labelClassName = 'min';
|
|
} else {
|
|
labelClassName = 'negative';
|
|
}
|
|
}
|
|
result.push({
|
|
value: labelValPrefix + label.value,
|
|
className: labelClassName,
|
|
account: label,
|
|
});
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_computeValueTooltip(change, score, labelName) {
|
|
if (!change.labels[labelName] ||
|
|
!change.labels[labelName].values ||
|
|
!change.labels[labelName].values[score]) { return ''; }
|
|
return change.labels[labelName].values[score];
|
|
},
|
|
|
|
_handleTopicChanged(e, topic) {
|
|
const lastTopic = this.change.topic;
|
|
if (!topic.length) { topic = null; }
|
|
this._settingTopic = true;
|
|
this.$.restAPI.setChangeTopic(this.change._number, topic)
|
|
.then(newTopic => {
|
|
this._settingTopic = false;
|
|
this.set(['change', 'topic'], newTopic);
|
|
if (newTopic !== lastTopic) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('topic-changed', {bubbles: true}));
|
|
}
|
|
});
|
|
},
|
|
|
|
_showAddTopic(changeRecord, settingTopic) {
|
|
const hasTopic = !!changeRecord && !!changeRecord.base.topic;
|
|
return !hasTopic && !settingTopic;
|
|
},
|
|
|
|
_showTopicChip(changeRecord, settingTopic) {
|
|
const hasTopic = !!changeRecord && !!changeRecord.base.topic;
|
|
return hasTopic && !settingTopic;
|
|
},
|
|
|
|
_handleHashtagChanged(e) {
|
|
const lastHashtag = this.change.hashtag;
|
|
if (!this._newHashtag.length) { return; }
|
|
const newHashtag = this._newHashtag;
|
|
this._newHashtag = '';
|
|
this.$.restAPI.setChangeHashtag(
|
|
this.change._number, {add: [newHashtag]}).then(newHashtag => {
|
|
this.set(['change', 'hashtags'], newHashtag);
|
|
if (newHashtag !== lastHashtag) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('hashtag-changed', {bubbles: true}));
|
|
}
|
|
});
|
|
},
|
|
|
|
_computeTopicReadOnly(mutable, change) {
|
|
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;
|
|
},
|
|
|
|
_computeTopicPlaceholder(_topicReadOnly) {
|
|
// Action items in Material Design are uppercase -- placeholder label text
|
|
// is sentence case.
|
|
return _topicReadOnly ? 'No topic' : 'ADD TOPIC';
|
|
},
|
|
|
|
_computeHashtagPlaceholder(_hashtagReadOnly) {
|
|
return _hashtagReadOnly ? '' : HASHTAG_ADD_MESSAGE;
|
|
},
|
|
|
|
_computeShowReviewersByState(serverConfig) {
|
|
return !!serverConfig.note_db_enabled;
|
|
},
|
|
|
|
/**
|
|
* A user is able to delete a vote iff the mutable property is true and the
|
|
* reviewer that left the vote exists in the list of removable_reviewers
|
|
* received from the backend.
|
|
*
|
|
* @param {!Object} reviewer An object describing the reviewer that left the
|
|
* vote.
|
|
* @param {boolean} mutable this.mutable describes whether the
|
|
* change-metadata section is modifiable by the current user.
|
|
*/
|
|
_computeCanDeleteVote(reviewer, mutable) {
|
|
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) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Closure annotation for Polymer.prototype.splice is off.
|
|
* For now, supressing annotations.
|
|
*
|
|
* TODO(beckysiegel) submit Polymer PR
|
|
*
|
|
* @suppress {checkTypes} */
|
|
_onDeleteVote(e) {
|
|
e.preventDefault();
|
|
const target = Polymer.dom(e).rootTarget;
|
|
target.disabled = true;
|
|
const labelName = target.labelName;
|
|
const accountID = parseInt(target.getAttribute('data-account-id'), 10);
|
|
this._xhrPromise =
|
|
this.$.restAPI.deleteVote(this.change._number, accountID, labelName)
|
|
.then(response => {
|
|
target.disabled = false;
|
|
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) {
|
|
if (label.hasOwnProperty(key) &&
|
|
label[key]._account_id === accountID) {
|
|
// Remove special label field, keeping change label values
|
|
// in sync with the backend.
|
|
this.change.labels[labelName][key] = null;
|
|
wasChanged = true;
|
|
}
|
|
}
|
|
this.change.labels[labelName].all.splice(i, 1);
|
|
wasChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
if (wasChanged) {
|
|
this.notifyPath('change.labels');
|
|
}
|
|
}).catch(err => {
|
|
target.disabled = false;
|
|
return;
|
|
});
|
|
},
|
|
|
|
_computeShowLabelStatus(change) {
|
|
const isNewChange = change.status === this.ChangeStatus.NEW;
|
|
const hasLabels = Object.keys(change.labels).length > 0;
|
|
return isNewChange && hasLabels;
|
|
},
|
|
|
|
_computeMissingLabelsHeader(missingLabels) {
|
|
return 'Needs label' +
|
|
(missingLabels.length > 1 ? 's' : '') + ':';
|
|
},
|
|
|
|
_showMissingLabels(missingLabels) {
|
|
return !!missingLabels.length;
|
|
},
|
|
|
|
_showMissingRequirements(missingLabels, workInProgress) {
|
|
return workInProgress || this._showMissingLabels(missingLabels);
|
|
},
|
|
|
|
_computeProjectURL(project) {
|
|
return Gerrit.Nav.getUrlForProjectChanges(project);
|
|
},
|
|
|
|
_computeBranchURL(project, branch) {
|
|
return Gerrit.Nav.getUrlForBranch(branch, project,
|
|
this.change.status == this.ChangeStatus.NEW ? 'open' :
|
|
this.change.status.toLowerCase());
|
|
},
|
|
|
|
_computeTopicURL(topic) {
|
|
return Gerrit.Nav.getUrlForTopic(topic);
|
|
},
|
|
|
|
_computeHashtagURL(hashtag) {
|
|
return Gerrit.Nav.getUrlForHashtag(hashtag);
|
|
},
|
|
|
|
_handleTopicRemoved(e) {
|
|
const target = Polymer.dom(e).rootTarget;
|
|
target.disabled = true;
|
|
this.$.restAPI.setChangeTopic(this.change._number, null).then(() => {
|
|
target.disabled = false;
|
|
this.set(['change', 'topic'], '');
|
|
this.dispatchEvent(
|
|
new CustomEvent('topic-changed', {bubbles: true}));
|
|
}).catch(err => {
|
|
target.disabled = false;
|
|
return;
|
|
});
|
|
},
|
|
|
|
_handleHashtagRemoved(e) {
|
|
e.preventDefault();
|
|
const target = Polymer.dom(e).rootTarget;
|
|
target.disabled = true;
|
|
this.$.restAPI.setChangeHashtag(this.change._number,
|
|
{remove: [target.text]})
|
|
.then(newHashtag => {
|
|
target.disabled = false;
|
|
this.set(['change', 'hashtags'], newHashtag);
|
|
}).catch(err => {
|
|
target.disabled = false;
|
|
return;
|
|
});
|
|
},
|
|
|
|
_computeIsWip(change) {
|
|
return !!change.work_in_progress;
|
|
},
|
|
|
|
_computeShowUploaderHide(change) {
|
|
return this._computeShowUploader(change) ? '' : 'hideDisplay';
|
|
},
|
|
|
|
_computeShowUploader(change) {
|
|
if (!change.current_revision ||
|
|
!change.revisions[change.current_revision]) {
|
|
return null;
|
|
}
|
|
|
|
const rev = change.revisions[change.current_revision];
|
|
|
|
if (!rev || !rev.uploader ||
|
|
change.owner._account_id === rev.uploader._account_id) {
|
|
return null;
|
|
}
|
|
|
|
return rev.uploader;
|
|
},
|
|
|
|
_computeParents(change) {
|
|
if (!change.current_revision ||
|
|
!change.revisions[change.current_revision] ||
|
|
!change.revisions[change.current_revision].commit) {
|
|
return undefined;
|
|
}
|
|
return change.revisions[change.current_revision].commit.parents;
|
|
},
|
|
|
|
_computeParentsLabel(parents) {
|
|
return parents.length > 1 ? 'Parents' : 'Parent';
|
|
},
|
|
|
|
_computeParentListClass(parents, parentIsCurrent) {
|
|
return [
|
|
'parentList',
|
|
parents.length > 1 ? 'merge' : 'nonMerge',
|
|
parentIsCurrent ? 'current' : 'notCurrent',
|
|
].join(' ');
|
|
},
|
|
});
|
|
})();
|