[task scheduler] Add skip-tasks-sk element
Change-Id: If7f824ca91be014fb4b02ba94019410282ed2421
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/328908
Reviewed-by: Weston Tracey <westont@google.com>
Commit-Queue: Eric Boren <borenet@google.com>
diff --git a/task_scheduler/modules/rpc-mock/fake-data.ts b/task_scheduler/modules/rpc-mock/fake-data.ts
index 5e846dc..0078cd8 100644
--- a/task_scheduler/modules/rpc-mock/fake-data.ts
+++ b/task_scheduler/modules/rpc-mock/fake-data.ts
@@ -1,5 +1,5 @@
import { Job, Task } from '../rpc';
-import { TaskStatus, JobStatus, RepoState } from '../rpc/rpc';
+import { TaskStatus, JobStatus, RepoState, SkipTaskRule } from '../rpc/rpc';
export const job1ID = 'aYwjrLWysQRUW2lGFQvR';
export const job2ID = 'bYwjrLWysQRUW2lGFQvX';
@@ -379,3 +379,25 @@
},
],
};
+
+export const skipRule1: SkipTaskRule = {
+ addedBy: 'you@google.com',
+ taskSpecPatterns: ['Test-.*', 'Perf-.*'],
+ commits: ['abc123'],
+ description: 'Skip all test and perf tasks at abc123',
+ name: 'No test/perf @ abc',
+};
+
+export const skipRule2: SkipTaskRule = {
+ addedBy: 'me@google.com',
+ commits: ['def456'],
+ description: 'Skip everything at def456',
+ name: 'def456 is bad',
+};
+
+export const skipRule3: SkipTaskRule = {
+ addedBy: 'you@google.com',
+ taskSpecPatterns: ['BadTask'],
+ description: 'Skip all BadTasks at every commit',
+ name: 'BadTask is bad!',
+};
diff --git a/task_scheduler/modules/rpc-mock/index.ts b/task_scheduler/modules/rpc-mock/index.ts
index d6ebad5..5fd1599 100644
--- a/task_scheduler/modules/rpc-mock/index.ts
+++ b/task_scheduler/modules/rpc-mock/index.ts
@@ -21,8 +21,19 @@
TriggerJobsRequest,
TriggerJobsResponse,
} from '../rpc';
-import { job1, task0, task1, task2, task3, task4, job2 } from './fake-data';
-import { JobStatus } from '../rpc/rpc';
+import {
+ job1,
+ task0,
+ task1,
+ task2,
+ task3,
+ task4,
+ job2,
+ skipRule1,
+ skipRule2,
+ skipRule3,
+} from './fake-data';
+import { JobStatus, SkipTaskRule } from '../rpc/rpc';
export * from './fake-data';
@@ -43,6 +54,7 @@
[task4.id]: task4,
};
private jobID: number = 0;
+ private skipRules = [skipRule1, skipRule2, skipRule3];
triggerJobs(
triggerJobsRequest: TriggerJobsRequest
@@ -122,22 +134,26 @@
getSkipTaskRules(
getSkipTaskRulesRequest: GetSkipTaskRulesRequest
): Promise<GetSkipTaskRulesResponse> {
- return new Promise((_, reject) => {
- reject('not implemented');
- });
+ return Promise.resolve({ rules: this.skipRules.slice() });
}
addSkipTaskRule(
addSkipTaskRuleRequest: AddSkipTaskRuleRequest
): Promise<AddSkipTaskRuleResponse> {
- return new Promise((_, reject) => {
- reject('not implemented');
+ this.skipRules.push({
+ addedBy: 'you@google.com',
+ taskSpecPatterns: addSkipTaskRuleRequest.taskSpecPatterns,
+ commits: addSkipTaskRuleRequest.commits,
+ description: addSkipTaskRuleRequest.description,
+ name: addSkipTaskRuleRequest.name,
});
+ return Promise.resolve({ rules: this.skipRules.slice() });
}
deleteSkipTaskRule(
deleteSkipTaskRuleRequest: DeleteSkipTaskRuleRequest
): Promise<DeleteSkipTaskRuleResponse> {
- return new Promise((_, reject) => {
- reject('not implemented');
- });
+ this.skipRules = this.skipRules.filter(
+ (rule: SkipTaskRule) => rule.name != deleteSkipTaskRuleRequest.id
+ );
+ return Promise.resolve({ rules: this.skipRules.slice() });
}
}
diff --git a/task_scheduler/modules/skip-tasks-sk/index.ts b/task_scheduler/modules/skip-tasks-sk/index.ts
new file mode 100644
index 0000000..cc73e13
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/index.ts
@@ -0,0 +1,2 @@
+import './skip-tasks-sk';
+import './skip-tasks-sk.scss';
diff --git a/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.html b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.html
new file mode 100644
index 0000000..71a0af3
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>skip-tasks-sk</title>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+<body>
+ <h1>skip-tasks-sk</h1>
+ <skip-tasks-sk></skip-tasks-sk>
+
+ <h2>Events</h2>
+ <pre id=events></pre>
+</body>
+</html>
diff --git a/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.ts b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.ts
new file mode 100644
index 0000000..6b04e67
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk-demo.ts
@@ -0,0 +1,6 @@
+import './index';
+import { SkipTasksSk } from './skip-tasks-sk';
+import { FakeTaskSchedulerService } from '../rpc-mock';
+
+const ele = <SkipTasksSk>document.querySelector('skip-tasks-sk')!;
+ele.rpc = new FakeTaskSchedulerService();
diff --git a/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.scss b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.scss
new file mode 100644
index 0000000..7f7131d
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.scss
@@ -0,0 +1,16 @@
+skip-tasks-sk {
+ .grid {
+ display: grid;
+ grid-template-columns: auto auto;
+ }
+ button {
+ background: inherit;
+ border: none;
+ cursor: pointer;
+ }
+
+ table.new-rule tr {
+ background: inherit;
+ border: none;
+ }
+}
diff --git a/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.ts b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.ts
new file mode 100644
index 0000000..48af0fd
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk.ts
@@ -0,0 +1,222 @@
+/**
+ * @module modules/skip-tasks-sk
+ * @description <h2><code>skip-tasks-sk</code></h2>
+ *
+ * Provides UI for manipulating rules to prevent triggering of matching tasks.
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import '../../../infra-sk/modules/multi-input-sk';
+import { MultiInputSk } from '../../../infra-sk/modules/multi-input-sk/multi-input-sk';
+import {
+ AddSkipTaskRuleResponse,
+ TaskSchedulerService,
+ SkipTaskRule,
+ GetSkipTaskRulesResponse,
+ DeleteSkipTaskRuleResponse,
+} from '../rpc';
+import { $$ } from 'common-sk/modules/dom';
+import 'elements-sk/icon/add-icon-sk';
+import 'elements-sk/icon/delete-icon-sk';
+import 'elements-sk/styles/buttons';
+import 'elements-sk/styles/table';
+
+export class SkipTasksSk extends ElementSk {
+ private static template = (ele: SkipTasksSk) => html`
+ ${
+ ele.rules
+ ? html`
+ <table>
+ <tr>
+ <th><!-- delete button--></th>
+ <th>Name</th>
+ <th>Added by</th>
+ <th>TaskSpec Patterns</th>
+ <th>Commits</th>
+ <th>Description</th>
+ </tr>
+ ${ele.rules.map(
+ (rule) => html`
+ <tr>
+ <td>
+ <button @click="${() => ele.deleteRule(rule)}">
+ <delete-icon-sk></delete-icon-sk>
+ </button>
+ </td>
+ <td>${rule.name}</td>
+ <td>${rule.addedBy}</td>
+ <td>
+ ${rule.taskSpecPatterns?.map(
+ (pattern) => html`
+ <div class="task_spec_pattern">${pattern}</div>
+ `
+ )}
+ </td>
+ <td>
+ ${rule.commits?.map(
+ (commit) => html` <div class="commit">${commit}</div> `
+ )}
+ </td>
+ <td>${rule.description}</td>
+ </tr>
+ `
+ )}
+ </table>
+ `
+ : html``
+ }
+ <button class="secondary-container-themes-sk" @click="${() => {
+ $$<HTMLDialogElement>('dialog', ele)!.showModal();
+ }}">
+ <add-icon-sk></add-icon-sk>Add Rule
+ </button>
+ <dialog>
+ <h2>New Rule</h2>
+ <table class="new-rule">
+ <tr>
+ <td>
+ <label for="input-name">Rule Name</label>
+ </td>
+ <td>
+ <input id="input-name"></input>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="input-task-specs">Task Spec(s)</label>
+ </td>
+ <td>
+ <multi-input-sk id="input-task-specs"></multi-input-sk>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="range-checkbox">Commit Range?</label>
+ </td>
+ <td>
+ <input
+ type="checkbox"
+ id="range-checkbox"
+ ?checked="${ele.isCommitRange}"
+ @change="${(ev: Event) => {
+ ele.isCommitRange = (<HTMLInputElement>ev.target).checked;
+ ele._render();
+ }}"
+ >
+ </input>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="input-range-start">
+ ${
+ ele.isCommitRange ? 'Range start (oldest; inclusive)' : 'Commit'
+ }
+ </label>
+ </td>
+ <td>
+ <input id="input-range-start"></input>
+ </td>
+ </tr>
+ ${
+ ele.isCommitRange
+ ? html`
+ <tr>
+ <td>
+ <label for="input-range-end">Range end (newest; non-inclusive)</label>
+ </td>
+ <td>
+ <input id="input-range-end"></input>
+ </td>
+ </tr>
+ `
+ : html``
+ }
+ <tr>
+ <td>
+ <label for="description">Rule Description</label>
+ </td>
+ <td>
+ <textarea id="input-description" rows="5"></textarea>
+ </td>
+ </tr>
+ </table>
+ <button id="add-button" class="secondary-container-themes-sk" @click="${
+ ele.addRule
+ }">Add Rule</button>
+ <button @click="${() => {
+ $$<HTMLDialogElement>('dialog', ele)?.close();
+ }}">
+ Cancel
+ </button>
+ </dialog>
+ `;
+
+ private isCommitRange: boolean = false;
+ private _rpc: TaskSchedulerService | null = null;
+ private rules: SkipTaskRule[] = [];
+
+ get rpc(): TaskSchedulerService | null {
+ return this._rpc;
+ }
+
+ set rpc(rpc: TaskSchedulerService | null) {
+ this._rpc = rpc;
+ this.reload();
+ }
+
+ constructor() {
+ super(SkipTasksSk.template);
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ }
+
+ private addRule() {
+ const name = $$<HTMLInputElement>('#input-name', this)!.value;
+ const description = $$<HTMLTextAreaElement>('#input-description', this)!
+ .value;
+ const commitStart = $$<HTMLInputElement>('#input-range-start', this)!.value;
+ const commits = [];
+ if (commitStart) {
+ commits.push(commitStart);
+ if (this.isCommitRange) {
+ const commitEnd = $$<HTMLInputElement>('#input-range-end', this)?.value;
+ if (commitEnd) {
+ commits.push(commitEnd);
+ }
+ }
+ }
+ const taskSpecs = $$<MultiInputSk>('#input-task-specs')!.values;
+ this.rpc!.addSkipTaskRule({
+ name: name,
+ commits: commits,
+ description: description,
+ taskSpecPatterns: taskSpecs,
+ }).then((resp: AddSkipTaskRuleResponse) => {
+ this.rules = resp.rules!;
+ this._render();
+ });
+ $$<HTMLDialogElement>('dialog', this)!.close();
+ }
+
+ private deleteRule(rule: SkipTaskRule) {
+ this.rpc!.deleteSkipTaskRule({ id: rule.name }).then(
+ (resp: DeleteSkipTaskRuleResponse) => {
+ this.rules = resp.rules!;
+ this._render();
+ }
+ );
+ }
+
+ private reload() {
+ this.rpc!.getSkipTaskRules({}).then((resp: GetSkipTaskRulesResponse) => {
+ this.rules = resp.rules!;
+ this._render();
+ });
+ }
+}
+
+define('skip-tasks-sk', SkipTasksSk);
diff --git a/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk_puppeteer_test.ts b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk_puppeteer_test.ts
new file mode 100644
index 0000000..04a3eb4
--- /dev/null
+++ b/task_scheduler/modules/skip-tasks-sk/skip-tasks-sk_puppeteer_test.ts
@@ -0,0 +1,55 @@
+import * as path from 'path';
+import { expect } from 'chai';
+import {
+ setUpPuppeteerAndDemoPageServer,
+ takeScreenshot,
+} from '../../../puppeteer-tests/util';
+
+describe('skip-tasks-sk', () => {
+ const testBed = setUpPuppeteerAndDemoPageServer(
+ path.join(__dirname, '..', '..', 'webpack.config.ts')
+ );
+
+ beforeEach(async () => {
+ await testBed.page.goto(`${testBed.baseUrl}/dist/skip-tasks-sk.html`);
+ await testBed.page.setViewport({ width: 550, height: 550 });
+ });
+
+ it('should render the demo page (smoke test)', async () => {
+ expect(await testBed.page.$$('skip-tasks-sk')).to.have.length(1);
+ });
+
+ describe('screenshots', () => {
+ it('starting point', async () => {
+ await takeScreenshot(
+ testBed.page,
+ 'task-scheduler',
+ 'skip-tasks-sk_start'
+ );
+ });
+ it('adds a rule', async () => {
+ await testBed.page.click('add-icon-sk');
+ await testBed.page.type('#input-name', 'New Rule');
+ await testBed.page.type('#input-task-specs input', '.*');
+ // TODO(borenet): I would like to use a commit range here, but I was
+ // unable to automate the checking of the checkbox and subsequent
+ // rendering of the new input field.
+ await testBed.page.type('#input-range-start', 'abc123');
+ await testBed.page.type(
+ '#input-description',
+ 'This is a detailed description of the rule.'
+ );
+ await takeScreenshot(
+ testBed.page,
+ 'task-scheduler',
+ 'skip-tasks-sk_adding-rule'
+ );
+ await testBed.page.click('#add-button');
+ await takeScreenshot(
+ testBed.page,
+ 'task-scheduler',
+ 'skip-tasks-sk_added-rule'
+ );
+ });
+ });
+});