Convert files to typescript

The change converts the following files to typescript:

* elements/change/gr-change-metadata/gr-change-metadata.ts

Change-Id: I8fa1d8eb3fb45da784c2af50e93d6a24b648e28f
This commit is contained in:
Dmitrii Filippov
2020-10-12 21:34:30 +02:00
parent 413c75a9c0
commit f7741dec73
4 changed files with 489 additions and 318 deletions

View File

@@ -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, string>([
[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)',
},
@property({type: Object})
change?: ParsedChangeInfo;
_assignee: Array,
_isWip: {
type: Boolean,
computed: '_computeIsWip(change)',
},
_newHashtag: String,
@property({type: Object, notify: true})
labels?: LabelNameToInfoMap;
_settingTopic: {
type: Boolean,
value: false,
},
@property({type: Object})
account?: AccountDetailInfo;
_currentParents: {
type: Array,
computed: '_computeParents(change, revision)',
},
@property({type: Object})
revision?: RevisionInfo;
/** @type {?} */
_CHANGE_ROLE: {
type: Object,
readOnly: true,
value: {
OWNER: 'owner',
UPLOADER: 'uploader',
AUTHOR: 'author',
COMMITTER: 'committer',
},
},
};
@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;
}
static get observers() {
return [
'_changeChanged(change)',
'_labelsChanged(change.labels)',
'_assigneeChanged(_assignee.*)',
];
}
_labelsChanged(labels) {
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<GrChangeMetadata, '_assignee'>
) {
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<string>) {
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}));
}
});
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, settingTopic) {
const hasTopic = !!changeRecord &&
!!changeRecord.base && !!changeRecord.base.topic;
_showAddTopic(
changeRecord: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
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<GrChangeMetadata, 'change'>,
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<GrChangeMetadata, 'change'>
) {
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}));
}
});
new CustomEvent('hashtag-changed', {
bubbles: true,
composed: true,
})
);
});
}
_computeTopicReadOnly(mutable, change) {
return !mutable ||
!change ||
!change.actions ||
!change.actions.topic ||
!change.actions.topic.enabled;
_computeTopicReadOnly(mutable?: boolean, change?: ParsedChangeInfo) {
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;
_computeHashtagReadOnly(mutable?: boolean, change?: ParsedChangeInfo) {
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;
_computeAssigneeReadOnly(mutable?: boolean, change?: ParsedChangeInfo) {
return (
!mutable ||
!change ||
!change.actions ||
!change.actions.assignee ||
!change.actions.assignee.enabled
);
}
_computeTopicPlaceholder(_topicReadOnly) {
_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;
}
}

View File

@@ -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(

View File

@@ -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<string> {
return (this._getChangeURLAndSend({
changeNum,
method: HttpMethod.PUT,
endpoint: '/topic',
body: {topic},
parseResponse: true,
reportUrlAsIs: true,
});
}) as unknown) as Promise<string>;
}
setChangeHashtag(changeNum: NumericChangeId, hashtag: HashtagsInput) {
return this._getChangeURLAndSend({
setChangeHashtag(
changeNum: NumericChangeId,
hashtag: HashtagsInput
): Promise<Hashtag[]> {
return (this._getChangeURLAndSend({
changeNum,
method: HttpMethod.POST,
endpoint: '/hashtags',
body: hashtag,
parseResponse: true,
reportUrlAsIs: true,
});
}) as unknown) as Promise<Hashtag[]>;
}
deleteAccountHttpPassword() {
@@ -3216,17 +3223,21 @@ export class GrRestApiInterface
});
}
setAssignee(changeNum: NumericChangeId, assignee: AssigneeInput) {
setAssignee(
changeNum: NumericChangeId,
assignee: AccountId
): Promise<Response> {
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<Response> {
return this._getChangeURLAndSend({
changeNum,
method: HttpMethod.DELETE,

View File

@@ -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<DashboardInfo | undefined>;
deleteDraftComments(query: string): Promise<Response>;
setAssignee(
changeNum: NumericChangeId,
assignee: AccountId
): Promise<Response>;
deleteAssignee(changeNum: NumericChangeId): Promise<Response>;
setChangeHashtag(
changeNum: NumericChangeId,
hashtag: HashtagsInput
): Promise<Hashtag[]>;
setChangeTopic(
changeNum: NumericChangeId,
topic: string | null
): Promise<string>;
}