[task scheduler] Add task-sk
Change-Id: Ie5d702e7abbf5104db980fd9d31f332bc4e1a8b7
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/323103
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
Reviewed-by: Weston Tracey <westont@google.com>
diff --git a/task_scheduler/modules/job-trigger-sk/job-trigger-sk-demo.ts b/task_scheduler/modules/job-trigger-sk/job-trigger-sk-demo.ts
index da44d1c..67335fb 100644
--- a/task_scheduler/modules/job-trigger-sk/job-trigger-sk-demo.ts
+++ b/task_scheduler/modules/job-trigger-sk/job-trigger-sk-demo.ts
@@ -1,5 +1,6 @@
-import { SetupMocks } from '../rpc-mock';
-
-SetupMocks();
-
import './index';
+import { JobTriggerSk } from './job-trigger-sk';
+import { FakeTaskSchedulerService } from '../rpc-mock';
+
+const ele = <JobTriggerSk>document.querySelector("job-trigger-sk")!;
+ele.rpc = new FakeTaskSchedulerService();
diff --git a/task_scheduler/modules/job-trigger-sk/job-trigger-sk.ts b/task_scheduler/modules/job-trigger-sk/job-trigger-sk.ts
index 22c9ace..16958466 100644
--- a/task_scheduler/modules/job-trigger-sk/job-trigger-sk.ts
+++ b/task_scheduler/modules/job-trigger-sk/job-trigger-sk.ts
@@ -80,7 +80,7 @@
`;
private jobs: TriggerJob[] = [{jobName: "", commitHash: ""}];
- private rpc: TaskSchedulerService = GetTaskSchedulerService(this);
+ private _rpc: TaskSchedulerService | null = null;
private triggeredJobs: TriggeredJob[] = [];
constructor() {
@@ -92,6 +92,13 @@
this._render();
}
+ get rpc(): TaskSchedulerService | null {
+ return this._rpc;
+ }
+ set rpc(rpc: TaskSchedulerService | null) {
+ this._rpc = rpc;
+ }
+
private addJob() {
this.jobs.push({jobName: "", commitHash: ""});
this._render();
@@ -103,6 +110,9 @@
}
private triggerJobs() {
+ if (!this.rpc) {
+ return;
+ }
const req: TriggerJobsRequest = {
jobs: this.jobs,
}
diff --git a/task_scheduler/modules/rpc-mock/fake-data.ts b/task_scheduler/modules/rpc-mock/fake-data.ts
index e640794..915395d 100644
--- a/task_scheduler/modules/rpc-mock/fake-data.ts
+++ b/task_scheduler/modules/rpc-mock/fake-data.ts
@@ -84,7 +84,7 @@
finishedAt: "2019-02-19T13:27:14.669965Z",
id: "QT5J8rNsgnumXH67JwTr",
isolatedOutput: "f43fcadbbffe79a92f5da6792ed992581aa620bd",
- jobs: [job1ID, job2ID],
+ jobs: [job1ID/*, job2ID*/],
maxAttempts: 2,
parentTaskIds: [
task0.id,
diff --git a/task_scheduler/modules/rpc-mock/index.ts b/task_scheduler/modules/rpc-mock/index.ts
index eedc481..d812498 100644
--- a/task_scheduler/modules/rpc-mock/index.ts
+++ b/task_scheduler/modules/rpc-mock/index.ts
@@ -12,7 +12,6 @@
GetSkipTaskRulesRequest,
GetSkipTaskRulesResponse,
Job,
- MockRPCsForTesting,
SearchJobsRequest,
SearchJobsResponse,
SearchTasksRequest,
@@ -22,23 +21,25 @@
TriggerJobsRequest,
TriggerJobsResponse,
} from '../rpc';
+import { job1, task0, task1, task2, task3, task4 } from './fake-data';
export * from './fake-data';
/**
- * SetupMocks changes the rpc module to use the mocked client from this module.
- */
-export function SetupMocks() {
- MockRPCsForTesting(new FakeTaskSchedulerService())
-}
-
-/**
* FakeTaskSchedulerService provides a mocked implementation of
* TaskSchedulerService.
*/
-class FakeTaskSchedulerService implements TaskSchedulerService {
- private jobs: {[key:string]:Job} = {};
- private tasks: {[key:string]:Task} = {};
+export class FakeTaskSchedulerService implements TaskSchedulerService {
+ private jobs: {[key:string]:Job} = {
+ [job1.id]: job1,
+ };
+ private tasks: {[key:string]:Task} = {
+ [task0.id]: task0,
+ [task1.id]: task1,
+ [task2.id]: task2,
+ [task3.id]: task3,
+ [task4.id]: task4,
+ };
private jobID: number = 0;
triggerJobs(triggerJobsRequest: TriggerJobsRequest): Promise<TriggerJobsResponse> {
@@ -59,7 +60,9 @@
return new Promise((_, reject) => { reject("not implemented")});
}
getTask(getTaskRequest: GetTaskRequest): Promise<GetTaskResponse> {
- return new Promise((_, reject) => { reject("not implemented")});
+ return Promise.resolve({
+ task: this.tasks[getTaskRequest.id],
+ });
}
searchTasks(searchTasksRequest: SearchTasksRequest): Promise<SearchTasksResponse> {
return new Promise((_, reject) => { reject("not implemented")});
diff --git a/task_scheduler/modules/rpc/index.ts b/task_scheduler/modules/rpc/index.ts
index cb5195c..66a279d 100644
--- a/task_scheduler/modules/rpc/index.ts
+++ b/task_scheduler/modules/rpc/index.ts
@@ -1,48 +1,39 @@
import {
- TaskSchedulerService,
- TaskSchedulerServiceClient,
- } from "./rpc";
+ TaskSchedulerService,
+ TaskSchedulerServiceClient,
+} from "./rpc";
- export * from "./rpc";
+export * from "./rpc";
+/**
+ * GetTaskSchedulerService returns an TaskSchedulerService implementation
+ * which dispatches events indicating when requests have started and ended.
+ *
+ * @param ele The parent element, used to dispatch events.
+ */
+export function GetTaskSchedulerService(ele: HTMLElement): TaskSchedulerService {
const host = window.location.protocol + "//" + window.location.host;
let rpcClient: TaskSchedulerService = new TaskSchedulerServiceClient(host, window.fetch.bind(window));
-
- /**
- * GetTaskSchedulerService returns an TaskSchedulerService implementation
- * which dispatches events indicating when requests have started and ended.
- *
- * @param ele The parent element, used to dispatch events.
- */
- export function GetTaskSchedulerService(ele: HTMLElement): TaskSchedulerService {
- const handler = {
- get(target: any, propKey: any, receiver: any) {
- const origMethod = target[propKey];
- return function(...args: any[]) {
- ele.dispatchEvent(new CustomEvent('begin-task', { bubbles: true }));
- return origMethod.apply(rpcClient, args).then((v: any) => {
- ele.dispatchEvent(new CustomEvent('end-task', { bubbles: true }));
- return v;
- }).catch((err: any) => {
- ele.dispatchEvent(new CustomEvent('fetch-error', {
- detail: {
- error: err,
- loading: propKey,
- },
- bubbles: true,
- }));
- Promise.reject(err);
- });
- }
+ const handler = {
+ get(target: any, propKey: any, receiver: any) {
+ const origMethod = target[propKey];
+ return function(...args: any[]) {
+ ele.dispatchEvent(new CustomEvent('begin-task', { bubbles: true }));
+ return origMethod.apply(rpcClient, args).then((v: any) => {
+ ele.dispatchEvent(new CustomEvent('end-task', { bubbles: true }));
+ return v;
+ }).catch((err: any) => {
+ ele.dispatchEvent(new CustomEvent('fetch-error', {
+ detail: {
+ error: err,
+ loading: propKey,
+ },
+ bubbles: true,
+ }));
+ Promise.reject(err);
+ });
}
- };
- return new Proxy(rpcClient, handler);
- }
-
- /**
- * MockRPCsForTesting switches this module to use the given
- * TaskSchedulerService for testing purposes.
- */
- export function MockRPCsForTesting(repl: TaskSchedulerService) {
- rpcClient = repl;
- }
\ No newline at end of file
+ }
+ };
+ return new Proxy(rpcClient, handler);
+}
diff --git a/task_scheduler/modules/task-graph-sk/task-graph-sk.ts b/task_scheduler/modules/task-graph-sk/task-graph-sk.ts
index 7ea920c..c73e120 100644
--- a/task_scheduler/modules/task-graph-sk/task-graph-sk.ts
+++ b/task_scheduler/modules/task-graph-sk/task-graph-sk.ts
@@ -5,7 +5,7 @@
* Displays a graph which shows the relationship between a set of tasks.
*/
-import { html, render, svg, TemplateResult } from 'lit-html';
+import { render, svg } from 'lit-html';
import { define } from 'elements-sk/define';
import {
diff --git a/task_scheduler/modules/task-sk/index.ts b/task_scheduler/modules/task-sk/index.ts
new file mode 100644
index 0000000..7ee8f70
--- /dev/null
+++ b/task_scheduler/modules/task-sk/index.ts
@@ -0,0 +1,2 @@
+import './task-sk';
+import './task-sk.scss';
diff --git a/task_scheduler/modules/task-sk/task-sk-demo.html b/task_scheduler/modules/task-sk/task-sk-demo.html
new file mode 100644
index 0000000..e5feeacf
--- /dev/null
+++ b/task_scheduler/modules/task-sk/task-sk-demo.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>task-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>task-sk</h1>
+ <task-sk></task-sk>
+</body>
+</html>
diff --git a/task_scheduler/modules/task-sk/task-sk-demo.ts b/task_scheduler/modules/task-sk/task-sk-demo.ts
new file mode 100644
index 0000000..b11e8fe
--- /dev/null
+++ b/task_scheduler/modules/task-sk/task-sk-demo.ts
@@ -0,0 +1,7 @@
+import { task2, FakeTaskSchedulerService } from '../rpc-mock';
+import './index';
+import { TaskSk } from './task-sk';
+
+const ele = <TaskSk>document.querySelector('task-sk')!;
+ele.rpc = new FakeTaskSchedulerService();
+ele.taskID = task2.id;
diff --git a/task_scheduler/modules/task-sk/task-sk.scss b/task_scheduler/modules/task-sk/task-sk.scss
new file mode 100644
index 0000000..cde3132
--- /dev/null
+++ b/task_scheduler/modules/task-sk/task-sk.scss
@@ -0,0 +1,3 @@
+task-sk {
+ font-family: Arial;
+}
diff --git a/task_scheduler/modules/task-sk/task-sk.ts b/task_scheduler/modules/task-sk/task-sk.ts
new file mode 100644
index 0000000..83fe6df
--- /dev/null
+++ b/task_scheduler/modules/task-sk/task-sk.ts
@@ -0,0 +1,242 @@
+/**
+ * @module modules/task-sk
+ * @description <h2><code>task-sk</code></h2>
+ *
+ * Displays basic information about a task, including a graph display of its
+ * context within the job(s) which utilize it.
+ *
+ * @attr {string} swarming - URL of the Swarming server.
+ * @attr {string} taskID - Unique ID of the task to display.
+ */
+import { diffDate } from 'common-sk/modules/human';
+import { define } from 'elements-sk/define';
+import 'elements-sk/styles/table';
+import { html } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { $$ } from 'common-sk/modules/dom';
+import {
+ GetTaskSchedulerService,
+ Job,
+ Task,
+ TaskSchedulerService,
+ TaskStatus,
+ GetTaskResponse,
+ GetJobResponse,
+} from '../rpc';
+import { TaskGraphSk } from '../task-graph-sk/task-graph-sk';
+import '../task-graph-sk';
+import '../../../infra-sk/modules/human-date-sk';
+
+const taskStatusToTextColor = new Map<TaskStatus, [string, string]>();
+taskStatusToTextColor.set(TaskStatus.TASK_STATUS_PENDING, [
+ 'pending',
+ 'rgb(255, 255, 255)',
+]);
+taskStatusToTextColor.set(TaskStatus.TASK_STATUS_RUNNING, [
+ 'running',
+ 'rgb(248, 230, 180)',
+]);
+taskStatusToTextColor.set(TaskStatus.TASK_STATUS_SUCCESS, [
+ 'succeeded',
+ 'rgb(209, 228, 188)',
+]);
+taskStatusToTextColor.set(TaskStatus.TASK_STATUS_FAILURE, [
+ 'failed',
+ 'rgb(217, 95, 2)',
+]);
+taskStatusToTextColor.set(TaskStatus.TASK_STATUS_MISHAP, [
+ 'mishap',
+ 'rgb(117, 112, 179)',
+]);
+
+export class TaskSk extends ElementSk {
+ private static template = (ele: TaskSk) => html`
+ <div class="container">
+ <h2>Task Information</h2>
+ <table>
+ <tr>
+ <td>ID</td>
+ <td>${ele.task!.id}</td>
+ </tr>
+ <tr>
+ <td>Name</td>
+ <td>${ele.task!.taskKey!.name}</td>
+ </tr>
+ <tr>
+ <td>Status</td>
+ <td style="background-color:${ele.statusColor}">${ele.statusText}</td>
+ </tr>
+ <tr>
+ <td>Created</td>
+ <td>
+ <human-date-sk .date="${ele.task!.createdAt!}"></human-date-sk>
+ </td>
+ </tr>
+ ${ele.task!.finishedAt
+ ? html`
+ <tr>
+ <td>Finished</td>
+ <td>
+ <human-date-sk
+ .date="${ele.task!.finishedAt!}"
+ ></human-date-sk>
+ </td>
+ </tr>
+ `
+ : html``}
+ <tr>
+ <td>Duration</td>
+ <td>${ele.duration}</td>
+ </tr>
+ <tr>
+ <td>Repo</td>
+ <td>
+ <a href="${ele.task!.taskKey!.repoState!.repo}" target="_blank"
+ >${ele.task!.taskKey!.repoState!.repo}</a
+ >
+ </td>
+ </tr>
+ <tr>
+ <td>Revision</td>
+ <td>
+ <a href="${ele.revisionLink}" target="_blank"
+ >${ele.task!.taskKey!.repoState!.revision}</a
+ >
+ </td>
+ </tr>
+ <tr>
+ <td>Swarming Task</td>
+ <td>
+ <a href="${ele.swarmingTaskLink}" target="_blank"
+ >${ele.task!.swarmingTaskId}</a
+ >
+ </td>
+ </tr>
+ <tr>
+ <td>Jobs</td>
+ <td>
+ ${ele.jobs.map(
+ (job: Job) => html` <a href="/job/${job.id}">${job.name}</a> `
+ )}
+ </td>
+ </tr>
+ ${ele.isTryJob
+ ? html`
+ <tr>
+ <td>Codereview Link</td>
+ <td>
+ <a href="${ele.codereviewLink}" target="_blank"
+ >${ele.codereviewLink}</a
+ >
+ </td>
+ </tr>
+ <tr>
+ <td>Codereview Server</td>
+ <td>${ele.task!.taskKey!.repoState!.patch!.server}</td>
+ </tr>
+ <tr>
+ <td>Issue</td>
+ <td>${ele.task!.taskKey!.repoState!.patch!.issue}</td>
+ </tr>
+ <tr>
+ <td>Patchset</td>
+ <td>${ele.task!.taskKey!.repoState!.patch!.patchset}</td>
+ </tr>
+ `
+ : html``}
+ </table>
+ </div>
+
+ <div class="container">
+ <h2>Context</h2>
+ <task-graph-sk></task-graph-sk>
+ </div>
+ `;
+
+ private codereviewLink: string = '';
+ private duration: string = '';
+ private isTryJob: boolean = false;
+ private jobs: Job[] = [];
+ private revisionLink: string = '';
+ private _rpc: TaskSchedulerService | null = null;
+ private statusColor: string = '';
+ private statusText: string = '';
+ private swarmingTaskLink: string = '';
+ private task: Task | null = null;
+
+ constructor() {
+ super(TaskSk.template);
+ }
+
+ get taskID(): string {
+ return this.getAttribute('task') || '';
+ }
+
+ set taskID(taskID: string) {
+ this.setAttribute('task', taskID);
+ this.reload();
+ }
+
+ get swarming(): string {
+ return this.getAttribute('swarming') || '';
+ }
+
+ set swarming(swarming: string) {
+ this.setAttribute('swarming', swarming);
+ }
+
+ get rpc(): TaskSchedulerService | null {
+ return this._rpc;
+ }
+
+ set rpc(rpc: TaskSchedulerService | null) {
+ this._rpc = rpc;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.rpc = GetTaskSchedulerService(this);
+ this.reload();
+ }
+
+ private reload() {
+ if (!this.taskID || !this.rpc) {
+ return;
+ }
+ this.rpc!.getTask({
+ id: this.taskID,
+ includeStats: false,
+ }).then((taskResp: GetTaskResponse) => {
+ this.task = taskResp.task!;
+ const start = new Date(this.task.createdAt!);
+ const end = this.task.finishedAt
+ ? new Date(this.task.finishedAt)
+ : new Date();
+ this.duration = diffDate(start.getTime(), end.getTime());
+ const rs = this.task.taskKey!.repoState!;
+ this.revisionLink = `${rs.repo}/+show/${rs.revision}`;
+ if (rs.patch) {
+ this.isTryJob = true;
+ const p = rs.patch!;
+ this.codereviewLink = `${p.server}/c/${p.issue}/${p.patchset}`;
+ }
+ [this.statusText, this.statusColor] = taskStatusToTextColor.get(
+ this.task.status
+ )!;
+ this.swarmingTaskLink = `https://${this.swarming}/task?id=${this.task.swarmingTaskId}`;
+ const jobReqs = this.task.jobs!.map((jobID: string) =>
+ this.rpc!.getJob({ id: jobID })
+ );
+ Promise.all(jobReqs).then((jobResps: GetJobResponse[]) => {
+ this.jobs = jobResps
+ .map((resp: GetJobResponse) => resp.job!)
+ .sort((a: Job, b: Job) => (a.name < b.name ? -1 : 1));
+ this._render();
+ const graph = $$<TaskGraphSk>('task-graph-sk', this);
+ graph?.draw(this.jobs, this.swarming, taskResp.task);
+ });
+ });
+ }
+}
+
+define('task-sk', TaskSk);
diff --git a/task_scheduler/modules/task-sk/task-sk_puppeteer_test.ts b/task_scheduler/modules/task-sk/task-sk_puppeteer_test.ts
new file mode 100644
index 0000000..0c2bcb0
--- /dev/null
+++ b/task_scheduler/modules/task-sk/task-sk_puppeteer_test.ts
@@ -0,0 +1,24 @@
+import * as path from 'path';
+import {
+ setUpPuppeteerAndDemoPageServer,
+ takeScreenshot,
+} from '../../../puppeteer-tests/util';
+import { TaskSk } from './task-sk';
+import { task2, FakeTaskSchedulerService } from '../rpc-mock';
+
+describe('task-sk', () => {
+ const testBed = setUpPuppeteerAndDemoPageServer(
+ path.join(__dirname, '..', '..', 'webpack.config.ts')
+ );
+
+ beforeEach(async () => {
+ await testBed.page.goto(`${testBed.baseUrl}/dist/task-sk.html`);
+ await testBed.page.setViewport({ width: 1429, height: 836 });
+ });
+
+ describe('screenshots', () => {
+ it('shows the default view', async () => {
+ await takeScreenshot(testBed.page, 'task-scheduler', 'task-sk');
+ });
+ });
+});