Dave Borowitz | 8cdc76b | 2018-03-26 10:04:27 -0400 | [diff] [blame] | 1 | /** |
| 2 | * @license |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 3 | * Copyright (C) 2015 The Android Open Source Project |
Dave Borowitz | 8cdc76b | 2018-03-26 10:04:27 -0400 | [diff] [blame] | 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | */ |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 17 | import '../../../scripts/bundled-polymer.js'; |
| 18 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 19 | import '../../shared/gr-account-chip/gr-account-chip.js'; |
| 20 | import '../../shared/gr-button/gr-button.js'; |
| 21 | import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js'; |
| 22 | import '../../../styles/shared-styles.js'; |
| 23 | import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js'; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 24 | import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js'; |
| 25 | import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js'; |
| 26 | import {PolymerElement} from '@polymer/polymer/polymer-element.js'; |
| 27 | import {htmlTemplate} from './gr-reviewer-list_html.js'; |
| 28 | |
| 29 | /** |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 30 | * @extends Polymer.Element |
| 31 | */ |
Tao Zhou | e25546a | 2020-04-14 12:25:25 +0200 | [diff] [blame] | 32 | class GrReviewerList extends GestureEventListeners( |
| 33 | LegacyElementMixin(PolymerElement)) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 34 | static get template() { return htmlTemplate; } |
| 35 | |
| 36 | static get is() { return 'gr-reviewer-list'; } |
| 37 | /** |
| 38 | * Fired when the "Add reviewer..." button is tapped. |
| 39 | * |
| 40 | * @event show-reply-dialog |
| 41 | */ |
| 42 | |
| 43 | static get properties() { |
| 44 | return { |
| 45 | change: Object, |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 46 | serverConfig: Object, |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 47 | disabled: { |
| 48 | type: Boolean, |
| 49 | value: false, |
| 50 | reflectToAttribute: true, |
| 51 | }, |
| 52 | mutable: { |
| 53 | type: Boolean, |
| 54 | value: false, |
| 55 | }, |
| 56 | reviewersOnly: { |
| 57 | type: Boolean, |
| 58 | value: false, |
| 59 | }, |
| 60 | ccsOnly: { |
| 61 | type: Boolean, |
| 62 | value: false, |
| 63 | }, |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 64 | |
| 65 | _displayedReviewers: { |
| 66 | type: Array, |
| 67 | value() { return []; }, |
| 68 | }, |
| 69 | _reviewers: { |
| 70 | type: Array, |
| 71 | value() { return []; }, |
| 72 | }, |
| 73 | _showInput: { |
| 74 | type: Boolean, |
| 75 | value: false, |
| 76 | }, |
| 77 | _addLabel: { |
| 78 | type: String, |
| 79 | computed: '_computeAddLabel(ccsOnly)', |
| 80 | }, |
| 81 | _hiddenReviewerCount: { |
| 82 | type: Number, |
| 83 | computed: '_computeHiddenCount(_reviewers, _displayedReviewers)', |
| 84 | }, |
| 85 | |
| 86 | // Used for testing. |
| 87 | _lastAutocompleteRequest: Object, |
| 88 | _xhrPromise: Object, |
| 89 | }; |
| 90 | } |
| 91 | |
| 92 | static get observers() { |
| 93 | return [ |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 94 | '_reviewersChanged(change.reviewers.*, change.owner, serverConfig)', |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 95 | ]; |
| 96 | } |
Andrew Bonventre | 78792e8 | 2016-03-04 17:48:22 -0500 | [diff] [blame] | 97 | |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 98 | /** |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 99 | * Converts change.permitted_labels to an array of hashes of label keys to |
| 100 | * numeric scores. |
| 101 | * Example: |
| 102 | * [{ |
| 103 | * 'Code-Review': ['-1', ' 0', '+1'] |
| 104 | * }] |
| 105 | * will be converted to |
| 106 | * [{ |
| 107 | * label: 'Code-Review', |
| 108 | * scores: [-1, 0, 1] |
| 109 | * }] |
Tao Zhou | 9a07681 | 2019-12-17 09:59:28 +0100 | [diff] [blame] | 110 | */ |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 111 | _permittedLabelsToNumericScores(labels) { |
| 112 | if (!labels) return []; |
| 113 | return Object.keys(labels).map(label => { |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 114 | return { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 115 | label, |
| 116 | scores: labels[label].map(v => parseInt(v, 10)), |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 117 | }; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 118 | }); |
| 119 | } |
Andrew Bonventre | 78792e8 | 2016-03-04 17:48:22 -0500 | [diff] [blame] | 120 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 121 | /** |
| 122 | * Returns hash of labels to max permitted score. |
| 123 | * |
| 124 | * @param {!Object} change |
| 125 | * @returns {!Object} labels to max permitted scores hash |
| 126 | */ |
| 127 | _getMaxPermittedScores(change) { |
| 128 | return this._permittedLabelsToNumericScores(change.permitted_labels) |
| 129 | .map(({label, scores}) => { |
| 130 | return { |
| 131 | [label]: scores |
| 132 | .map(v => parseInt(v, 10)) |
| 133 | .reduce((a, b) => Math.max(a, b))}; |
| 134 | }) |
| 135 | .reduce((acc, i) => Object.assign(acc, i), {}); |
| 136 | } |
Andrew Bonventre | 78792e8 | 2016-03-04 17:48:22 -0500 | [diff] [blame] | 137 | |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 138 | /** |
| 139 | * Returns max permitted score for reviewer. |
| 140 | * |
| 141 | * @param {!Object} reviewer |
| 142 | * @param {!Object} change |
| 143 | * @param {string} label |
| 144 | * @return {number} |
| 145 | */ |
| 146 | _getReviewerPermittedScore(reviewer, change, label) { |
| 147 | // Note (issue 7874): sometimes the "all" list is not included in change |
| 148 | // detail responses, even when DETAILED_LABELS is included in options. |
| 149 | if (!change.labels[label].all) { return NaN; } |
| 150 | const detailed = change.labels[label].all.filter( |
| 151 | ({_account_id}) => reviewer._account_id === _account_id).pop(); |
| 152 | if (!detailed) { |
Viktar Donich | 14130f1 | 2018-04-18 14:44:25 -0700 | [diff] [blame] | 153 | return NaN; |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 154 | } |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 155 | if (detailed.hasOwnProperty('permitted_voting_range')) { |
| 156 | return detailed.permitted_voting_range.max; |
| 157 | } else if (detailed.hasOwnProperty('value')) { |
| 158 | // If preset, user can vote on the label. |
| 159 | return 0; |
| 160 | } |
| 161 | return NaN; |
| 162 | } |
Viktar Donich | 8de21eb | 2017-11-21 16:17:48 -0800 | [diff] [blame] | 163 | |
Dmitrii Filippov | 6c0b1b1 | 2020-03-18 14:17:56 +0100 | [diff] [blame] | 164 | _computeVoteableText(reviewer, change) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 165 | if (!change || !change.labels) { return ''; } |
| 166 | const maxScores = []; |
| 167 | const maxPermitted = this._getMaxPermittedScores(change); |
| 168 | for (const label of Object.keys(change.labels)) { |
| 169 | const maxScore = |
| 170 | this._getReviewerPermittedScore(reviewer, change, label); |
| 171 | if (isNaN(maxScore) || maxScore < 0) { continue; } |
| 172 | if (maxScore > 0 && maxScore === maxPermitted[label]) { |
| 173 | maxScores.push(`${label}: +${maxScore}`); |
Viktar Donich | 8de21eb | 2017-11-21 16:17:48 -0800 | [diff] [blame] | 174 | } else { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 175 | maxScores.push(`${label}`); |
Viktar Donich | 8de21eb | 2017-11-21 16:17:48 -0800 | [diff] [blame] | 176 | } |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 177 | } |
Dmitrii Filippov | 6c0b1b1 | 2020-03-18 14:17:56 +0100 | [diff] [blame] | 178 | return maxScores.join(', '); |
Dmitrii Filippov | 3fd2b10 | 2019-11-15 16:16:46 +0100 | [diff] [blame] | 179 | } |
| 180 | |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 181 | _reviewersChanged(changeRecord, owner, serverConfig) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 182 | // Polymer 2: check for undefined |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 183 | if ([changeRecord, owner, serverConfig].some(arg => arg === undefined)) { |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 184 | return; |
| 185 | } |
| 186 | |
| 187 | let result = []; |
| 188 | const reviewers = changeRecord.base; |
| 189 | for (const key in reviewers) { |
| 190 | if (this.reviewersOnly && key !== 'REVIEWER') { |
| 191 | continue; |
| 192 | } |
| 193 | if (this.ccsOnly && key !== 'CC') { |
| 194 | continue; |
| 195 | } |
| 196 | if (key === 'REVIEWER' || key === 'CC') { |
| 197 | result = result.concat(reviewers[key]); |
| 198 | } |
| 199 | } |
| 200 | this._reviewers = result |
| 201 | .filter(reviewer => reviewer._account_id != owner._account_id); |
| 202 | |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 203 | const isFirstNameConfigured = serverConfig.accounts |
| 204 | && serverConfig.accounts.default_display_name === 'FIRST_NAME'; |
| 205 | const maxReviewers = isFirstNameConfigured ? 6 : 3; |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 206 | // If there is one or two more than the max reviewers, don't show the |
| 207 | // 'show more' button, because it takes up just as much space. |
Dmitrii Filippov | bcf84d2 | 2020-03-18 15:31:59 +0100 | [diff] [blame] | 208 | if (this._reviewers.length > maxReviewers + 2) { |
| 209 | this._displayedReviewers = this._reviewers.slice(0, maxReviewers); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 210 | } else { |
| 211 | this._displayedReviewers = this._reviewers; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | _computeHiddenCount(reviewers, displayedReviewers) { |
| 216 | // Polymer 2: check for undefined |
| 217 | if ([reviewers, displayedReviewers].some(arg => arg === undefined)) { |
| 218 | return undefined; |
| 219 | } |
| 220 | |
| 221 | return reviewers.length - displayedReviewers.length; |
| 222 | } |
| 223 | |
| 224 | _computeCanRemoveReviewer(reviewer, mutable) { |
| 225 | if (!mutable) { return false; } |
| 226 | |
| 227 | let current; |
| 228 | for (let i = 0; i < this.change.removable_reviewers.length; i++) { |
| 229 | current = this.change.removable_reviewers[i]; |
| 230 | if (current._account_id === reviewer._account_id || |
| 231 | (!reviewer._account_id && current.email === reviewer.email)) { |
| 232 | return true; |
| 233 | } |
| 234 | } |
| 235 | return false; |
| 236 | } |
| 237 | |
| 238 | _handleRemove(e) { |
| 239 | e.preventDefault(); |
| 240 | const target = dom(e).rootTarget; |
| 241 | if (!target.account) { return; } |
| 242 | const accountID = target.account._account_id || target.account.email; |
| 243 | this.disabled = true; |
| 244 | this._xhrPromise = this._removeReviewer(accountID).then(response => { |
| 245 | this.disabled = false; |
| 246 | if (!response.ok) { return response; } |
| 247 | |
| 248 | const reviewers = this.change.reviewers; |
| 249 | |
| 250 | for (const type of ['REVIEWER', 'CC']) { |
| 251 | reviewers[type] = reviewers[type] || []; |
| 252 | for (let i = 0; i < reviewers[type].length; i++) { |
| 253 | if (reviewers[type][i]._account_id == accountID || |
| 254 | reviewers[type][i].email == accountID) { |
| 255 | this.splice('change.reviewers.' + type, i, 1); |
| 256 | break; |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | }) |
| 261 | .catch(err => { |
| 262 | this.disabled = false; |
| 263 | throw err; |
| 264 | }); |
| 265 | } |
| 266 | |
| 267 | _handleAddTap(e) { |
| 268 | e.preventDefault(); |
| 269 | const value = {}; |
| 270 | if (this.reviewersOnly) { |
| 271 | value.reviewersOnly = true; |
| 272 | } |
| 273 | if (this.ccsOnly) { |
| 274 | value.ccsOnly = true; |
| 275 | } |
Tao Zhou | 0a3c986 | 2020-04-08 14:45:37 +0200 | [diff] [blame] | 276 | this.dispatchEvent(new CustomEvent('show-reply-dialog', { |
| 277 | detail: {value}, |
| 278 | composed: true, bubbles: true, |
| 279 | })); |
Dmitrii Filippov | daf0ec9 | 2020-03-17 11:27:28 +0100 | [diff] [blame] | 280 | } |
| 281 | |
| 282 | _handleViewAll(e) { |
| 283 | this._displayedReviewers = this._reviewers; |
| 284 | } |
| 285 | |
| 286 | _removeReviewer(id) { |
| 287 | return this.$.restAPI.removeChangeReviewer(this.change._number, id); |
| 288 | } |
| 289 | |
| 290 | _computeAddLabel(ccsOnly) { |
| 291 | return ccsOnly ? 'Add CC' : 'Add reviewer'; |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | customElements.define(GrReviewerList.is, GrReviewerList); |