blob: c9c9d91fcb55d5f7430657f0a7202c54995fcb34 [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 {OwnerStatus} from './code-owners-fetcher.js';
import {CodeOwnersModelMixin} from './code-owners-model-mixin.js';
const MAGIC_FILES = ['/COMMIT_MSG', '/MERGE_LIST', '/PATCHSET_LEVEL'];
const STATUS_CODE = {
PENDING: 'pending',
PENDING_OLD_PATH: 'pending-old-path',
MISSING: 'missing',
MISSING_OLD_PATH: 'missing-old-path',
APPROVED: 'approved',
ERROR: 'error',
ERROR_OLD_PATH: 'error-old-path',
};
const STATUS_PRIORITY_ORDER = [
STATUS_CODE.ERROR,
STATUS_CODE.ERROR_OLD_PATH,
STATUS_CODE.MISSING,
STATUS_CODE.PENDING,
STATUS_CODE.MISSING_OLD_PATH,
STATUS_CODE.PENDING_OLD_PATH,
STATUS_CODE.APPROVED,
];
const STATUS_ICON = {
[STATUS_CODE.PENDING]: 'gr-icons:schedule',
[STATUS_CODE.MISSING]: 'gr-icons:close',
[STATUS_CODE.PENDING_OLD_PATH]: 'gr-icons:schedule',
[STATUS_CODE.MISSING_OLD_PATH]: 'gr-icons:close',
[STATUS_CODE.APPROVED]: 'gr-icons:check',
[STATUS_CODE.ERROR]: 'gr-icons:info-outline',
[STATUS_CODE.ERROR]: 'gr-icons:info-outline',
};
const STATUS_TOOLTIP = {
[STATUS_CODE.PENDING]: 'Pending code owner approval',
[STATUS_CODE.MISSING]: 'Missing code owner approval',
[STATUS_CODE.PENDING_OLD_PATH]:
'Pending code owner approval on pre-renamed file',
[STATUS_CODE.MISSING_OLD_PATH]:
'Missing code owner approval on pre-renamed file',
[STATUS_CODE.APPROVED]: 'Approved by code owner',
[STATUS_CODE.ERROR]: 'Failed to fetch code owner status',
[STATUS_CODE.ERROR_OLD_PATH]: 'Failed to fetch code owner status',
};
class BaseEl extends CodeOwnersModelMixin(Polymer.Element) {
static get properties() {
return {
patchRange: Object,
hidden: {
type: Boolean,
reflectToAttribute: true,
computed: 'computeHidden(change, patchRange, ' +
'model.status.newerPatchsetUploaded)',
},
};
}
computeHidden(change, patchRange, newerPatchsetUploaded) {
if ([change, patchRange, newerPatchsetUploaded].includes(undefined)) {
return true;
}
// if code-owners is not a submit requirement, don't show status column
if (change.requirements &&
!change.requirements.find(r => r.type === 'code-owners')) {
return true;
}
if (newerPatchsetUploaded) return true;
const latestPatchset = change.revisions[change.current_revision];
// Note: in some special cases, patchNum is undefined on latest patchset
// like after publishing the edit, still show for them
// TODO: this should be fixed in Gerrit
if (patchRange.patchNum === undefined) return false;
// only show if its latest patchset
if (`${patchRange.patchNum}` !== `${latestPatchset._number}`) return true;
return false;
}
}
/**
* Column header element for owner status.
*/
export class OwnerStatusColumnHeader extends BaseEl {
static get is() {
return 'owner-status-column-header';
}
static get template() {
return Polymer.html`
<style include="shared-styles">
:host(:not([hidden])) {
display: block;
padding-right: var(--spacing-m);
width: 3em;
}
</style>
<div></div>
`;
}
}
customElements.define(OwnerStatusColumnHeader.is, OwnerStatusColumnHeader);
/**
* Row content element for owner status.
*/
export class OwnerStatusColumnContent extends BaseEl {
static get is() {
return 'owner-status-column-content';
}
static get properties() {
return {
path: String,
oldPath: String,
cleanlyMergedPaths: Array,
cleanlyMergedOldPaths: Array,
ownerService: Object,
statusIcon: {
type: String,
computed: '_computeIcon(status)',
},
statusInfo: {
type: String,
computed: '_computeTooltip(status)',
},
status: {
type: String,
reflectToAttribute: true,
},
};
}
static get template() {
return Polymer.html`
<style include="shared-styles">
:host(:not([hidden])) {
display:block;
padding-right: var(--spacing-m);
width: 3em;
text-align: center;
}
iron-icon {
padding: var(--spacing-xs) 0px;
}
:host([status=approved]) iron-icon {
color: var(--positive-green-text-color);
}
:host([status=pending]) iron-icon {
color: #ffa62f;
}
:host([status=missing]) iron-icon,
:host([status=error]) iron-icon {
color: var(--negative-red-text-color);
}
</style>
<gr-tooltip-content title="[[statusInfo]]" has-tooltip>
<iron-icon icon="[[statusIcon]]"></iron-icon>
</gr-tooltip-content>
`;
}
static get observers() {
return [
'computeStatusIcon(model.status,path, oldPath, cleanlyMergedPaths, ' +
'cleanlyMergedOldPaths)',
];
}
loadPropertiesAfterModelChanged() {
super.loadPropertiesAfterModelChanged();
this.modelLoader.loadStatus();
}
computeStatusIcon(
modelStatus,
path,
oldPath,
cleanlyMergedPaths,
cleanlyMergedOldPaths
) {
if (
modelStatus === undefined ||
([path, oldPath].includes(undefined) && cleanlyMergedPaths === undefined)
) {
return;
}
const codeOwnerStatusMap = modelStatus.codeOwnerStatusMap;
const paths = path === undefined ? cleanlyMergedPaths : [path];
const oldPaths = oldPath === undefined ? cleanlyMergedOldPaths : [oldPath];
const statuses = paths
.filter(path => !MAGIC_FILES.includes(path))
.map(path => this._computeStatus(codeOwnerStatusMap.get(path)));
// oldPath may contain null, so filter that as well.
const oldStatuses = oldPaths
.filter(path => !MAGIC_FILES.includes(path) && !!path)
.map(path => this._computeStatus(codeOwnerStatusMap.get(path), true));
const allStatuses = statuses.concat(oldStatuses);
if (allStatuses.length === 0) {
return;
}
this.status = allStatuses.reduce((a, b) => {
return STATUS_PRIORITY_ORDER.indexOf(a) <
STATUS_PRIORITY_ORDER.indexOf(b)
? a
: b;
});
}
_computeIcon(status) {
return STATUS_ICON[status];
}
_computeTooltip(status) {
return STATUS_TOOLTIP[status];
}
_computeStatus(statusItem, oldPath = false) {
if (statusItem === undefined) {
return oldPath ? STATUS_CODE.ERROR_OLD_PATH : STATUS_CODE.ERROR;
} else if (statusItem.status === OwnerStatus.INSUFFICIENT_REVIEWERS) {
return oldPath ? STATUS_CODE.MISSING_OLD_PATH : STATUS_CODE.MISSING;
} else if (statusItem.status === OwnerStatus.PENDING) {
return oldPath ? STATUS_CODE.PENDING_OLD_PATH : STATUS_CODE.PENDING;
} else {
return STATUS_CODE.APPROVED;
}
}
}
customElements.define(OwnerStatusColumnContent.is, OwnerStatusColumnContent);