/** * @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. */ (function() { 'use strict'; const Defs = {}; /** * @typedef {{ * message: string, * icon: string, * class: string, * }} */ Defs.PushCertificateValidation; 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', }; 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', }; Polymer({ is: 'gr-change-metadata', /** * Fired when the change topic is changed. * * @event topic-changed */ properties: { /** @type {?} */ change: Object, labels: { type: Object, notify: true, }, account: Object, /** @type {?} */ revision: Object, commitInfo: Object, _mutable: { type: Boolean, computed: '_computeIsMutable(account)', }, /** * @type {{ note_db_enabled: string }} */ 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)', }, _showReviewersByState: { type: Boolean, computed: '_computeShowReviewersByState(serverConfig)', }, /** * @type {Defs.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)', }, /** @type {?} */ _CHANGE_ROLE: { type: Object, readOnly: true, value: { OWNER: 'owner', UPLOADER: 'uploader', AUTHOR: 'author', COMMITTER: 'committer', }, }, }, behaviors: [ Gerrit.RESTClientBehavior, ], observers: [ '_changeChanged(change)', '_labelsChanged(change.labels)', '_assigneeChanged(_assignee.*)', ], _labelsChanged(labels) { this.labels = Object.assign({}, labels) || null; }, _changeChanged(change) { this._assignee = change.assignee ? [change.assignee] : []; }, _assigneeChanged(assigneeRecord) { if (!this.change) { return; } const assignee = assigneeRecord.base; if (assignee.length) { const acct = assignee[0]; if (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; } this.set(['change', 'assignee'], undefined); this.$.restAPI.deleteAssignee(this.change._number); } }, _computeHideStrategy(change) { return !this.changeIsOpen(change.status); }, /** * @param {Object} commitInfo * @return {?Array} 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; } const weblinks = Gerrit.Nav.getChangeWeblinks( this.change ? this.change.repo : '', commitInfo.commit, { weblinks: commitInfo.web_links, config: serverConfig, }); return weblinks.length ? weblinks : null; }, _computeStrategy(change) { return SubmitTypeLabel[change.submit_type]; }, _computeLabelNames(labels) { return Object.keys(labels).sort(); }, _handleTopicChanged(e, topic) { const lastTopic = this.change.topic; if (!topic.length) { topic = null; } this._settingTopic = true; this.$.restAPI.setChangeTopic(this.change._number, topic) .then(newTopic => { this._settingTopic = false; this.set(['change', 'topic'], newTopic); if (newTopic !== lastTopic) { this.dispatchEvent( new CustomEvent('topic-changed', {bubbles: true})); } }); }, _showAddTopic(changeRecord, settingTopic) { const hasTopic = !!changeRecord && !!changeRecord.base.topic; return !hasTopic && !settingTopic; }, _showTopicChip(changeRecord, settingTopic) { const hasTopic = !!changeRecord && !!changeRecord.base.topic; return hasTopic && !settingTopic; }, _handleHashtagChanged(e) { const lastHashtag = this.change.hashtag; 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.dispatchEvent( new CustomEvent('hashtag-changed', {bubbles: true})); } }); }, _computeTopicReadOnly(mutable, change) { return !mutable || !change.actions || !change.actions.topic || !change.actions.topic.enabled; }, _computeHashtagReadOnly(mutable, change) { return !mutable || !change.actions || !change.actions.hashtags || !change.actions.hashtags.enabled; }, _computeAssigneeReadOnly(mutable, change) { return !mutable || !change.actions || !change.actions.assignee || !change.actions.assignee.enabled; }, _computeTopicPlaceholder(_topicReadOnly) { // Action items in Material Design are uppercase -- placeholder label text // is sentence case. return _topicReadOnly ? 'No topic' : 'ADD TOPIC'; }, _computeHashtagPlaceholder(_hashtagReadOnly) { return _hashtagReadOnly ? '' : HASHTAG_ADD_MESSAGE; }, _computeShowReviewersByState(serverConfig) { return !!serverConfig.note_db_enabled; }, _computeShowRequirements(change) { if (change.status !== this.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; return hasRequirements || hasLabels || !!change.work_in_progress; }, /** * @return {?Defs.PushCertificateValidation} object representing data for * the push validation. */ _computePushCertificateValidation(serverConfig, change) { if (!serverConfig || !serverConfig.receive || !serverConfig.receive.enable_signed_push) { return null; } const rev = change.revisions[change.current_revision]; if (!rev.push_certificate || !rev.push_certificate.key) { return { class: 'help', icon: 'gr-icons:help', message: 'This patch set was created without a push certificate', }; } const key = rev.push_certificate.key; switch (key.status) { case CertificateStatus.BAD: return { class: 'invalid', icon: 'gr-icons:close', message: this._problems('Push certificate is invalid', key), }; case CertificateStatus.OK: return { class: 'notTrusted', icon: 'gr-icons:info', message: this._problems( 'Push certificate is valid, but key is not trusted', key), }; case CertificateStatus.TRUSTED: return { class: 'trusted', icon: 'gr-icons:check', message: this._problems( 'Push certificate is valid and key is trusted', key), }; default: throw new Error(`unknown certificate status: ${key.status}`); } }, _problems(msg, key) { if (!key || !key.problems || key.problems.length === 0) { return msg; } return [msg + ':'].concat(key.problems).join('\n'); }, _computeProjectURL(project) { return Gerrit.Nav.getUrlForProjectChanges(project); }, _computeBranchURL(project, branch) { return Gerrit.Nav.getUrlForBranch(branch, project, this.change.status == this.ChangeStatus.NEW ? 'open' : this.change.status.toLowerCase()); }, _computeTopicURL(topic) { return Gerrit.Nav.getUrlForTopic(topic); }, _computeHashtagURL(hashtag) { return Gerrit.Nav.getUrlForHashtag(hashtag); }, _handleTopicRemoved(e) { const target = Polymer.dom(e).rootTarget; 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})); }).catch(err => { target.disabled = false; return; }); }, _handleHashtagRemoved(e) { e.preventDefault(); const target = Polymer.dom(e).rootTarget; 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; }); }, _computeIsWip(change) { return !!change.work_in_progress; }, _computeShowRoleClass(change, role) { 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 accound or null. */ _getNonOwnerRole(change, role) { if (!change.current_revision || !change.revisions[change.current_revision]) { return null; } const rev = change.revisions[change.current_revision]; if (!rev) { return null; } if (role === this._CHANGE_ROLE.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) { return rev.commit.author; } if (role === this._CHANGE_ROLE.COMMITTER && rev.commit && rev.commit.committer && change.owner.email !== rev.commit.committer.email) { return rev.commit.committer; } return null; }, _computeParents(change) { if (!change.current_revision || !change.revisions[change.current_revision] || !change.revisions[change.current_revision].commit) { return undefined; } return change.revisions[change.current_revision].commit.parents; }, _computeParentsLabel(parents) { return parents.length > 1 ? 'Parents' : 'Parent'; }, _computeParentListClass(parents, parentIsCurrent) { return [ 'parentList', parents.length > 1 ? 'merge' : 'nonMerge', parentIsCurrent ? 'current' : 'notCurrent', ].join(' '); }, _computeIsMutable(account) { return !!Object.keys(account).length; }, editTopic() { if (this._topicReadOnly || this.change.topic) { return; } // Cannot use `this.$.ID` syntax because the element exists inside of a // dom-if. this.$$('.topicEditableLabel').open(); }, }); })();