blob: ce8b37a4bfdaa0e0b7da7e28544df68e14ba4112 [file] [log] [blame]
/**
* @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);
}
a {
text-decoration: none;
}
</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="[[_overrideInfoUrl]]">
<a on-click="_reportDocClick" href="[[_overrideInfoUrl]]" target="_blank">
<iron-icon icon="gr-icons:help-outline" title="Documentation for overriding code owners"></iron-icon>
</a>
</template>
<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: {
type: Boolean,
value: true,
},
_allApproved: {
type: Boolean,
computed: '_computeAllApproved(_statusCount)',
},
_isOverriden: Boolean,
_overrideInfoUrl: String,
};
}
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);
this.ownerService.getLoggedInUserInitialRole().then(role => {
// 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, user_role: role}
);
});
})
.finally(() => {
this._isLoading = false;
});
}
_updateOverrideInfoUrl() {
this.ownerService.getProjectConfig().then(config => {
this._overrideInfoUrl = config.general && config.general.override_info_url
?
config.general.override_info_url : '';
});
}
_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();
this._updateOverrideInfoUrl();
}
_openReplyDialog() {
this.dispatchEvent(
new CustomEvent('open-reply-dialog', {
detail: {},
composed: true,
bubbles: true,
})
);
ownerState.expandSuggestion = true;
this.ownerService.getLoggedInUserInitialRole().then(role => {
this.reporting.reportInteraction(
'suggest-owners-from-submit-requirement', {user_role: role});
});
}
}
customElements.define(OwnerRequirementValue.is, OwnerRequirementValue);