| /** |
| * @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. |
| */ |
| |
| // Tags identifying ChangeMessages that move change into WIP state. |
| const WIP_TAGS = [ |
| 'autogenerated:gerrit:newWipPatchSet', |
| 'autogenerated:gerrit:setWorkInProgress', |
| ]; |
| |
| // Tags identifying ChangeMessages that move change out of WIP state. |
| const READY_TAGS = [ |
| 'autogenerated:gerrit:setReadyForReview', |
| ]; |
| |
| /** @polymerBehavior Gerrit.PatchSetBehavior*/ |
| export const PatchSetBehavior = { |
| EDIT_NAME: 'edit', |
| PARENT_NAME: 'PARENT', |
| |
| /** |
| * As patchNum can be either a string (e.g. 'edit', 'PARENT') OR a number, |
| * this function checks for patchNum equality. |
| * |
| * @param {string|number} a |
| * @param {string|number|undefined} b Undefined sometimes because |
| * computeLatestPatchNum can return undefined. |
| * @return {boolean} |
| */ |
| patchNumEquals(a, b) { |
| return a + '' === b + ''; |
| }, |
| |
| /** |
| * Whether the given patch is a numbered parent of a merge (i.e. a negative |
| * number). |
| * |
| * @param {string|number} n |
| * @return {boolean} |
| */ |
| isMergeParent(n) { |
| return (n + '')[0] === '-'; |
| }, |
| |
| /** |
| * Given an object of revisions, get a particular revision based on patch |
| * num. |
| * |
| * @param {Object} revisions The object of revisions given by the API |
| * @param {number|string} patchNum The number index of the revision |
| * @return {Object} The correspondent revision obj from {revisions} |
| */ |
| getRevisionByPatchNum(revisions, patchNum) { |
| for (const rev of Object.values(revisions || {})) { |
| if (PatchSetBehavior.patchNumEquals(rev._number, patchNum)) { |
| return rev; |
| } |
| } |
| }, |
| |
| /** |
| * Find change edit base revision if change edit exists. |
| * |
| * @param {!Array<!Object>} revisions The revisions array. |
| * @return {Object} change edit parent revision or null if change edit |
| * doesn't exist. |
| */ |
| findEditParentRevision(revisions) { |
| const editInfo = |
| revisions.find(info => info._number === |
| PatchSetBehavior.EDIT_NAME); |
| |
| if (!editInfo) { return null; } |
| |
| return revisions.find(info => info._number === editInfo.basePatchNum) || |
| null; |
| }, |
| |
| /** |
| * Find change edit base patch set number if change edit exists. |
| * |
| * @param {!Array<!Object>} revisions The revisions array. |
| * @return {number} Change edit patch set number or -1. |
| */ |
| findEditParentPatchNum(revisions) { |
| const revisionInfo = |
| PatchSetBehavior.findEditParentRevision(revisions); |
| return revisionInfo ? revisionInfo._number : -1; |
| }, |
| |
| /** |
| * Sort given revisions array according to the patch set number, in |
| * descending order. |
| * The sort algorithm is change edit aware. Change edit has patch set number |
| * equals 'edit', but must appear after the patch set it was based on. |
| * Example: change edit is based on patch set 2, and another patch set was |
| * uploaded after change edit creation, the sorted order should be: |
| * 3, edit, 2, 1. |
| * |
| * @param {!Array<!Object>} revisions The revisions array |
| * @return {!Array<!Object>} The sorted {revisions} array |
| */ |
| sortRevisions(revisions) { |
| const editParent = |
| PatchSetBehavior.findEditParentPatchNum(revisions); |
| // Map a normal patchNum to 2 * (patchNum - 1) + 1... I.e. 1 -> 1, |
| // 2 -> 3, 3 -> 5, etc. |
| // Map an edit to the patchNum of parent*2... I.e. edit on 2 -> 4. |
| const num = r => (r._number === PatchSetBehavior.EDIT_NAME ? |
| 2 * editParent : |
| 2 * (r._number - 1) + 1); |
| return revisions.sort((a, b) => num(b) - num(a)); |
| }, |
| |
| /** |
| * Construct a chronological list of patch sets derived from change details. |
| * Each element of this list is an object with the following properties: |
| * |
| * * num {number} The number identifying the patch set |
| * * desc {!string} Optional patch set description |
| * * wip {boolean} If true, this patch set was never subject to review. |
| * * sha {string} hash of the commit |
| * |
| * The wip property is determined by the change's current work_in_progress |
| * property and its log of change messages. |
| * |
| * @param {!Object} change The change details |
| * @return {!Array<!Object>} Sorted list of patch set objects, as described |
| * above |
| */ |
| computeAllPatchSets(change) { |
| if (!change) { return []; } |
| let patchNums = []; |
| if (change.revisions && Object.keys(change.revisions).length) { |
| const revisions = Object.keys(change.revisions) |
| .map(sha => Object.assign({sha}, change.revisions[sha])); |
| patchNums = |
| PatchSetBehavior.sortRevisions(revisions) |
| .map(e => { |
| // TODO(kaspern): Mark which patchset an edit was made on, if an |
| // edit exists -- perhaps with a temporary description. |
| return { |
| num: e._number, |
| desc: e.description, |
| sha: e.sha, |
| }; |
| }); |
| } |
| return PatchSetBehavior._computeWipForPatchSets(change, patchNums); |
| }, |
| |
| /** |
| * Populate the wip properties of the given list of patch sets. |
| * |
| * @param {!Object} change The change details |
| * @param {!Array<!Object>} patchNums Sorted list of patch set objects, as |
| * generated by computeAllPatchSets |
| * @return {!Array<!Object>} The given list of patch set objects, with the |
| * wip property set on each of them |
| */ |
| _computeWipForPatchSets(change, patchNums) { |
| if (!change.messages || !change.messages.length) { |
| return patchNums; |
| } |
| const psWip = {}; |
| let wip = change.work_in_progress; |
| for (let i = 0; i < change.messages.length; i++) { |
| const msg = change.messages[i]; |
| if (WIP_TAGS.includes(msg.tag)) { |
| wip = true; |
| } else if (READY_TAGS.includes(msg.tag)) { |
| wip = false; |
| } |
| if (psWip[msg._revision_number] !== false) { |
| psWip[msg._revision_number] = wip; |
| } |
| } |
| |
| for (let i = 0; i < patchNums.length; i++) { |
| patchNums[i].wip = psWip[patchNums[i].num]; |
| } |
| return patchNums; |
| }, |
| |
| /** @return {number|undefined} */ |
| computeLatestPatchNum(allPatchSets) { |
| if (!allPatchSets || !allPatchSets.length) { return undefined; } |
| if (allPatchSets[0].num === PatchSetBehavior.EDIT_NAME) { |
| return allPatchSets[1].num; |
| } |
| return allPatchSets[0].num; |
| }, |
| |
| /** @return {boolean} */ |
| hasEditBasedOnCurrentPatchSet(allPatchSets) { |
| if (!allPatchSets || allPatchSets.length < 2) { return false; } |
| return allPatchSets[0].num === PatchSetBehavior.EDIT_NAME; |
| }, |
| |
| /** @return {boolean} */ |
| hasEditPatchsetLoaded(patchRangeRecord) { |
| const patchRange = patchRangeRecord.base; |
| if (!patchRange) { return false; } |
| return patchRange.patchNum === PatchSetBehavior.EDIT_NAME || |
| patchRange.basePatchNum === PatchSetBehavior.EDIT_NAME; |
| }, |
| |
| /** |
| * Check whether there is no newer patch than the latest patch that was |
| * available when this change was loaded. |
| * |
| * @return {Promise<!Object>} A promise that yields true if the latest patch |
| * has been loaded, and false if a newer patch has been uploaded in the |
| * meantime. The promise is rejected on network error. |
| */ |
| fetchChangeUpdates(change, restAPI) { |
| const knownLatest = PatchSetBehavior.computeLatestPatchNum( |
| PatchSetBehavior.computeAllPatchSets(change)); |
| return restAPI.getChangeDetail(change._number) |
| .then(detail => { |
| if (!detail) { |
| const error = new Error('Unable to check for latest patchset.'); |
| return Promise.reject(error); |
| } |
| const actualLatest = PatchSetBehavior.computeLatestPatchNum( |
| PatchSetBehavior.computeAllPatchSets(detail)); |
| return { |
| isLatest: actualLatest <= knownLatest, |
| newStatus: change.status !== detail.status ? detail.status : null, |
| newMessages: change.messages.length < detail.messages.length, |
| }; |
| }); |
| }, |
| |
| /** |
| * @param {number|string} patchNum |
| * @param {!Array<!Object>} revisions A sorted array of revisions. |
| * |
| * @return {number} The index of the revision with the given patchNum. |
| */ |
| findSortedIndex(patchNum, revisions) { |
| revisions = revisions || []; |
| const findNum = rev => rev._number + '' === patchNum + ''; |
| return revisions.findIndex(findNum); |
| }, |
| |
| /** |
| * Convert parent indexes from patch range expressions to numbers. |
| * For example, in a patch range expression `"-3"` becomes `3`. |
| * |
| * @param {number|string} rangeBase |
| * @return {number} |
| */ |
| getParentIndex(rangeBase) { |
| return -parseInt(rangeBase + '', 10); |
| }, |
| }; |
| |
| // eslint-disable-next-line no-unused-vars |
| function defineEmptyMixin() { |
| // This is a temporary function. |
| // Polymer linter doesn't process correctly the following code: |
| // class MyElement extends Polymer.mixinBehaviors([legacyBehaviors], ...) {...} |
| // To workaround this issue, the mock mixin is declared in this method. |
| // In the following changes, legacy behaviors will be converted to mixins. |
| |
| /** |
| * @polymer |
| * @mixinFunction |
| */ |
| const PatchSetMixin = base => // eslint-disable-line no-unused-vars |
| class extends base { |
| computeLatestPatchNum(allPatchSets) {} |
| |
| hasEditPatchsetLoaded(patchRangeRecord) {} |
| |
| hasEditBasedOnCurrentPatchSet(allPatchSets) {} |
| |
| computeAllPatchSets(change) {} |
| }; |
| } |
| |
| // TODO(dmfilippov) Remove the following lines with assignments |
| // Plugins can use the behavior because it was accessible with |
| // the global Gerrit... variable. To avoid breaking changes in plugins |
| // temporary assign global variables. |
| window.Gerrit = window.Gerrit || {}; |
| window.Gerrit.PatchSetBehavior = PatchSetBehavior; |