| /** |
| * @license |
| * Copyright (C) 2020 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. |
| */ |
| |
| import {CodeOwnerService, OwnerStatus} from './code-owners-service.js'; |
| import {ownerState} from './owner-ui-state.js'; |
| |
| /** |
| * Owner requirement control for `submit-requirement-item-code-owners` endpoint. |
| * |
| * This will show the status and suggest owners button next to |
| * the code-owners submit requirement. |
| */ |
| export class OwnerRequirementValue extends Polymer.Element { |
| static get is() { |
| return 'owner-requirement-value'; |
| } |
| |
| static get template() { |
| return Polymer.html` |
| <style include="shared-styles"> |
| :host { |
| --gr-button: { |
| padding: 0px; |
| } |
| } |
| p.loading { |
| display: flex; |
| align-content: center; |
| align-items: center; |
| justify-content: center; |
| } |
| .loadingSpin { |
| display: inline-block; |
| margin-right: var(--spacing-m); |
| width: 18px; |
| height: 18px; |
| } |
| gr-button { |
| padding-left: var(--spacing-m); |
| } |
| </style> |
| <p class="loading" hidden="[[!_isLoading]]"> |
| <span class="loadingSpin"></span> |
| Loading status ... |
| </p> |
| <template is="dom-if" if="[[!_isLoading]]"> |
| <span>[[_computeStatusText(_statusCount, _isOverriden)]]</span> |
| <template is="dom-if" if="[[!_allApproved]]"> |
| <gr-button link on-click="_openReplyDialog"> |
| Suggest owners |
| </gr-button> |
| </template> |
| </template> |
| `; |
| } |
| |
| static get properties() { |
| return { |
| change: Object, |
| reporting: Object, |
| restApi: Object, |
| |
| ownerService: Object, |
| |
| _statusCount: Object, |
| _isLoading: Boolean, |
| _allApproved: { |
| type: Boolean, |
| computed: '_computeAllApproved(_statusCount)', |
| }, |
| _isOverriden: Boolean, |
| }; |
| } |
| |
| static get observers() { |
| return [ |
| 'onInputChanged(restApi, change, reporting)', |
| ]; |
| } |
| |
| _checkIfOverriden() { |
| this.ownerService.getProjectConfig().then(res => { |
| if (!res['override_approval']) { |
| // no override label configured |
| this._isOverriden = false; |
| return; |
| } |
| |
| const overridenLabel = res['override_approval'].label; |
| const overridenValue = res['override_approval'].value; |
| |
| if (this.change.labels[overridenLabel]) { |
| const votes = this.change.labels[overridenLabel].all || []; |
| if (votes.find(v => `${v.value}` === `${overridenValue}`)) { |
| this._isOverriden = true; |
| return; |
| } |
| } |
| |
| // otherwise always reset it to false |
| this._isOverriden = false; |
| }); |
| } |
| |
| _updateStatus() { |
| this._isLoading = true; |
| this.reporting.reportLifeCycle('owners-submit-requirement-summary-start'); |
| |
| return this.ownerService.getStatus() |
| .then(({rawStatuses}) => { |
| this._statusCount = this._getStatusCount(rawStatuses); |
| |
| // Send a metric with overall summary when code owners submit |
| // requirement shown and finished fetching status |
| this.reporting.reportLifeCycle( |
| 'owners-submit-requirement-summary-shown', |
| {...this._statusCount} |
| ); |
| }) |
| .finally(() => { |
| this._isLoading = false; |
| }); |
| } |
| |
| _computeAllApproved(statusCount) { |
| return statusCount.missing === 0 |
| && statusCount.pending === 0; |
| } |
| |
| _getStatusCount(rawStatuses) { |
| return rawStatuses |
| .reduce((prev, cur) => { |
| const oldPathStatus = cur.old_path_status; |
| const newPathStatus = cur.new_path_status; |
| if (this._isMissing(newPathStatus.status)) { |
| prev.missing ++; |
| } else if (this._isPending(newPathStatus.status)) { |
| prev.pending ++; |
| } else if (oldPathStatus) { |
| // check oldPath if newPath approved |
| if (this._isMissing(oldPathStatus.status)) { |
| prev.missing ++; |
| } else if (this._isPending(oldPathStatus.status)) { |
| prev.pending ++; |
| } |
| } else { |
| prev.approved ++; |
| } |
| return prev; |
| }, {missing: 0, pending: 0, approved: 0}); |
| } |
| |
| _computeStatusText(statusCount, isOverriden) { |
| const statusText = []; |
| if (statusCount.missing) { |
| statusText.push(`${statusCount.missing} missing`); |
| } |
| |
| if (statusCount.pending) { |
| statusText.push(`${statusCount.pending} pending`); |
| } |
| |
| if (!statusText.length) { |
| statusText.push(isOverriden ? 'Approved (Owners-Override)' : 'Approved'); |
| } |
| |
| return statusText.join(', '); |
| } |
| |
| _isMissing(status) { |
| return status === OwnerStatus.INSUFFICIENT_REVIEWERS; |
| } |
| |
| _isPending(status) { |
| return status === OwnerStatus.PENDING; |
| } |
| |
| onInputChanged(restApi, change, reporting) { |
| if ([restApi, change, reporting].includes(undefined)) return; |
| this.ownerService = CodeOwnerService.getOwnerService(this.restApi, change); |
| this._updateStatus(); |
| this._checkIfOverriden(); |
| } |
| |
| _openReplyDialog() { |
| this.dispatchEvent( |
| new CustomEvent('open-reply-dialog', { |
| detail: {}, |
| composed: true, |
| bubbles: true, |
| }) |
| ); |
| ownerState.expandSuggestion = true; |
| |
| this.reporting.reportInteraction('suggest-owners-from-submit-requirement'); |
| } |
| } |
| |
| customElements.define(OwnerRequirementValue.is, OwnerRequirementValue); |