blob: e005c47bccbc2b8b2a7e910263168947f987234e [file] [log] [blame]
/**
* @module module/verfiers-detail-sk
* @description <h2><code>verifiers-detail-sk</code></h2>
*
* Displays the details of all verifiers that have run for a
* change+patchset.
*
* @attr {number} change_id - The change ID of the Gerrit change.
*
* @attr {number} patchset_id - The patchset ID of the Gerrit change.
*
*/
import { html, TemplateResult } from 'lit/html.js';
import { define } from '../../../elements-sk/modules/define';
import { upgradeProperty } from '../../../elements-sk/modules/upgradeProperty';
import { diffDate } from '../../../infra-sk/modules/human';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import '../../../elements-sk/modules/error-toast-sk';
import '../../../elements-sk/modules/icons/folder-icon-sk';
import '../../../elements-sk/modules/icons/help-icon-sk';
import '../../../elements-sk/modules/icons/home-icon-sk';
import '../../../elements-sk/modules/spinner-sk';
import '../../../infra-sk/modules/app-sk';
import '../../../infra-sk/modules/theme-chooser-sk';
import { escapeAndLinkify } from '../../../infra-sk/modules/linkify';
import { doImpl } from '../skcq';
import {
GetChangeAttemptsRequest,
GetChangeAttemptsResponse,
ChangeAttempts,
ChangeAttempt,
VerifierState,
} from '../json';
const VerifierStateToClassName: Record<VerifierState, string> = {
SUCCESSFUL: 'success',
WAITING: 'waiting',
ABORTED: 'aborted',
FAILURE: 'failed',
};
export class VerifiersDetailSk extends ElementSk {
noData: boolean = true;
updatingData: boolean = true;
changeAttempts: ChangeAttempts | null = null;
constructor() {
super(VerifiersDetailSk.template);
}
private static template = (el: VerifiersDetailSk) => html`
<spinner-sk ?active=${el.updatingData}></spinner-sk>
<div ?hidden=${el.noData}>
<h3>
SkCQ attempts for
<a
href="http://skia-review.googlesource.com/c/${el.changeID}/${el.patchsetID}"
target="_blank"
>${el.changeID}/${el.patchsetID}</a
>
</h3>
${el.displayAttempts()}
</div>
${el.displayNotFoundMsg(el.noData, el.updatingData)}
`;
connectedCallback(): void {
super.connectedCallback();
this.fetchAttempts();
this._render();
}
disconnectedCallback(): void {
super.disconnectedCallback();
}
/** @prop change_id The Change ID of the Gerrit change. */
get changeID(): number {
return +this.getAttribute('change_id')!;
}
/** @prop patchset_id The Patchset ID of the Gerrit change. */
get patchsetID(): number {
return +this.getAttribute('patchset_id')!;
}
private displayNotFoundMsg(
hidden: boolean,
updatingData: boolean
): TemplateResult {
if (hidden && !updatingData) {
return html`<h3>
No data available for
<a
href="http://skia-review.googlesource.com/c/${this.changeID}/${this
.patchsetID}"
target="_blank"
>${this.changeID}/${this.patchsetID}</a
>
</h3>`;
}
return html``;
}
private displayAttempts(): TemplateResult[] {
if (!this.changeAttempts || !this.changeAttempts!.attempts) {
return [];
}
return this.changeAttempts!.attempts.reverse().map(
(attempt, index) => html`
<span
class="attempt-section ${this.getStateClass(
attempt!.overall_status
)}">
<table class="attempt-table">
<tr>
<th width="33%">
Attempt #${this.changeAttempts!.attempts!.length - index}
${this.getDryRunText(attempt!)}
</th>
<th width="33%">${attempt?.overall_status}</th>
<th width="33%">
Total duration: ${this.getTotalDurationText(attempt!)}
</th>
</tr>
</table>
<br />
<b>Start time</b> ${new Date(
attempt!.start_ts * 1000
).toLocaleString()}<br />
${this.getCommittedText(attempt!)}
${this.displaySubmittableChanges(attempt!)}
<br />
<b>State of Verifiers</b>
${this.displayVerifiers(attempt!)}
</span>
`
);
}
private displayVerifiers(attempt: ChangeAttempt): TemplateResult[] {
if (!attempt.verifiers_statuses || !attempt.verifiers_statuses.length) {
return [];
}
return attempt.verifiers_statuses.map(
(verifier) => html`
<table class="verifier-table ${this.getStateClass(verifier!.state)}">
<tr>
<td width="33%">${verifier?.name}</td>
<td width="33%">${verifier?.state}</td>
<td width="33%">
${this.getVerificationDurationText(
verifier!.start_ts,
verifier!.stop_ts
)}
</td>
</tr>
<tr>
<td colspan="3">${this.displayReason(verifier!.reason)}</td>
</tr>
</table>
`
);
}
private displayReason(reason: string): TemplateResult[] {
const lines = reason.split('\n');
return lines.map((line) => html` ${escapeAndLinkify(line)}<br /> `);
}
private getStateClass(state: VerifierState): string {
return VerifierStateToClassName[state];
}
private getVerificationDurationText(
start: number,
stop: number
): TemplateResult {
let endTime = Date.now();
let additionalInfo = '(still running)';
if (stop) {
endTime = stop * 1000;
additionalInfo = '';
}
return html`Total duration: ${diffDate(start * 1000, endTime)}
${additionalInfo}<br />`;
}
private displaySubmittableChanges(attempt: ChangeAttempt): TemplateResult {
if (
!attempt.dry_run &&
attempt.submittable_changes &&
attempt.submittable_changes.length > 0
) {
return html`<b>Changes submitted together</b>
<ul>
${attempt.submittable_changes.map(
(ch) =>
html`<li>
<a
href="http://skia-review.googlesource.com/c/${ch}"
target="_blank"
>${ch}</a
>
</li>`
)}
</ul> `;
}
return html``;
}
private getDryRunText(attempt: ChangeAttempt): TemplateResult {
if (attempt.dry_run) {
return html`(Dry-Run)`;
}
return html`(CQ)`;
}
private getTotalDurationText(attempt: ChangeAttempt): TemplateResult {
let endTime = Date.now();
let additionalInfo = '(still running)';
if (attempt.stop_ts) {
endTime = attempt.stop_ts * 1000;
additionalInfo = '';
}
if (attempt.cq_abandoned) {
additionalInfo = '(was abandoned)';
}
return html`${diffDate((attempt?.start_ts as number) * 1000, endTime)}
${additionalInfo}`;
}
private getCommittedText(attempt: ChangeAttempt): TemplateResult {
if (attempt.committed_ts) {
const d = new Date(attempt.committed_ts * 1000).toLocaleString();
return html`<b>Patch committed at</b> ${d}<br />`;
}
return html``;
}
private fetchAttempts() {
if (!this.changeID || !this.patchsetID) {
return;
}
const detail: GetChangeAttemptsRequest = {
change_id: this.changeID,
patchset_id: this.patchsetID,
};
doImpl<GetChangeAttemptsRequest, GetChangeAttemptsResponse>(
'/_/get_change_attempts',
detail,
(json: GetChangeAttemptsResponse) => {
this.changeAttempts = json.change_attempts!;
this.noData =
!this.changeAttempts || this.changeAttempts.attempts!.length === 0;
this.updatingData = false;
this._render();
}
);
}
}
define('verifiers-detail-sk', VerifiersDetailSk);