blob: 806e1476d2f2ec392560f8db77a70312bac6c0d1 [file] [log] [blame]
/**
* @license
* Copyright (C) 2015 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.
*/
import '../../../styles/shared-styles.js';
import '../../shared/gr-dropdown-list/gr-dropdown-list.js';
import '../../shared/gr-select/gr-select.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
import {mixinBehaviors} from '@polymer/polymer/lib/legacy/class.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
import {htmlTemplate} from './gr-patch-range-select_html.js';
import {PatchSetBehavior} from '../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.js';
import {GrCountStringFormatter} from '../../shared/gr-count-string-formatter/gr-count-string-formatter.js';
import {appContext} from '../../../services/app-context.js';
// Maximum length for patch set descriptions.
const PATCH_DESC_MAX_LENGTH = 500;
/**
* Fired when the patch range changes
*
* @event patch-range-change
*
* @property {string} patchNum
* @property {string} basePatchNum
* @extends PolymerElement
*/
class GrPatchRangeSelect extends mixinBehaviors( [
PatchSetBehavior,
], GestureEventListeners(
LegacyElementMixin(
PolymerElement))) {
static get template() { return htmlTemplate; }
static get is() { return 'gr-patch-range-select'; }
static get properties() {
return {
availablePatches: Array,
_baseDropdownContent: {
type: Object,
computed: '_computeBaseDropdownContent(availablePatches, patchNum,' +
'_sortedRevisions, changeComments, revisionInfo)',
},
_patchDropdownContent: {
type: Object,
computed: '_computePatchDropdownContent(availablePatches,' +
'basePatchNum, _sortedRevisions, changeComments)',
},
changeNum: String,
changeComments: Object,
/** @type {{ meta_a: !Array, meta_b: !Array}} */
filesWeblinks: Object,
patchNum: String,
basePatchNum: String,
revisions: Object,
revisionInfo: Object,
_sortedRevisions: Array,
};
}
static get observers() {
return [
'_updateSortedRevisions(revisions.*)',
];
}
constructor() {
super();
this.reporting = appContext.reportingService;
}
_getShaForPatch(patch) {
return patch.sha.substring(0, 10);
}
_computeBaseDropdownContent(availablePatches, patchNum, _sortedRevisions,
changeComments, revisionInfo) {
// Polymer 2: check for undefined
if ([
availablePatches,
patchNum,
_sortedRevisions,
changeComments,
revisionInfo,
].includes(undefined)) {
return undefined;
}
const parentCounts = revisionInfo.getParentCountMap();
const currentParentCount = parentCounts.hasOwnProperty(patchNum) ?
parentCounts[patchNum] : 1;
const maxParents = revisionInfo.getMaxParents();
const isMerge = currentParentCount > 1;
const dropdownContent = [];
for (const basePatch of availablePatches) {
const basePatchNum = basePatch.num;
const entry = this._createDropdownEntry(basePatchNum, 'Patchset ',
_sortedRevisions, changeComments, this._getShaForPatch(basePatch));
dropdownContent.push(Object.assign({}, entry, {
disabled: this._computeLeftDisabled(
basePatch.num, patchNum, _sortedRevisions),
}));
}
dropdownContent.push({
text: isMerge ? 'Auto Merge' : 'Base',
value: 'PARENT',
});
for (let idx = 0; isMerge && idx < maxParents; idx++) {
dropdownContent.push({
disabled: idx >= currentParentCount,
triggerText: `Parent ${idx + 1}`,
text: `Parent ${idx + 1}`,
mobileText: `Parent ${idx + 1}`,
value: -(idx + 1),
});
}
return dropdownContent;
}
_computeMobileText(patchNum, changeComments, revisions) {
return `${patchNum}` +
`${this._computePatchSetCommentsString(changeComments, patchNum)}` +
`${this._computePatchSetDescription(revisions, patchNum, true)}`;
}
_computePatchDropdownContent(availablePatches, basePatchNum,
_sortedRevisions, changeComments) {
// Polymer 2: check for undefined
if ([
availablePatches,
basePatchNum,
_sortedRevisions,
changeComments,
].includes(undefined)) {
return undefined;
}
const dropdownContent = [];
for (const patch of availablePatches) {
const patchNum = patch.num;
const entry = this._createDropdownEntry(
patchNum, patchNum === 'edit' ? '' : 'Patchset ', _sortedRevisions,
changeComments, this._getShaForPatch(patch));
dropdownContent.push(Object.assign({}, entry, {
disabled: this._computeRightDisabled(basePatchNum, patchNum,
_sortedRevisions),
}));
}
return dropdownContent;
}
_computeText(patchNum, prefix, changeComments, sha) {
return `${prefix}${patchNum}` +
`${this._computePatchSetCommentsString(changeComments, patchNum)}` +
(` | ${sha}`);
}
_createDropdownEntry(patchNum, prefix, sortedRevisions, changeComments,
sha) {
const entry = {
triggerText: `${prefix}${patchNum}`,
text: this._computeText(patchNum, prefix, changeComments, sha),
mobileText: this._computeMobileText(patchNum, changeComments,
sortedRevisions),
bottomText: `${this._computePatchSetDescription(
sortedRevisions, patchNum)}`,
value: patchNum,
};
const date = this._computePatchSetDate(sortedRevisions, patchNum);
if (date) {
entry['date'] = date;
}
return entry;
}
_updateSortedRevisions(revisionsRecord) {
const revisions = revisionsRecord.base;
this._sortedRevisions = this.sortRevisions(Object.values(revisions));
}
/**
* The basePatchNum should always be <= patchNum -- because sortedRevisions
* is sorted in reverse order (higher patchset nums first), invalid base
* patch nums have an index greater than the index of patchNum.
*
* @param {number|string} basePatchNum The possible base patch num.
* @param {number|string} patchNum The current selected patch num.
* @param {!Array} sortedRevisions
*/
_computeLeftDisabled(basePatchNum, patchNum, sortedRevisions) {
return this.findSortedIndex(basePatchNum, sortedRevisions) <=
this.findSortedIndex(patchNum, sortedRevisions);
}
/**
* The basePatchNum should always be <= patchNum -- because sortedRevisions
* is sorted in reverse order (higher patchset nums first), invalid patch
* nums have an index greater than the index of basePatchNum.
*
* In addition, if the current basePatchNum is 'PARENT', all patchNums are
* valid.
*
* If the current basePatchNum is a parent index, then only patches that have
* at least that many parents are valid.
*
* @param {number|string} basePatchNum The current selected base patch num.
* @param {number|string} patchNum The possible patch num.
* @param {!Array} sortedRevisions
* @return {boolean}
*/
_computeRightDisabled(basePatchNum, patchNum, sortedRevisions) {
if (this.patchNumEquals(basePatchNum, 'PARENT')) { return false; }
if (this.isMergeParent(basePatchNum)) {
// Note: parent indices use 1-offset.
return this.revisionInfo.getParentCount(patchNum) <
this.getParentIndex(basePatchNum);
}
return this.findSortedIndex(basePatchNum, sortedRevisions) <=
this.findSortedIndex(patchNum, sortedRevisions);
}
_computePatchSetCommentsString(changeComments, patchNum) {
if (!changeComments) { return; }
const commentCount = changeComments.computeCommentCount({patchNum});
const commentString = GrCountStringFormatter.computePluralString(
commentCount, 'comment');
const unresolvedCount = changeComments.computeUnresolvedNum({patchNum});
const unresolvedString = GrCountStringFormatter.computeString(
unresolvedCount, 'unresolved');
if (!commentString.length && !unresolvedString.length) {
return '';
}
return ` (${commentString}` +
// Add a comma + space if both comments and unresolved
(commentString && unresolvedString ? ', ' : '') +
`${unresolvedString})`;
}
/**
* @param {!Array} revisions
* @param {number|string} patchNum
* @param {boolean=} opt_addFrontSpace
*/
_computePatchSetDescription(revisions, patchNum, opt_addFrontSpace) {
const rev = this.getRevisionByPatchNum(revisions, patchNum);
return (rev && rev.description) ?
(opt_addFrontSpace ? ' ' : '') +
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
}
/**
* @param {!Array} revisions
* @param {number|string} patchNum
*/
_computePatchSetDate(revisions, patchNum) {
const rev = this.getRevisionByPatchNum(revisions, patchNum);
return rev ? rev.created : undefined;
}
/**
* Catches value-change events from the patchset dropdowns and determines
* whether or not a patch change event should be fired.
*/
_handlePatchChange(e) {
const detail = {patchNum: this.patchNum, basePatchNum: this.basePatchNum};
const target = dom(e).localTarget;
const latestPatchNum = this.computeLatestPatchNum(this.availablePatches);
if (target === this.$.patchNumDropdown) {
if (detail.patchNum === e.detail.value) return;
this.reporting.reportInteraction('right-patchset-changed',
{
previous: detail.patchNum,
current: e.detail.value,
latest: latestPatchNum,
});
detail.patchNum = e.detail.value;
} else {
if (this.patchNumEquals(detail.basePatchNum, e.detail.value)) return;
this.reporting.reportInteraction('left-patchset-changed',
{previous: detail.basePatchNum, current: e.detail.value});
detail.basePatchNum = e.detail.value;
}
this.dispatchEvent(
new CustomEvent('patch-range-change', {detail, bubbles: false}));
}
}
customElements.define(GrPatchRangeSelect.is, GrPatchRangeSelect);