Convert gr-reviewer-list to typescript
Change-Id: I1f77106d7cdb3a3de694894dd32912e32ebcf221
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index 174bbbb..70e7ba7 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -14,87 +14,97 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../shared/gr-account-chip/gr-account-chip.js';
-import '../../shared/gr-button/gr-button.js';
-import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js';
-import '../../../styles/shared-styles.js';
-import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.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-reviewer-list_html.js';
+import '../../shared/gr-account-chip/gr-account-chip';
+import '../../shared/gr-button/gr-button';
+import '../../shared/gr-rest-api-interface/gr-rest-api-interface';
+import '../../../styles/shared-styles';
+import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-reviewer-list_html';
+import {hasAttention, isServiceUser} from '../../../utils/account-util';
+import {customElement, property, computed, observe} from '@polymer/decorators';
import {
- hasAttention,
- isServiceUser,
-} from '../../../utils/account-util.js';
+ ChangeInfo,
+ ServerInfo,
+ LabelNameToValueMap,
+ AccountInfo,
+ ApprovalInfo,
+ Reviewers,
+ AccountId,
+ DetailedLabelInfo,
+} from '../../../types/common';
+import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
+import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
+import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api';
+import {hasOwnProperty} from '../../../utils/common-util';
-/**
- * @extends PolymerElement
- */
-class GrReviewerList extends GestureEventListeners(
- LegacyElementMixin(PolymerElement)) {
- static get template() { return htmlTemplate; }
+export interface GrReviewerList {
+ $: {
+ restAPI: RestApiService & Element;
+ };
+}
- static get is() { return 'gr-reviewer-list'; }
+@customElement('gr-reviewer-list')
+export class GrReviewerList extends GestureEventListeners(
+ LegacyElementMixin(PolymerElement)
+) {
+ static get template() {
+ return htmlTemplate;
+ }
+
/**
* Fired when the "Add reviewer..." button is tapped.
*
* @event show-reply-dialog
*/
- static get properties() {
- return {
- change: Object,
- serverConfig: Object,
- disabled: {
- type: Boolean,
- value: false,
- reflectToAttribute: true,
- },
- mutable: {
- type: Boolean,
- value: false,
- },
- reviewersOnly: {
- type: Boolean,
- value: false,
- },
- ccsOnly: {
- type: Boolean,
- value: false,
- },
+ @property({type: Object})
+ change?: ChangeInfo;
- _displayedReviewers: {
- type: Array,
- value() { return []; },
- },
- _reviewers: {
- type: Array,
- value() { return []; },
- },
- _showInput: {
- type: Boolean,
- value: false,
- },
- _addLabel: {
- type: String,
- computed: '_computeAddLabel(ccsOnly)',
- },
- _hiddenReviewerCount: {
- type: Number,
- computed: '_computeHiddenCount(_reviewers, _displayedReviewers)',
- },
+ @property({type: Object})
+ serverConfig?: ServerInfo;
- // Used for testing.
- _lastAutocompleteRequest: Object,
- _xhrPromise: Object,
- };
+ @property({type: Boolean, reflectToAttribute: true})
+ disabled = false;
+
+ @property({type: Boolean})
+ mutable = false;
+
+ @property({type: Boolean})
+ reviewersOnly = false;
+
+ @property({type: Boolean})
+ ccsOnly = false;
+
+ @property({type: Array})
+ _displayedReviewers: AccountInfo[] = [];
+
+ @property({type: Array})
+ _reviewers: AccountInfo[] = [];
+
+ @property({type: Boolean})
+ _showInput = false;
+
+ @property({type: Object})
+ _xhrPromise?: Promise<Response | undefined>;
+
+ @computed('ccsOnly')
+ get _addLabel() {
+ return this.ccsOnly ? 'Add CC' : 'Add reviewer';
}
- static get observers() {
- return [
- '_reviewersChanged(change.reviewers.*, change.owner, serverConfig)',
- ];
+ @computed('_reviewers', '_displayedReviewers')
+ get _hiddenReviewerCount() {
+ // Polymer 2: check for undefined
+ if (
+ this._reviewers === undefined ||
+ this._displayedReviewers === undefined
+ ) {
+ return undefined;
+ }
+ return this._reviewers.length - this._displayedReviewers.length;
}
/**
@@ -110,7 +120,7 @@
* scores: [-1, 0, 1]
* }]
*/
- _permittedLabelsToNumericScores(labels) {
+ _permittedLabelsToNumericScores(labels: LabelNameToValueMap | undefined) {
if (!labels) return [];
return Object.keys(labels).map(label => {
return {
@@ -123,54 +133,65 @@
/**
* Returns hash of labels to max permitted score.
*
- * @param {!Object} change
- * @returns {!Object} labels to max permitted scores hash
+ * @returns labels to max permitted scores hash
*/
- _getMaxPermittedScores(change) {
+ _getMaxPermittedScores(change: ChangeInfo) {
return this._permittedLabelsToNumericScores(change.permitted_labels)
- .map(({label, scores}) => {
- return {
- [label]: scores
- .map(v => parseInt(v, 10))
- .reduce((a, b) => Math.max(a, b))};
- })
- .reduce((acc, i) => Object.assign(acc, i), {});
+ .map(({label, scores}) => {
+ return {
+ [label]: scores.reduce((a, b) => Math.max(a, b)),
+ };
+ })
+ .reduce((acc, i) => Object.assign(acc, i), {});
}
/**
* Returns max permitted score for reviewer.
- *
- * @param {!Object} reviewer
- * @param {!Object} change
- * @param {string} label
- * @return {number}
*/
- _getReviewerPermittedScore(reviewer, change, label) {
+ _getReviewerPermittedScore(
+ reviewer: AccountInfo,
+ change: ChangeInfo,
+ label: string
+ ) {
// Note (issue 7874): sometimes the "all" list is not included in change
// detail responses, even when DETAILED_LABELS is included in options.
- if (!change.labels[label].all) { return NaN; }
- const detailed = change.labels[label].all.filter(
- ({_account_id}) => reviewer._account_id === _account_id).pop();
+ if (!change.labels) {
+ return NaN;
+ }
+ const detailedLabel = change.labels[label] as DetailedLabelInfo;
+ if (!detailedLabel.all) {
+ return NaN;
+ }
+ const detailed = detailedLabel.all
+ .filter(
+ (approval: ApprovalInfo) =>
+ reviewer._account_id === approval._account_id
+ )
+ .pop();
if (!detailed) {
return NaN;
}
- if (detailed.hasOwnProperty('permitted_voting_range')) {
+ if (hasOwnProperty(detailed, 'permitted_voting_range')) {
+ if (!detailed.permitted_voting_range) return NaN;
return detailed.permitted_voting_range.max;
- } else if (detailed.hasOwnProperty('value')) {
+ } else if (hasOwnProperty(detailed, 'value')) {
// If preset, user can vote on the label.
return 0;
}
return NaN;
}
- _computeVoteableText(reviewer, change) {
- if (!change || !change.labels) { return ''; }
+ _computeVoteableText(reviewer: AccountInfo, change: ChangeInfo) {
+ if (!change || !change.labels) {
+ return '';
+ }
const maxScores = [];
const maxPermitted = this._getMaxPermittedScores(change);
for (const label of Object.keys(change.labels)) {
- const maxScore =
- this._getReviewerPermittedScore(reviewer, change, label);
- if (isNaN(maxScore) || maxScore < 0) { continue; }
+ const maxScore = this._getReviewerPermittedScore(reviewer, change, label);
+ if (isNaN(maxScore) || maxScore < 0) {
+ continue;
+ }
if (maxScore > 0 && maxScore === maxPermitted[label]) {
maxScores.push(`${label}: +${maxScore}`);
} else {
@@ -180,13 +201,22 @@
return maxScores.join(', ');
}
- _reviewersChanged(changeRecord, owner, serverConfig) {
+ @observe('change.reviewers.*', 'change.owner', 'serverConfig')
+ _reviewersChanged(
+ changeRecord: PolymerDeepPropertyChange<Reviewers, Reviewers>,
+ owner: AccountInfo,
+ serverConfig: ServerInfo
+ ) {
// Polymer 2: check for undefined
- if ([changeRecord, owner, serverConfig].includes(undefined)) {
+ if (
+ changeRecord === undefined ||
+ owner === undefined ||
+ serverConfig === undefined ||
+ this.change === undefined
+ ) {
return;
}
-
- let result = [];
+ let result: AccountInfo[] = [];
const reviewers = changeRecord.base;
for (const key in reviewers) {
if (this.reviewersOnly && key !== 'REVIEWER') {
@@ -196,22 +226,22 @@
continue;
}
if (key === 'REVIEWER' || key === 'CC') {
- result = result.concat(reviewers[key]);
+ result = result.concat(reviewers[key]!);
}
}
this._reviewers = result
- .filter(reviewer => reviewer._account_id != owner._account_id)
- // Sort order:
- // 1. Human users in the attention set.
- // 2. Other human users.
- // 3. Service users.
- .sort((r1, r2) => {
- const a1 = hasAttention(serverConfig, r1, this.change) ? 1 : 0;
- const a2 = hasAttention(serverConfig, r2, this.change) ? 1 : 0;
- const s1 = isServiceUser(r1) ? -2 : 0;
- const s2 = isServiceUser(r2) ? -2 : 0;
- return a2 - a1 + s2 - s1;
- });
+ .filter(reviewer => reviewer._account_id !== owner._account_id)
+ // Sort order:
+ // 1. Human users in the attention set.
+ // 2. Other human users.
+ // 3. Service users.
+ .sort((r1, r2) => {
+ const a1 = hasAttention(serverConfig, r1, this.change!) ? 1 : 0;
+ const a2 = hasAttention(serverConfig, r2, this.change!) ? 1 : 0;
+ const s1 = isServiceUser(r1) ? -2 : 0;
+ const s2 = isServiceUser(r2) ? -2 : 0;
+ return a2 - a1 + s2 - s1;
+ });
if (this._reviewers.length > 8) {
this._displayedReviewers = this._reviewers.slice(0, 6);
@@ -220,84 +250,90 @@
}
}
- _computeHiddenCount(reviewers, displayedReviewers) {
- // Polymer 2: check for undefined
- if ([reviewers, displayedReviewers].includes(undefined)) {
- return undefined;
+ _computeCanRemoveReviewer(reviewer: AccountInfo, mutable: boolean) {
+ if (
+ !mutable ||
+ this.change === undefined ||
+ this.change.removable_reviewers === undefined
+ ) {
+ return false;
}
- return reviewers.length - displayedReviewers.length;
- }
-
- _computeCanRemoveReviewer(reviewer, mutable) {
- if (!mutable) { return false; }
-
let current;
for (let i = 0; i < this.change.removable_reviewers.length; i++) {
current = this.change.removable_reviewers[i];
- if (current._account_id === reviewer._account_id ||
- (!reviewer._account_id && current.email === reviewer.email)) {
+ if (
+ current._account_id === reviewer._account_id ||
+ (!reviewer._account_id && current.email === reviewer.email)
+ ) {
return true;
}
}
return false;
}
- _handleRemove(e) {
+ _handleRemove(e: Event) {
e.preventDefault();
- const target = dom(e).rootTarget;
- if (!target.account) { return; }
- const accountID = target.account._account_id || target.account.email;
+ const target = (dom(e) as EventApi).rootTarget as GrAccountChip;
+ if (!target.account || !this.change) {
+ return;
+ }
+ const accountID = target.account._account_id;
this.disabled = true;
- this._xhrPromise = this._removeReviewer(accountID).then(response => {
- this.disabled = false;
- if (!response.ok) { return response; }
-
- const reviewers = this.change.reviewers;
-
- for (const type of ['REVIEWER', 'CC']) {
- reviewers[type] = reviewers[type] || [];
- for (let i = 0; i < reviewers[type].length; i++) {
- if (reviewers[type][i]._account_id == accountID ||
- reviewers[type][i].email == accountID) {
- this.splice('change.reviewers.' + type, i, 1);
- break;
+ if (!accountID) return;
+ this._xhrPromise = this._removeReviewer(accountID)
+ .then((response: Response | undefined) => {
+ this.disabled = false;
+ if (!response || !response.ok) {
+ return response;
+ }
+ if (!this.change || !this.change.reviewers) return;
+ const reviewers: {[type: string]: AccountInfo[] | undefined} = this
+ .change!.reviewers;
+ for (const type of ['REVIEWER', 'CC']) {
+ reviewers[type] = reviewers[type] || [];
+ for (let i = 0; i < reviewers[type]!.length; i++) {
+ if (reviewers[type]![i]._account_id === accountID) {
+ this.splice('change.reviewers.' + type, i, 1);
+ break;
+ }
}
}
- }
- })
- .catch(err => {
- this.disabled = false;
- throw err;
- });
+ return;
+ })
+ .catch((err: Error) => {
+ this.disabled = false;
+ throw err;
+ });
}
- _handleAddTap(e) {
+ _handleAddTap(e: Event) {
e.preventDefault();
- const value = {};
+ const value = {
+ reviewersOnly: false,
+ ccsOnly: false,
+ };
if (this.reviewersOnly) {
value.reviewersOnly = true;
}
if (this.ccsOnly) {
value.ccsOnly = true;
}
- this.dispatchEvent(new CustomEvent('show-reply-dialog', {
- detail: {value},
- composed: true, bubbles: true,
- }));
+ this.dispatchEvent(
+ new CustomEvent('show-reply-dialog', {
+ detail: {value},
+ composed: true,
+ bubbles: true,
+ })
+ );
}
- _handleViewAll(e) {
+ _handleViewAll() {
this._displayedReviewers = this._reviewers;
}
- _removeReviewer(id) {
+ _removeReviewer(id: AccountId): Promise<Response | undefined> {
+ if (!this.change) return Promise.resolve(undefined);
return this.$.restAPI.removeChangeReviewer(this.change._number, id);
}
-
- _computeAddLabel(ccsOnly) {
- return ccsOnly ? 'Add CC' : 'Add reviewer';
- }
}
-
-customElements.define(GrReviewerList.is, GrReviewerList);