commit f7741dec73fd578b0af44cbaa4ffb128dc15099d Author: Dmitrii Filippov Date: Mon Oct 12 21:34:30 2020 +0200 Convert files to typescript The change converts the following files to typescript: * elements/change/gr-change-metadata/gr-change-metadata.ts Change-Id: I8fa1d8eb3fb45da784c2af50e93d6a24b648e28f diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts index b038bf6..539697e 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts @@ -14,333 +14,415 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import '../../../styles/shared-styles.js'; -import '../../../styles/gr-change-metadata-shared-styles.js'; -import '../../../styles/gr-change-view-integration-shared-styles.js'; -import '../../../styles/gr-voting-styles.js'; -import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.js'; -import '../../plugins/gr-endpoint-param/gr-endpoint-param.js'; -import '../../plugins/gr-external-style/gr-external-style.js'; -import '../../shared/gr-account-chip/gr-account-chip.js'; -import '../../shared/gr-date-formatter/gr-date-formatter.js'; -import '../../shared/gr-editable-label/gr-editable-label.js'; -import '../../shared/gr-icons/gr-icons.js'; -import '../../shared/gr-limited-text/gr-limited-text.js'; -import '../../shared/gr-linked-chip/gr-linked-chip.js'; -import '../../shared/gr-tooltip-content/gr-tooltip-content.js'; -import '../../shared/gr-rest-api-interface/gr-rest-api-interface.js'; -import '../gr-change-requirements/gr-change-requirements.js'; -import '../gr-commit-info/gr-commit-info.js'; -import '../gr-reviewer-list/gr-reviewer-list.js'; -import '../../shared/gr-account-list/gr-account-list.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-change-metadata_html.js'; -import {GrReviewerSuggestionsProvider, SUGGESTIONS_PROVIDERS_USERS_TYPES} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js'; -import {GerritNav} from '../../core/gr-navigation/gr-navigation.js'; -import {ChangeStatus} from '../../../constants/constants.js'; -import {changeIsOpen} from '../../../utils/change-util.js'; +import '../../../styles/shared-styles'; +import '../../../styles/gr-change-metadata-shared-styles'; +import '../../../styles/gr-change-view-integration-shared-styles'; +import '../../../styles/gr-voting-styles'; +import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator'; +import '../../plugins/gr-endpoint-param/gr-endpoint-param'; +import '../../plugins/gr-external-style/gr-external-style'; +import '../../shared/gr-account-chip/gr-account-chip'; +import '../../shared/gr-date-formatter/gr-date-formatter'; +import '../../shared/gr-editable-label/gr-editable-label'; +import '../../shared/gr-icons/gr-icons'; +import '../../shared/gr-limited-text/gr-limited-text'; +import '../../shared/gr-linked-chip/gr-linked-chip'; +import '../../shared/gr-tooltip-content/gr-tooltip-content'; +import '../../shared/gr-rest-api-interface/gr-rest-api-interface'; +import '../gr-change-requirements/gr-change-requirements'; +import '../gr-commit-info/gr-commit-info'; +import '../gr-reviewer-list/gr-reviewer-list'; +import '../../shared/gr-account-list/gr-account-list'; +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-change-metadata_html'; +import { + GrReviewerSuggestionsProvider, + SUGGESTIONS_PROVIDERS_USERS_TYPES, +} from '../../../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider'; +import {GerritNav} from '../../core/gr-navigation/gr-navigation'; +import { + ChangeStatus, + GpgKeyInfoStatus, + SubmitType, +} from '../../../constants/constants'; +import {changeIsOpen} from '../../../utils/change-util'; +import {customElement, property, observe} from '@polymer/decorators'; +import {ParsedChangeInfo} from '../../shared/gr-rest-api-interface/gr-reviewer-updates-parser'; +import { + AccountDetailInfo, + AccountInfo, + BranchName, + CommitId, + CommitInfo, + ElementPropertyDeepChange, + GpgKeyInfo, + Hashtag, + LabelNameToInfoMap, + NumericChangeId, + ParentCommitInfo, + PatchSetNum, + RepoName, + RevisionInfo, + ServerInfo, + TopicName, +} from '../../../types/common'; +import {assertNever} from '../../../utils/common-util'; +import {RestApiService} from '../../../services/services/gr-rest-api/gr-rest-api'; +import {GrEditableLabel} from '../../shared/gr-editable-label/gr-editable-label'; +import {GrLinkedChip} from '../../shared/gr-linked-chip/gr-linked-chip'; const HASHTAG_ADD_MESSAGE = 'Add Hashtag'; -const SubmitTypeLabel = { - FAST_FORWARD_ONLY: 'Fast Forward Only', - MERGE_IF_NECESSARY: 'Merge if Necessary', - REBASE_IF_NECESSARY: 'Rebase if Necessary', - MERGE_ALWAYS: 'Always Merge', - REBASE_ALWAYS: 'Rebase Always', - CHERRY_PICK: 'Cherry Pick', -}; +enum ChangeRole { + OWNER = 'owner', + UPLOADER = 'uploader', + AUTHOR = 'author', + COMMITTER = 'committer', +} + +export interface CommitInfoWithRequiredCommit extends CommitInfo { + // gr-change-view always assigns commit to CommitInfo + commit: CommitId; +} + +const SubmitTypeLabel = new Map([ + [SubmitType.FAST_FORWARD_ONLY, 'Fast Forward Only'], + [SubmitType.MERGE_IF_NECESSARY, 'Merge if Necessary'], + [SubmitType.REBASE_IF_NECESSARY, 'Rebase if Necessary'], + [SubmitType.MERGE_ALWAYS, 'Always Merge'], + [SubmitType.REBASE_ALWAYS, 'Rebase Always'], + [SubmitType.CHERRY_PICK, 'Cherry Pick'], +]); const NOT_CURRENT_MESSAGE = 'Not current - rebase possible'; -/** - * @enum {string} - */ -const CertificateStatus = { - /** - * This certificate status is bad. - */ - BAD: 'BAD', - /** - * This certificate status is OK. - */ - OK: 'OK', - /** - * This certificate status is TRUSTED. - */ - TRUSTED: 'TRUSTED', -}; +interface PushCertifacteValidationInfo { + class: string; + icon: string; + message: string; +} -/** - * @extends PolymerElement - */ -class GrChangeMetadata extends GestureEventListeners( - LegacyElementMixin(PolymerElement)) { - static get template() { return htmlTemplate; } +export interface GrChangeMetadata { + $: { + restAPI: RestApiService & Element; + }; +} + +@customElement('gr-change-metadata') +export class GrChangeMetadata extends GestureEventListeners( + LegacyElementMixin(PolymerElement) +) { + static get template() { + return htmlTemplate; + } - static get is() { return 'gr-change-metadata'; } /** * Fired when the change topic is changed. * * @event topic-changed */ - static get properties() { - return { - /** @type {?} */ - change: Object, - labels: { - type: Object, - notify: true, - }, - account: Object, - /** @type {?} */ - revision: Object, - commitInfo: Object, - _mutable: { - type: Boolean, - computed: '_computeIsMutable(account)', - }, - /** @type {?} */ - serverConfig: Object, - parentIsCurrent: Boolean, - _notCurrentMessage: { - type: String, - value: NOT_CURRENT_MESSAGE, - readOnly: true, - }, - _topicReadOnly: { - type: Boolean, - computed: '_computeTopicReadOnly(_mutable, change)', - }, - _hashtagReadOnly: { - type: Boolean, - computed: '_computeHashtagReadOnly(_mutable, change)', - }, - /** - * @type {Gerrit.PushCertificateValidation} - */ - _pushCertificateValidation: { - type: Object, - computed: '_computePushCertificateValidation(serverConfig, change)', - }, - _showRequirements: { - type: Boolean, - computed: '_computeShowRequirements(change)', - }, - - _assignee: Array, - _isWip: { - type: Boolean, - computed: '_computeIsWip(change)', - }, - _newHashtag: String, - - _settingTopic: { - type: Boolean, - value: false, - }, - - _currentParents: { - type: Array, - computed: '_computeParents(change, revision)', - }, - - /** @type {?} */ - _CHANGE_ROLE: { - type: Object, - readOnly: true, - value: { - OWNER: 'owner', - UPLOADER: 'uploader', - AUTHOR: 'author', - COMMITTER: 'committer', - }, - }, - }; - } - - static get observers() { - return [ - '_changeChanged(change)', - '_labelsChanged(change.labels)', - '_assigneeChanged(_assignee.*)', - ]; - } + @property({type: Object}) + change?: ParsedChangeInfo; + + @property({type: Object, notify: true}) + labels?: LabelNameToInfoMap; - _labelsChanged(labels) { - this.labels = ({...labels}) || null; + @property({type: Object}) + account?: AccountDetailInfo; + + @property({type: Object}) + revision?: RevisionInfo; + + @property({type: Object}) + commitInfo?: CommitInfoWithRequiredCommit; + + @property({type: Boolean, computed: '_computeIsMutable(account)'}) + _mutable = false; + + @property({type: Object}) + serverConfig?: ServerInfo; + + @property({type: Boolean}) + parentIsCurrent?: boolean; + + @property({type: String, readOnly: true}) + _notCurrentMessage = NOT_CURRENT_MESSAGE; + + @property({ + type: Boolean, + computed: '_computeTopicReadOnly(_mutable, change)', + }) + _topicReadOnly = true; + + @property({ + type: Boolean, + computed: '_computeHashtagReadOnly(_mutable, change)', + }) + _hashtagReadOnly = true; + + @property({ + type: Object, + computed: '_computePushCertificateValidation(serverConfig, change)', + }) + _pushCertificateValidation: PushCertifacteValidationInfo | null = null; + + @property({type: Boolean, computed: '_computeShowRequirements(change)'}) + _showRequirements = false; + + @property({type: Array}) + _assignee?: AccountInfo[]; + + @property({type: Boolean, computed: '_computeIsWip(change)'}) + _isWip = false; + + @property({type: String}) + _newHashtag?: Hashtag; + + @property({type: Boolean}) + _settingTopic = false; + + @property({type: Array, computed: '_computeParents(change, revision)'}) + _currentParents: ParentCommitInfo[] = []; + + @property({type: Object}) + _CHANGE_ROLE = ChangeRole; + + @observe('change.labels') + _labelsChanged(labels?: LabelNameToInfoMap) { + this.labels = {...labels} || null; } - _changeChanged(change) { - this._assignee = change.assignee ? [change.assignee] : []; + @observe('change') + _changeChanged(change?: ParsedChangeInfo) { + this._assignee = change?.assignee ? [change.assignee] : []; this._settingTopic = false; } - _assigneeChanged(assigneeRecord) { + @observe('_assignee.*') + _assigneeChanged( + assigneeRecord: ElementPropertyDeepChange + ) { if (!this.change || !this._isAssigneeEnabled(this.serverConfig)) { return; } const assignee = assigneeRecord.base; - if (assignee.length) { + if (assignee?.length) { const acct = assignee[0]; - if (this.change.assignee && - acct._account_id === this.change.assignee._account_id) { return; } + if ( + !acct._account_id || + (this.change.assignee && + acct._account_id === this.change.assignee._account_id) + ) { + return; + } this.set(['change', 'assignee'], acct); this.$.restAPI.setAssignee(this.change._number, acct._account_id); } else { - if (!this.change.assignee) { return; } + if (!this.change.assignee) { + return; + } this.set(['change', 'assignee'], undefined); this.$.restAPI.deleteAssignee(this.change._number); } } - _computeHideStrategy(change) { + _computeHideStrategy(change?: ParsedChangeInfo) { return !changeIsOpen(change); } /** - * @param {Object} commitInfo - * @return {?Array} If array is empty, returns null instead so + * @return If array is empty, returns null instead so * an existential check can be used to hide or show the webLinks * section. */ - _computeWebLinks(commitInfo, serverConfig) { - if (!commitInfo) { return null; } + _computeWebLinks( + commitInfo?: CommitInfoWithRequiredCommit, + serverConfig?: ServerInfo + ) { + if (!commitInfo) { + return null; + } const weblinks = GerritNav.getChangeWeblinks( - this.change ? this.change.repo : '', - commitInfo.commit, - { - weblinks: commitInfo.web_links, - config: serverConfig, - }); + this.change ? this.change.project : ('' as RepoName), + commitInfo.commit, + { + weblinks: commitInfo.web_links, + config: serverConfig, + } + ); return weblinks.length ? weblinks : null; } - _isAssigneeEnabled(serverConfig) { - return serverConfig && serverConfig.change - && !!serverConfig.change.enable_assignee; + _isAssigneeEnabled(serverConfig?: ServerInfo) { + return ( + serverConfig && + serverConfig.change && + !!serverConfig.change.enable_assignee + ); } - _computeStrategy(change) { - return SubmitTypeLabel[change.submit_type]; + _computeStrategy(change?: ParsedChangeInfo) { + if (!change?.submit_type) { + return ''; + } + + return SubmitTypeLabel.get(change.submit_type); } - _computeLabelNames(labels) { - return Object.keys(labels).sort(); + _computeLabelNames(labels?: LabelNameToInfoMap) { + return labels ? Object.keys(labels).sort() : []; } - _handleTopicChanged(e, topic) { + _handleTopicChanged(e: CustomEvent) { + if (!this.change) { + throw new Error('change must be set'); + } const lastTopic = this.change.topic; - if (!topic.length) { topic = null; } + const topic = e.detail.length ? e.detail : null; this._settingTopic = true; const topicChangedForChangeNumber = this.change._number; - this.$.restAPI.setChangeTopic(topicChangedForChangeNumber, topic) - .then(newTopic => { - if (this.change._number !== topicChangedForChangeNumber) { - return; - } - this._settingTopic = false; - this.set(['change', 'topic'], newTopic); - if (newTopic !== lastTopic) { - this.dispatchEvent(new CustomEvent( - 'topic-changed', {bubbles: true, composed: true})); - } - }); - } - - _showAddTopic(changeRecord, settingTopic) { - const hasTopic = !!changeRecord && - !!changeRecord.base && !!changeRecord.base.topic; + this.$.restAPI + .setChangeTopic(topicChangedForChangeNumber, topic) + .then(newTopic => { + if ( + !this.change || + this.change._number !== topicChangedForChangeNumber + ) { + return; + } + this._settingTopic = false; + this.set(['change', 'topic'], newTopic); + if (newTopic !== lastTopic) { + this.dispatchEvent( + new CustomEvent('topic-changed', {bubbles: true, composed: true}) + ); + } + }); + } + + _showAddTopic( + changeRecord: ElementPropertyDeepChange, + settingTopic?: boolean + ) { + const hasTopic = + !!changeRecord && !!changeRecord.base && !!changeRecord.base.topic; return !hasTopic && !settingTopic; } - _showTopicChip(changeRecord, settingTopic) { - const hasTopic = !!changeRecord && - !!changeRecord.base && !!changeRecord.base.topic; + _showTopicChip( + changeRecord: ElementPropertyDeepChange, + settingTopic?: boolean + ) { + const hasTopic = + !!changeRecord && !!changeRecord.base && !!changeRecord.base.topic; return hasTopic && !settingTopic; } - _showCherryPickOf(changeRecord) { - const hasCherryPickOf = !!changeRecord && - !!changeRecord.base && !!changeRecord.base.cherry_pick_of_change && - !!changeRecord.base.cherry_pick_of_patch_set; + _showCherryPickOf( + changeRecord: ElementPropertyDeepChange + ) { + const hasCherryPickOf = + !!changeRecord && + !!changeRecord.base && + !!changeRecord.base.cherry_pick_of_change && + !!changeRecord.base.cherry_pick_of_patch_set; return hasCherryPickOf; } - _handleHashtagChanged(e) { - const lastHashtag = this.change.hashtag; - if (!this._newHashtag.length) { return; } + _handleHashtagChanged() { + if (!this.change) { + throw new Error('change must be set'); + } + if (!this._newHashtag?.length) { + return; + } const newHashtag = this._newHashtag; - this._newHashtag = ''; - this.$.restAPI.setChangeHashtag( - this.change._number, {add: [newHashtag]}).then(newHashtag => { - this.set(['change', 'hashtags'], newHashtag); - if (newHashtag !== lastHashtag) { + this._newHashtag = '' as Hashtag; + this.$.restAPI + .setChangeHashtag(this.change._number, {add: [newHashtag]}) + .then(newHashtag => { + this.set(['change', 'hashtags'], newHashtag); this.dispatchEvent( - new CustomEvent('hashtag-changed', { - bubbles: true, composed: true})); - } - }); - } - - _computeTopicReadOnly(mutable, change) { - return !mutable || - !change || - !change.actions || - !change.actions.topic || - !change.actions.topic.enabled; - } - - _computeHashtagReadOnly(mutable, change) { - return !mutable || - !change || - !change.actions || - !change.actions.hashtags || - !change.actions.hashtags.enabled; - } - - _computeAssigneeReadOnly(mutable, change) { - return !mutable || - !change || - !change.actions || - !change.actions.assignee || - !change.actions.assignee.enabled; - } - - _computeTopicPlaceholder(_topicReadOnly) { + new CustomEvent('hashtag-changed', { + bubbles: true, + composed: true, + }) + ); + }); + } + + _computeTopicReadOnly(mutable?: boolean, change?: ParsedChangeInfo) { + return ( + !mutable || + !change || + !change.actions || + !change.actions.topic || + !change.actions.topic.enabled + ); + } + + _computeHashtagReadOnly(mutable?: boolean, change?: ParsedChangeInfo) { + return ( + !mutable || + !change || + !change.actions || + !change.actions.hashtags || + !change.actions.hashtags.enabled + ); + } + + _computeAssigneeReadOnly(mutable?: boolean, change?: ParsedChangeInfo) { + return ( + !mutable || + !change || + !change.actions || + !change.actions.assignee || + !change.actions.assignee.enabled + ); + } + + _computeTopicPlaceholder(_topicReadOnly?: boolean) { // Action items in Material Design are uppercase -- placeholder label text // is sentence case. return _topicReadOnly ? 'No topic' : 'ADD TOPIC'; } - _computeHashtagPlaceholder(_hashtagReadOnly) { + _computeHashtagPlaceholder(_hashtagReadOnly?: boolean) { return _hashtagReadOnly ? '' : HASHTAG_ADD_MESSAGE; } - _computeShowRequirements(change) { + _computeShowRequirements(change?: ParsedChangeInfo) { + if (!change) { + return false; + } if (change.status !== ChangeStatus.NEW) { // TODO(maximeg) change this to display the stored // requirements, once it is implemented server-side. return false; } - const hasRequirements = !!change.requirements && - Object.keys(change.requirements).length > 0; - const hasLabels = !!change.labels && - Object.keys(change.labels).length > 0; + const hasRequirements = + !!change.requirements && Object.keys(change.requirements).length > 0; + const hasLabels = !!change.labels && Object.keys(change.labels).length > 0; return hasRequirements || hasLabels || !!change.work_in_progress; } /** - * @return {?Gerrit.PushCertificateValidation} object representing data for - * the push validation. + * @return object representing data for the push validation. */ - _computePushCertificateValidation(serverConfig, change) { - if (!change || !serverConfig || !serverConfig.receive || - !serverConfig.receive.enable_signed_push) { + _computePushCertificateValidation( + serverConfig?: ServerInfo, + change?: ParsedChangeInfo + ): PushCertifacteValidationInfo | null { + if ( + !change || + !serverConfig || + !serverConfig.receive || + !serverConfig.receive.enable_signed_push + ) { return null; } const rev = change.revisions[change.current_revision]; @@ -354,32 +436,39 @@ class GrChangeMetadata extends GestureEventListeners( const key = rev.push_certificate.key; switch (key.status) { - case CertificateStatus.BAD: + case GpgKeyInfoStatus.BAD: return { class: 'invalid', icon: 'gr-icons:close', message: this._problems('Push certificate is invalid', key), }; - case CertificateStatus.OK: + case GpgKeyInfoStatus.OK: return { class: 'notTrusted', icon: 'gr-icons:info', message: this._problems( - 'Push certificate is valid, but key is not trusted', key), + 'Push certificate is valid, but key is not trusted', + key + ), }; - case CertificateStatus.TRUSTED: + case GpgKeyInfoStatus.TRUSTED: return { class: 'trusted', icon: 'gr-icons:check', message: this._problems( - 'Push certificate is valid and key is trusted', key), + 'Push certificate is valid and key is trusted', + key + ), }; + case undefined: + // TODO(TS): Process it correctly + throw new Error('deleted certificate'); default: - throw new Error(`unknown certificate status: ${key.status}`); + assertNever(key.status, `unknown certificate status: ${key.status}`); } } - _problems(msg, key) { + _problems(msg: string, key: GpgKeyInfo) { if (!key || !key.problems || key.problems.length === 0) { return msg; } @@ -387,126 +476,164 @@ class GrChangeMetadata extends GestureEventListeners( return [msg + ':'].concat(key.problems).join('\n'); } - _computeShowRepoBranchTogether(repo, branch) { + _computeShowRepoBranchTogether(repo?: RepoName, branch?: BranchName) { return !!repo && !!branch && repo.length + branch.length < 40; } - _computeProjectUrl(project) { + _computeProjectUrl(project?: RepoName) { + if (!project) return ''; return GerritNav.getUrlForProjectChanges(project); } - _computeBranchUrl(project, branch) { - if (!this.change || !this.change.status) return ''; - return GerritNav.getUrlForBranch(branch, project, - this.change.status == ChangeStatus.NEW ? 'open' : - this.change.status.toLowerCase()); + _computeBranchUrl(project?: RepoName, branch?: BranchName) { + if (!project || !branch || !this.change || !this.change.status) return ''; + return GerritNav.getUrlForBranch( + branch, + project, + this.change.status === ChangeStatus.NEW + ? 'open' + : this.change.status.toLowerCase() + ); } - _computeCherryPickOfUrl(change, patchset, project) { + _computeCherryPickOfUrl( + change?: NumericChangeId, + patchset?: PatchSetNum, + project?: RepoName + ) { + if (!change || !project) { + return ''; + } return GerritNav.getUrlForChangeById(change, project, patchset); } - _computeTopicUrl(topic) { + _computeTopicUrl(topic: TopicName) { return GerritNav.getUrlForTopic(topic); } - _computeHashtagUrl(hashtag) { + _computeHashtagUrl(hashtag: Hashtag) { return GerritNav.getUrlForHashtag(hashtag); } - _handleTopicRemoved(e) { - const target = dom(e).rootTarget; + _handleTopicRemoved(e: CustomEvent) { + if (!this.change) { + throw new Error('change must be set'); + } + const target = (dom(e) as EventApi).rootTarget as GrLinkedChip; target.disabled = true; - this.$.restAPI.setChangeTopic(this.change._number, null) - .then(() => { - target.disabled = false; - this.set(['change', 'topic'], ''); - this.dispatchEvent( - new CustomEvent('topic-changed', - {bubbles: true, composed: true})); - }) - .catch(err => { - target.disabled = false; - return; - }); + this.$.restAPI + .setChangeTopic(this.change._number, null) + .then(() => { + target.disabled = false; + this.set(['change', 'topic'], ''); + this.dispatchEvent( + new CustomEvent('topic-changed', {bubbles: true, composed: true}) + ); + }) + .catch(() => { + target.disabled = false; + return; + }); } - _handleHashtagRemoved(e) { + _handleHashtagRemoved(e: CustomEvent) { e.preventDefault(); - const target = dom(e).rootTarget; + if (!this.change) { + throw new Error('change must be set'); + } + const target = (dom(e) as EventApi).rootTarget as GrLinkedChip; target.disabled = true; - this.$.restAPI.setChangeHashtag(this.change._number, - {remove: [target.text]}) - .then(newHashtag => { - target.disabled = false; - this.set(['change', 'hashtags'], newHashtag); - }) - .catch(err => { - target.disabled = false; - return; - }); + this.$.restAPI + .setChangeHashtag(this.change._number, {remove: [target.text as Hashtag]}) + .then(newHashtags => { + target.disabled = false; + this.set(['change', 'hashtags'], newHashtags); + }) + .catch(() => { + target.disabled = false; + return; + }); } - _computeIsWip(change) { - return !!change.work_in_progress; + _computeIsWip(change?: ParsedChangeInfo) { + return change && !!change.work_in_progress; } - _computeShowRoleClass(change, role) { + _computeShowRoleClass(change?: ParsedChangeInfo, role?: ChangeRole) { return this._getNonOwnerRole(change, role) ? '' : 'hideDisplay'; } /** * Get the user with the specified role on the change. Returns null if the * user with that role is the same as the owner. - * - * @param {!Object} change - * @param {string} role One of the values from _CHANGE_ROLE - * @return {Object|null} either an account or null. */ - _getNonOwnerRole(change, role) { - if (!change || !change.current_revision || - !change.revisions[change.current_revision]) { + _getNonOwnerRole(change?: ParsedChangeInfo, role?: ChangeRole) { + if ( + !change || + !change.current_revision || + !change.revisions[change.current_revision] + ) { return null; } const rev = change.revisions[change.current_revision]; - if (!rev) { return null; } + if (!rev) { + return null; + } - if (role === this._CHANGE_ROLE.UPLOADER && - rev.uploader && - change.owner._account_id !== rev.uploader._account_id) { + if ( + role === ChangeRole.UPLOADER && + rev.uploader && + change.owner._account_id !== rev.uploader._account_id + ) { return rev.uploader; } - if (role === this._CHANGE_ROLE.AUTHOR && - rev.commit && rev.commit.author && - change.owner.email !== rev.commit.author.email) { + if ( + role === ChangeRole.AUTHOR && + rev.commit && + rev.commit.author && + change.owner.email !== rev.commit.author.email + ) { return rev.commit.author; } - if (role === this._CHANGE_ROLE.COMMITTER && - rev.commit && rev.commit.committer && - change.owner.email !== rev.commit.committer.email) { + if ( + role === ChangeRole.COMMITTER && + rev.commit && + rev.commit.committer && + change.owner.email !== rev.commit.committer.email + ) { return rev.commit.committer; } return null; } - _computeParents(change, revision) { + _computeParents( + change?: ParsedChangeInfo, + revision?: RevisionInfo + ): ParentCommitInfo[] { if (!revision || !revision.commit) { - if (!change || !change.current_revision) { return []; } + if (!change || !change.current_revision) { + return []; + } revision = change.revisions[change.current_revision]; - if (!revision || !revision.commit) { return []; } + if (!revision || !revision.commit) { + return []; + } } return revision.commit.parents; } - _computeParentsLabel(parents) { + _computeParentsLabel(parents?: ParentCommitInfo[]) { return parents && parents.length > 1 ? 'Parents' : 'Parent'; } - _computeParentListClass(parents, parentIsCurrent) { + _computeParentListClass( + parents?: ParentCommitInfo[], + parentIsCurrent?: boolean + ) { // Undefined check for polymer 2 if (parents === undefined || parentIsCurrent === undefined) { return ''; @@ -519,23 +646,37 @@ class GrChangeMetadata extends GestureEventListeners( ].join(' '); } - _computeIsMutable(account) { - return !!Object.keys(account).length; + _computeIsMutable(account?: AccountDetailInfo) { + return account && !!Object.keys(account).length; } editTopic() { - if (this._topicReadOnly || this.change.topic) { return; } + if (this._topicReadOnly || !this.change || this.change.topic) { + return; + } // Cannot use `this.$.ID` syntax because the element exists inside of a // dom-if. - this.shadowRoot.querySelector('.topicEditableLabel').open(); + (this.shadowRoot!.querySelector( + '.topicEditableLabel' + ) as GrEditableLabel).open(); } - _getReviewerSuggestionsProvider(change) { - const provider = GrReviewerSuggestionsProvider.create(this.$.restAPI, - change._number, SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY); + _getReviewerSuggestionsProvider(change?: ParsedChangeInfo) { + if (!change) { + return undefined; + } + const provider = GrReviewerSuggestionsProvider.create( + this.$.restAPI, + change._number, + SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY + ); provider.init(); return provider; } } -customElements.define(GrChangeMetadata.is, GrChangeMetadata); +declare global { + interface HTMLElementTagNameMap { + 'gr-change-metadata': GrChangeMetadata; + } +} diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js index bdcd480..5f53641 100644 --- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js +++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.js @@ -686,7 +686,7 @@ suite('gr-change-metadata tests', () => { const newTopic = 'the new topic'; sinon.stub(element.$.restAPI, 'setChangeTopic').returns( Promise.resolve(newTopic)); - element._handleTopicChanged({}, newTopic); + element._handleTopicChanged({detail: newTopic}); const topicChangedSpy = sinon.spy(); element.addEventListener('topic-changed', topicChangedSpy); assert.isTrue(element.$.restAPI.setChangeTopic.calledWith( diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts index fcdfa08..d1459a3 100644 --- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts +++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.ts @@ -137,6 +137,7 @@ import { ActionNameToActionInfoMap, RevisionId, GroupName, + Hashtag, } from '../../../types/common'; import { CancelConditionCallback, @@ -3040,26 +3041,32 @@ export class GrRestApiInterface }); } - setChangeTopic(changeNum: NumericChangeId, topic: string | null) { - return this._getChangeURLAndSend({ + setChangeTopic( + changeNum: NumericChangeId, + topic: string | null + ): Promise { + return (this._getChangeURLAndSend({ changeNum, method: HttpMethod.PUT, endpoint: '/topic', body: {topic}, parseResponse: true, reportUrlAsIs: true, - }); + }) as unknown) as Promise; } - setChangeHashtag(changeNum: NumericChangeId, hashtag: HashtagsInput) { - return this._getChangeURLAndSend({ + setChangeHashtag( + changeNum: NumericChangeId, + hashtag: HashtagsInput + ): Promise { + return (this._getChangeURLAndSend({ changeNum, method: HttpMethod.POST, endpoint: '/hashtags', body: hashtag, parseResponse: true, reportUrlAsIs: true, - }); + }) as unknown) as Promise; } deleteAccountHttpPassword() { @@ -3216,17 +3223,21 @@ export class GrRestApiInterface }); } - setAssignee(changeNum: NumericChangeId, assignee: AssigneeInput) { + setAssignee( + changeNum: NumericChangeId, + assignee: AccountId + ): Promise { + const body: AssigneeInput = {assignee}; return this._getChangeURLAndSend({ changeNum, method: HttpMethod.PUT, endpoint: '/assignee', - body: {assignee}, + body, reportUrlAsIs: true, }); } - deleteAssignee(changeNum: NumericChangeId) { + deleteAssignee(changeNum: NumericChangeId): Promise { return this._getChangeURLAndSend({ changeNum, method: HttpMethod.DELETE, diff --git a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts index 367d1c3..3082aab 100644 --- a/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts +++ b/polygerrit-ui/app/services/services/gr-rest-api/gr-rest-api.ts @@ -94,6 +94,8 @@ import { RevisionId, GroupName, DashboardId, + HashtagsInput, + Hashtag, } from '../../../types/common'; import {ParsedChangeInfo} from '../../../elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser'; import {HttpMethod, IgnoreWhitespaceType} from '../../../constants/constants'; @@ -796,4 +798,21 @@ export interface RestApiService { ): Promise; deleteDraftComments(query: string): Promise; + + setAssignee( + changeNum: NumericChangeId, + assignee: AccountId + ): Promise; + + deleteAssignee(changeNum: NumericChangeId): Promise; + + setChangeHashtag( + changeNum: NumericChangeId, + hashtag: HashtagsInput + ): Promise; + + setChangeTopic( + changeNum: NumericChangeId, + topic: string | null + ): Promise; }