blob: a39a532cbbbf53a7e127b000a39bf70b00a834c7 [file] [log] [blame]
/**
* @license
* Copyright (C) 2024 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 {customElement, query, property, state} from 'lit/decorators';
import {css, CSSResult, html, LitElement} from 'lit';
import {classMap} from 'lit/directives/class-map.js';
import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
declare global {
interface Window {
CANONICAL_PATH?: string;
}
}
// https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#capability-info
export interface AccountCapabilityInfo {
administrateServer: boolean;
'code-owners-checkCodeOwner': boolean;
}
@customElement('gr-check-code-owner')
export class GrCheckCodeOwner extends LitElement {
@query('#projectInput')
projectInput!: HTMLInputElement;
@query('#branchInput')
branchInput!: HTMLInputElement;
@query('#emailInput')
emailInput!: HTMLInputElement;
@query('#pathInput')
pathInput!: HTMLInputElement;
@query('#userInput')
userInput!: HTMLInputElement;
@query('#resultOutput')
resultOutput!: HTMLInputElement;
@query('#noteAboutLimitedDebugInformation')
noteAboutLimitedDebugInformation!: HTMLInputElement;
@property()
plugin!: PluginApi;
@state()
dataValid = false;
@state()
isChecking = false;
@state()
hasAdminPermissions = false;
static override get styles() {
return [
window.Gerrit.styles.font as CSSResult,
window.Gerrit.styles.form as CSSResult,
css`
main {
margin: 2em auto;
max-width: 50em;
}
.heading {
font-size: x-large;
font-weight: 500;
}
.output {
min-width: 50em;
}
.hidden {
display: none;
}
`,
];
}
override render() {
return html`
<main class="gr-form-styles read-only">
<div>
<h1 class="heading">Check Code Owner</h1>
</div>
<p>
Checks the code ownership of a user for a path in a branch, see
<a href="${window.CANONICAL_PATH || ''}/plugins/code-owners/Documentation/rest-api.html#check-code-owner" target="_blank">documentation<a/>.
</p>
<p>Required fields:</p>
<fieldset>
<section>
<span class="title">
<gr-tooltip-content
has-tooltip
title="The project from which the code owner configuration should be read."
>
Project* <gr-icon icon="info"></gr-icon>:
</gr-tooltip-content>
</span>
<span class="value">
<input
id="projectInput"
type="text"
@input=${this.validateData}
/>
</span>
</section>
<section>
<span class="title">
<gr-tooltip-content
has-tooltip
title="The branch from which the code owner configuration should be read."
>
Branch* <gr-icon icon="info"></gr-icon>:
</gr-tooltip-content>
</span>
<span class="value">
<input
id="branchInput"
type="text"
@input=${this.validateData}
/>
</span>
</section>
<section>
<span class="title">
<gr-tooltip-content
has-tooltip
title="Email for which the code ownership should be checked."
>
Email* <gr-icon icon="info"></gr-icon>:
</gr-tooltip-content>
</span>
<span class="value">
<input
id="emailInput"
type="text"
@input=${this.validateData}
/>
</span>
</section>
<section>
<span class="title">
<gr-tooltip-content
has-tooltip
title="Path for which the code ownership should be checked."
>
Path* <gr-icon icon="info"></gr-icon>:
</gr-tooltip-content>
</span>
<span class="value">
<input
id="pathInput"
type="text"
placeholder="/path/to/file.ext"
@input=${this.validateData}
/>
</span>
</section>
</fieldset>
<p>Admin options (usage requires having the
<a href="${window.CANONICAL_PATH || ''}/plugins/code-owners/Documentation/rest-api.html#checkCodeOwner" target="_blank">Check Code Owner</a>
or the
<a href="${window.CANONICAL_PATH || ''}/Documentation/access-control.html#capability_administrateServer" target="_blank">Administrate Server</a>
global capability):
<fieldset>
<section>
<span class="title">
<gr-tooltip-content
has-tooltip
title="User for which the code owner visibility should be checked.\nCan be any account identifier (e.g. email, account ID).\nIf not specified the code owner visibility is not checked.\nCan be used to investigate why a code owner is not shown/suggested to this user."
>
Calling User (optional) <gr-icon icon="info"></gr-icon>:
</gr-tooltip-content>
</span>
<span class="value">
<input
id="userInput"
type="text"
?disabled=${!this.hasAdminPermissions}
@input=${this.validateData}
/>
</span>
</section>
</fieldset>
<gr-button
@click=${this.handleCheckCodeOwner}
?disabled="${!this.dataValid || this.isChecking}"
>
Check Code Owner
</gr-button>
<p>
See
<a href="${window.CANONICAL_PATH || ''}/plugins/code-owners/Documentation/rest-api.html#code-owner-check-info" target="_blank">CheckCodeOwnerInfo</a>
for an explanation of the JSON fields.
</p>
<section>
<span class="value">
<iron-autogrow-textarea
class="output"
id="resultOutput"
readonly
>
</iron-autogrow-textarea>
</span>
</section>
<p
class=${classMap({hidden: this.hasAdminPermissions})}
>
Note: The calling user doesn't have the
<a href="${window.CANONICAL_PATH || ''}/plugins/code-owners/Documentation/rest-api.html#checkCodeOwner" target="_blank">Check Code Owner</a>
or the
<a href="${window.CANONICAL_PATH || ''}/Documentation/access-control.html#capability_administrateServer" target="_blank">Administrate Server</a>
global capability, hence the returned debug information (field
'debug_logs') is limited. If more information is needed, please reach
out to a host administrator to check the code ownership.
</p>
</main>
`;
}
override connectedCallback() {
super.connectedCallback();
this.checkAdminPermissions();
}
private async checkAdminPermissions() {
await this.plugin
.restApi()
.get<AccountCapabilityInfo>('/accounts/self/capabilities/')
.then(capabilities => {
this.hasAdminPermissions = capabilities &&
(capabilities['administrateServer'] ||
capabilities['code-owners-checkCodeOwner']);
});
}
private validateData() {
this.dataValid =
this.validateHasValue(this.projectInput.value) &&
this.validateHasValue(this.branchInput.value) &&
this.validateEmail(this.emailInput.value) &&
this.validateHasValue(this.pathInput.value);
}
private validateHasValue(value: string) {
if (value && value.trim().length > 0) {
return true;
}
return false;
}
private validateEmail(email: string) {
if (email && email.includes('@')) {
return true;
}
return false;
}
private async handleCheckCodeOwner() {
this.isChecking = true;
var project = this.projectInput.value.trim();
var branch = this.branchInput.value.trim();
var email = this.emailInput.value.trim();
var path = this.pathInput.value.trim();
var url = `/a/projects/${encodeURIComponent(project)}/branches/${encodeURIComponent(branch)}/code_owners.check/?email=${encodeURIComponent(email)}&path=${encodeURIComponent(path)}`;
if (this.userInput.value) {
url = url + `&user=${encodeURIComponent(this.userInput.value.trim())}`
}
url = url + "&pp=1";
try {
await this.plugin
.restApi()
.get<String>(url)
.then(response => {
this.resultOutput.value = JSON.stringify(response, null, 2);
})
.catch(error => {
this.dispatchEvent(
new CustomEvent('show-error', {
detail: {message: error},
bubbles: true,
composed: true,
})
);
});
} finally {
this.isChecking = false;
}
}
}