blob: 729ead82c9135fe323aeea1527970d03b35483f3 [file] [log] [blame]
/**
* @fileoverview The bulk of the Metrics Analysis page of CT.
*/
import 'elements-sk/icon/delete-icon-sk';
import 'elements-sk/icon/cancel-icon-sk';
import 'elements-sk/icon/check-circle-icon-sk';
import 'elements-sk/icon/help-icon-sk';
import 'elements-sk/toast-sk';
import '../suggest-input-sk';
import '../input-sk';
import '../patch-sk';
import '../task-repeater-sk';
import '../task-priority-sk';
import { $$, $ } from 'common-sk/modules/dom';
import { define } from 'elements-sk/define';
import 'elements-sk/select-sk';
import { errorMessage } from 'elements-sk/errorMessage';
import { html } from 'lit-html';
import { jsonOrThrow } from 'common-sk/modules/jsonOrThrow';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import { InputSk } from '../input-sk/input-sk';
import { PatchSk } from '../patch-sk/patch-sk';
import { TaskPrioritySk } from '../task-priority-sk/task-priority-sk';
import { TaskRepeaterSk } from '../task-repeater-sk/task-repeater-sk';
import {
combineClDescriptions,
moreThanThreeActiveTasksChecker,
} from '../ctfe_utils';
import {
MetricsAnalysisAddTaskVars,
EditTaskRequest,
} from '../json';
interface ExpandableTextArea extends InputSk {
open: boolean;
}
export class MetricsAnalysisSk extends ElementSk {
private _triggeringTask: boolean = false;
private _moreThanThreeActiveTasks = moreThanThreeActiveTasksChecker();
// Variables that represent UI elements.
private metricName!: InputSk;
private analysisTaskId!: InputSk;
private customTraces!: ExpandableTextArea;
private benchmarkArgs!: InputSk;
private valueColumnName!: InputSk;
private description!: InputSk;
private chromiumPatch!: PatchSk;
private catapultPatch!: PatchSk;
private repeatAfterDays!: TaskRepeaterSk;
private taskPriority!: TaskPrioritySk;
private ccList!: InputSk;
constructor() {
super(MetricsAnalysisSk.template);
}
private static template = (el: MetricsAnalysisSk) => html`
<table class=options>
<tr>
<td>Metric Name</td>
<td>
<input-sk
id=metric_name
label="The metric to parse the provided traces with. Eg: loadingMetric"
class=medium-field
></input-sk>
</td>
</tr>
<tr>
<td>Source of Traces</td>
<td>
<input-sk
id=analysis_task_id
label="Analysis Task Id"
class=medium-field
></input-sk>
<expandable-textarea-sk
id=custom_traces
minRows=5
displaytext="Specify custom list of traces"
placeholder="Eg: trace1,trace2,trace3"
@click=${el._toggleAnalysisTaskId}>
</expandable-textarea-sk>
</td>
</tr>
<tr>
<td>Benchmark Arguments</td>
<td>
<input-sk value="--output-format=csv" id=benchmark_args class=long-field></input-sk>
<span class=smaller-font>These will be the arguments to the analysis_metrics_ct benchmark.</span><br/>
<span class=smaller-font><b>Note:</b> Use --run-benchmark-timeout=[secs] to specify the timeout of the run_benchmark script. 300 is the default.</span><br/>
<span class=smaller-font><b>Note:</b> Use --max-pages-per-bot=[num] to specify the number of pages to run per bot. 50 is the default.</span>
</td>
</tr>
<tr>
<td>Field Value Column Name</td>
<td>
<input-sk value="avg" id=value_column_name class="medium-field"></input-sk>
<span class="smaller-font">Which column's entries to use as field values.</span>
</td>
</tr>
<tr>
<td>
Chromium Git patch (optional)<br/>
Applied to Chromium ToT<br/>
</td>
<td>
<patch-sk id=chromium_patch
patchType=chromium
@cl-description-changed=${el._patchChanged}>
</patch-sk>
</td>
</tr>
<tr>
<td>
Catapult Git patch (optional)<br/>
Applied to Catapult Rev in <a href="https://chromium.googlesource.com/chromium/src/+show/HEAD/DEPS">DEPS</a>
</td>
<td>
<patch-sk id=catapult_patch
patchType=catapult
@cl-description-changed=${el._patchChanged}>
</patch-sk>
</td>
</tr>
<tr>
<td>Repeat this task</td>
<td>
<task-repeater-sk id=repeat_after_days></task-repeater-sk>
</td>
</tr>
<tr>
<td>Task Priority</td>
<td>
<task-priority-sk id=task_priority></task-priority-sk>
</td>
</tr>
<tr>
<td>
Notifications CC list (optional)<br/>
Email will be sent by ct@skia.org
</td>
<td>
<input-sk value="" id=cc_list label="email1,email2,email3" class=long-field></input-sk>
</td>
</tr>
<tr>
<td>Description</td>
<td>
<input-sk value="" id=description label="Description is required" class=long-field></input-sk>
</td>
</tr>
<tr>
<td colspan="2" class="center">
<div class="triggering-spinner">
<spinner-sk .active=${el._triggeringTask} alt="Trigger task"></spinner-sk>
</div>
<button id=submit ?disabled=${el._triggeringTask} @click=${el._validateTask}>Queue Task
</button>
</td>
</tr>
<tr>
<td colspan=2 class=center>
<button id=view_history @click=${el._gotoRunsHistory}>View runs history</button>
</td>
</tr>
</table>
`;
connectedCallback(): void {
super.connectedCallback();
this._render();
this.metricName = $$<InputSk>('#metric_name', this)!;
this.analysisTaskId = $$<InputSk>('#analysis_task_id', this)!;
this.customTraces = $$<ExpandableTextArea>('#custom_traces', this)!;
this.benchmarkArgs = $$<InputSk>('#benchmark_args', this)!;
this.valueColumnName = $$<InputSk>('#value_column_name', this)!;
this.description = $$<InputSk>('#description', this)!;
this.chromiumPatch = $$<PatchSk>('#chromium_patch', this)!;
this.catapultPatch = $$<PatchSk>('#catapult_patch', this)!;
this.repeatAfterDays = $$<TaskRepeaterSk>('#repeat_after_days', this)!;
this.taskPriority = $$<TaskPrioritySk>('#task_priority', this)!;
this.ccList = $$<InputSk>('#cc_list', this)!;
// If template_id is specified then load the template.
const params = new URLSearchParams(window.location.search);
const template_id = params.get('template_id');
if (template_id) {
this.handleTemplateID(template_id);
}
}
handleTemplateID(template_id: string): void {
this.dispatchEvent(new CustomEvent('begin-task', { bubbles: true }));
const req: EditTaskRequest = { id: +template_id };
fetch('/_/edit_metrics_analysis_task', { method: 'POST', body: JSON.stringify(req) })
.then(jsonOrThrow)
.then((json: MetricsAnalysisAddTaskVars) => {
// Populate all fields from the EditTaskRequest.
this.metricName.value = json.metric_name;
this.analysisTaskId.value = json.analysis_task_id;
this.benchmarkArgs.value = json.benchmark_args;
this.valueColumnName.value = json.value_column_name;
this.description.value = json.desc;
// If custom webpages are specified then populate the field and expand it.
if (json.custom_traces) {
this.customTraces.value = json.custom_traces;
if (!this.customTraces.open) {
($$('button', this.customTraces) as HTMLElement).click();
}
}
// Patches.
if (json.chromium_patch) {
this.chromiumPatch.patch = json.chromium_patch;
this.chromiumPatch.expandTextArea();
}
if (json.catapult_patch) {
this.catapultPatch.patch = json.catapult_patch;
this.catapultPatch.expandTextArea();
}
this.repeatAfterDays.frequency = json.repeat_after_days;
this.taskPriority.priority = json.task_priority;
if (json.cc_list) {
this.ccList.value = json.cc_list.join(',');
}
// Focus and then blur the benchmark name so that we go back to the
// top of the page.
this.metricName.querySelector('input')!.focus();
this.metricName.querySelector('input')!.blur();
})
.catch(errorMessage)
.finally(() => {
this._render();
this.dispatchEvent(new CustomEvent('end-task', { bubbles: true }));
});
}
_toggleAnalysisTaskId(): void {
if (this.customTraces.open === this.analysisTaskId.hidden) {
// This click wasn't toggling the expandable textarea.
return;
}
this.analysisTaskId.hidden = this.customTraces.open;
// We assume if someone opens the custom traces window they will be using
// custom traces, and if they close it, they won't be.
if (this.customTraces.open) {
this.customTraces.value = '';
} else {
this.customTraces.value = '';
}
this._render();
}
_patchChanged(): void {
this.description.value = combineClDescriptions(
$('patch-sk', this).map((patch) => (patch as PatchSk).clDescription),
);
}
_validateTask(): void {
if (!$('patch-sk', this).every((patch) => (patch as PatchSk).validate())) {
return;
}
if (!this.metricName.value) {
errorMessage('Please specify a metric name');
this.metricName.focus();
return;
}
if (!this.analysisTaskId.value && !this.customTraces.value) {
errorMessage('Please specify an analysis task id or custom traces');
this.analysisTaskId.focus();
return;
}
if (!this.description.value) {
errorMessage('Please specify a description');
this.description.focus();
return;
}
if (this._moreThanThreeActiveTasks()) {
return;
}
const confirmed = window.confirm('Proceed with queueing task?');
if (confirmed) {
this._queueTask();
}
}
_queueTask(): void {
this._triggeringTask = true;
const params = {} as MetricsAnalysisAddTaskVars;
params.metric_name = this.metricName.value;
params.analysis_task_id = this.analysisTaskId.value;
params.custom_traces = this.customTraces.value;
params.benchmark_args = this.benchmarkArgs.value;
params.value_column_name = this.valueColumnName.value;
params.desc = this.description.value;
params.chromium_patch = this.chromiumPatch.patch;
params.catapult_patch = this.catapultPatch.patch;
params.repeat_after_days = this.repeatAfterDays.frequency;
params.task_priority = this.taskPriority.priority;
if (this.ccList.value) {
params.cc_list = this.ccList.value.split(',');
}
fetch('/_/add_metrics_analysis_task', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
.then(() => this._gotoRunsHistory())
.catch((e) => {
this._triggeringTask = false;
errorMessage(e);
});
}
_gotoRunsHistory(): void {
window.location.href = '/metrics_analysis_runs/';
}
}
define('metrics-analysis-sk', MetricsAnalysisSk);