|  | /** | 
|  | * @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'; | 
|  |  | 
|  | // 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 | 
|  | */ | 
|  |  | 
|  | Polymer({ | 
|  | is: 'gr-patch-range-select', | 
|  |  | 
|  | properties: { | 
|  | 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, | 
|  | }, | 
|  |  | 
|  | observers: [ | 
|  | '_updateSortedRevisions(revisions.*)', | 
|  | ], | 
|  |  | 
|  | behaviors: [Gerrit.PatchSetBehavior], | 
|  |  | 
|  | _computeBaseDropdownContent(availablePatches, patchNum, _sortedRevisions, | 
|  | changeComments, revisionInfo) { | 
|  | 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); | 
|  | 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) { | 
|  | const dropdownContent = []; | 
|  | for (const patch of availablePatches) { | 
|  | const patchNum = patch.num; | 
|  | const entry = this._createDropdownEntry( | 
|  | patchNum, patchNum === 'edit' ? '' : 'Patchset ', _sortedRevisions, | 
|  | changeComments); | 
|  | dropdownContent.push(Object.assign({}, entry, { | 
|  | disabled: this._computeRightDisabled(basePatchNum, patchNum, | 
|  | _sortedRevisions), | 
|  | })); | 
|  | } | 
|  | return dropdownContent; | 
|  | }, | 
|  |  | 
|  | _createDropdownEntry(patchNum, prefix, sortedRevisions, changeComments) { | 
|  | const entry = { | 
|  | triggerText: `${prefix}${patchNum}`, | 
|  | text: `${prefix}${patchNum}` + | 
|  | `${this._computePatchSetCommentsString( | 
|  | changeComments, patchNum)}`, | 
|  | 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 curent 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 = Polymer.dom(e).localTarget; | 
|  |  | 
|  | if (target === this.$.patchNumDropdown) { | 
|  | detail.patchNum = e.detail.value; | 
|  | } else { | 
|  | detail.basePatchNum = e.detail.value; | 
|  | } | 
|  |  | 
|  | this.dispatchEvent( | 
|  | new CustomEvent('patch-range-change', {detail, bubbles: false})); | 
|  | }, | 
|  | }); | 
|  | })(); |