[task scheduler] Add job timeline page
Change-Id: Ia7376c3991fc400a23315268ef44257143978c26
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/217383
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/task_scheduler/elements.html b/task_scheduler/elements.html
index 643ef75..7284af7 100644
--- a/task_scheduler/elements.html
+++ b/task_scheduler/elements.html
@@ -16,6 +16,7 @@
<link rel="import" href="/res/imp/job-sk.html" />
<link rel="import" href="/res/imp/job-search-sk.html" />
+ <link rel="import" href="/res/imp/job-timeline-sk.html" />
<link rel="import" href="/res/imp/job-trigger-sk.html" />
<link rel="import" href="/res/imp/task-scheduler-blacklist-sk.html" />
<link rel="import" href="/res/imp/task-scheduler-status-sk.html" />
diff --git a/task_scheduler/go/task_scheduler/main.go b/task_scheduler/go/task_scheduler/main.go
index 85cb7e3..f711925 100644
--- a/task_scheduler/go/task_scheduler/main.go
+++ b/task_scheduler/go/task_scheduler/main.go
@@ -72,12 +72,13 @@
repos repograph.Map
// HTML templates.
- blacklistTemplate *template.Template = nil
- jobTemplate *template.Template = nil
- jobSearchTemplate *template.Template = nil
- mainTemplate *template.Template = nil
- taskTemplate *template.Template = nil
- triggerTemplate *template.Template = nil
+ blacklistTemplate *template.Template = nil
+ jobTemplate *template.Template = nil
+ jobSearchTemplate *template.Template = nil
+ jobTimelineTemplate *template.Template = nil
+ mainTemplate *template.Template = nil
+ taskTemplate *template.Template = nil
+ triggerTemplate *template.Template = nil
// Flags.
btInstance = flag.String("bigtable_instance", "", "BigTable instance to use.")
@@ -136,6 +137,11 @@
filepath.Join(*resourcesDir, "templates/header.html"),
filepath.Join(*resourcesDir, "templates/footer.html"),
))
+ jobTimelineTemplate = template.Must(template.ParseFiles(
+ filepath.Join(*resourcesDir, "templates/job_timeline.html"),
+ filepath.Join(*resourcesDir, "templates/header.html"),
+ filepath.Join(*resourcesDir, "templates/footer.html"),
+ ))
mainTemplate = template.Must(template.ParseFiles(
filepath.Join(*resourcesDir, "templates/main.html"),
filepath.Join(*resourcesDir, "templates/header.html"),
@@ -386,6 +392,59 @@
}
}
+func jobTimelineHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+
+ // Don't use cached templates in testing mode.
+ if *local {
+ reloadTemplates()
+ }
+
+ jobId, ok := mux.Vars(r)["id"]
+ if !ok {
+ httputils.ReportError(w, r, nil, "Job ID is required.")
+ return
+ }
+
+ job, err := tsDb.GetJobById(jobId)
+ if err != nil {
+ httputils.ReportError(w, r, err, "Failed to retrieve Job.")
+ return
+ }
+ var tasks = make([]*types.Task, 0, len(job.Tasks))
+ for _, summaries := range job.Tasks {
+ for _, t := range summaries {
+ task, err := tsDb.GetTaskById(t.Id)
+ if err != nil {
+ httputils.ReportError(w, r, err, "Failed to retrieve Task.")
+ return
+ }
+ tasks = append(tasks, task)
+ }
+ }
+ enc, err := json.Marshal(&struct {
+ Job *types.Job `json:"job"`
+ Tasks []*types.Task `json:"tasks"`
+ Epochs []time.Time `json:"epochs"`
+ }{
+ Job: job,
+ Tasks: tasks,
+ Epochs: []time.Time{}, // TODO(borenet): Record tick timestamps.
+ })
+ if err != nil {
+ httputils.ReportError(w, r, err, "Failed to encode JSON.")
+ return
+ }
+ if err := jobTimelineTemplate.Execute(w, struct {
+ Data string
+ }{
+ Data: string(enc),
+ }); err != nil {
+ httputils.ReportError(w, r, err, "Failed to execute template.")
+ return
+ }
+}
+
func taskHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
@@ -502,6 +561,7 @@
r.HandleFunc("/", mainHandler)
r.HandleFunc("/blacklist", blacklistHandler)
r.HandleFunc("/job/{id}", jobHandler)
+ r.HandleFunc("/job/{id}/timeline", jobTimelineHandler)
r.HandleFunc("/jobs/search", jobSearchHandler)
r.HandleFunc("/task/{id}", taskHandler)
r.HandleFunc("/trigger", triggerHandler)
diff --git a/task_scheduler/res/imp/job-timeline-sk-demo.html b/task_scheduler/res/imp/job-timeline-sk-demo.html
index cd4250e..59c3b6e 100644
--- a/task_scheduler/res/imp/job-timeline-sk-demo.html
+++ b/task_scheduler/res/imp/job-timeline-sk-demo.html
@@ -71,7 +71,12 @@
</script>
<link rel="import" href="job-timeline-sk.html">
<link rel="import" href="/res/common/imp/error-toast-sk.html">
- <link rel="import" href="/res/imp/bower_components/paper-toggle-button/paper-toggle-button.html" />
+ <style>
+ body {
+ display: flex;
+ flex-direction: column;
+ }
+ </style>
</head>
<body>
<h1>job-timeline-sk demo</h1>
diff --git a/task_scheduler/res/imp/job-timeline-sk.html b/task_scheduler/res/imp/job-timeline-sk.html
index 7dfdb2d..5a0f88c 100644
--- a/task_scheduler/res/imp/job-timeline-sk.html
+++ b/task_scheduler/res/imp/job-timeline-sk.html
@@ -28,8 +28,22 @@
<link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
<dom-module id="job-timeline-sk">
<template>
+ <style>
+ :host{
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ }
+ #svg {
+ flex-grow: 1;
+ min-width: 600px;
+ min-height: 300px;
+ max-width: 1800px;
+ max-height: 800px;
+ }
+ </style>
<h2>Job <span>[[job.id]]</span></h2>
- <svg id="svg" width="60%" height="60%"></svg>
+ <svg id="svg"></svg>
</template>
<script src="/res/imp/bower_components/d3/d3.min.js"></script>
<script src="/res/js/gantt.js"></script>
@@ -57,9 +71,10 @@
"_draw(job.*, tasks.*, epochs.*)",
],
- ready: function() {
+ attached: function() {
this._chart = gantt(this.$.svg);
window.addEventListener("resize", this.draw.bind(this));
+ this.draw();
},
draw: function() {
@@ -69,7 +84,7 @@
},
_draw: function() {
- if (!this._chart || !this.job || !this.tasks || this.epochs === undefined) {
+ if (!this.job || !this.tasks || this.epochs === undefined || !this._chart) {
return;
}
var tasks = [{
diff --git a/task_scheduler/res/js/gantt.js b/task_scheduler/res/js/gantt.js
index bf96c5a..2f19405 100644
--- a/task_scheduler/res/js/gantt.js
+++ b/task_scheduler/res/js/gantt.js
@@ -57,8 +57,9 @@
}
// Calculate label offset.
- const totalWidth = this._svg.getBoundingClientRect().width;
- const totalHeight = this._svg.getBoundingClientRect().height;
+ const boundingRect = this._svg.getBoundingClientRect();
+ const totalWidth = boundingRect.width;
+ const totalHeight = boundingRect.height;
const chartMarginLeft = 5;
const chartMarginRight = 110;
const chartMarginY = 5;
@@ -279,9 +280,7 @@
// event.
this._layoutGetMouseX = function(e) {
// Convert event x-coordinate to a coordinate within the chart area.
- // TODO(borenet): Without subtracting 10px here, x is on the right side of
- // the cursor. Why?
- let x = e.clientX - 10;
+ let x = e.clientX - boundingRect.x;
if (x < blockStartX) {
x = blockStartX;
} else if (x > totalWidth - chartMarginRight) {
@@ -398,7 +397,7 @@
};
// Set the mouse line location.
- if (this._layoutMouseLine) {
+ if (this._layoutMouseLine && this._layoutMouseLine.length > 0) {
this._layoutMouseLine[0].y2 = blockStartY + blocksHeight;
} else {
this._layoutMouseLine = [];
@@ -406,12 +405,12 @@
this._layoutMouseTime = this._layoutMouseTime || [];
// Set the layout selection box location.
- if (this._layoutSelectBox) {
+ if (this._layoutSelectBox && this._layoutSelectBox.length > 0) {
this._layoutSelectBox[0].height = blocksHeight;
} else {
this._layoutSelectBox = [];
}
- if (this._layoutSelectedTimeRange) {
+ if (this._layoutSelectedTimeRange && this._layoutSelectedTimeRange.length > 0) {
this._layoutSelectedTimeRange[0].y1 = blockStartY - 10;
this._layoutSelectedTimeRange[0].y2 = blockStartY;
} else {
@@ -600,7 +599,9 @@
* Handler for mousemove events.
*/
rv._mouseMove = function(e) {
- this._layoutUpdateMouse(e);
+ if (this._layoutUpdateMouse) {
+ this._layoutUpdateMouse(e);
+ }
};
rv._svg.addEventListener('mousemove', rv._mouseMove.bind(rv));
@@ -608,7 +609,9 @@
* Handler for mousedown events.
*/
rv._mouseDown = function(e) {
- this._layoutStartSelection(e);
+ if (this._layoutStartSelection) {
+ this._layoutStartSelection(e);
+ }
};
rv._svg.addEventListener('mousedown', rv._mouseDown.bind(rv));
diff --git a/task_scheduler/templates/job_timeline.html b/task_scheduler/templates/job_timeline.html
new file mode 100644
index 0000000..462c421
--- /dev/null
+++ b/task_scheduler/templates/job_timeline.html
@@ -0,0 +1,13 @@
+{{template "header.html"}}
+
+<job-timeline-sk id="timeline" class="fit"></job-timeline-sk>
+<error-toast-sk></error-toast-sk>
+<script>
+// Add information from the server to the job-timeline-sk.
+var ele = document.getElementById("timeline");
+var data = JSON.parse("{{.Data}}");
+ele.job = data.job;
+ele.tasks = data.tasks;
+ele.epochs = data.epochs;
+</script>
+{{template "footer.html"}}