|  | /** | 
|  | * @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(); | 
|  | }, | 
|  | }); | 
|  | })(); |