1
0
mirror of https://github.com/ohwgiles/laminar.git synced 2026-03-02 03:40:21 +00:00

assign run numbers at queue time

This allows build chains to be traced in the common case where
an upstream job calls `laminarc queue' instead of `laminarc start'.
Incomplete job runs now have database entries, which requires
some adjustments in queries. Queued jobs can now be viewed in
the frontend and there is a corresponding status icon.
This commit is contained in:
Oliver Giles
2020-09-25 15:29:30 +12:00
parent 6d2c0b208b
commit 06a5f3d8ef
10 changed files with 119 additions and 112 deletions

View File

@@ -118,7 +118,8 @@
<th class="text-center vp-sm-hide">Reason <a class="sort" :class="(sort.field=='reason'?sort.order:'')" v-on:click="do_sort('reason')">&nbsp;</a></th>
</tr></thead>
<tr v-show="nQueued">
<td colspan="5"><i>{{nQueued}} run(s) queued</i></td>
<td style="width:1px"><span v-html="runIcon('queued')"></span></td>
<td colspan="4"><i>{{nQueued}} run(s) queued</i></td>
</tr>
<tr v-for="job in jobsRunning.concat(jobsRecent)" track-by="$index">
<td style="width:1px"><span v-html="runIcon(job.result)"></span></td>
@@ -152,10 +153,10 @@
<dl>
<dt>Reason</dt><dd>{{job.reason}}</dd>
<dt v-show="job.upstream.num > 0">Upstream</dt><dd v-show="job.upstream.num > 0"><router-link :to="'/jobs/'+job.upstream.name">{{job.upstream.name}}</router-link> <router-link :to="'/jobs/'+job.upstream.name+'/'+job.upstream.num">#{{job.upstream.num}}</router-link></li></dd>
<dt>Queued for</dt><dd>{{job.queued}}s</dd>
<dt>Started</dt><dd>{{formatDate(job.started)}}</dd>
<dt>Queued for</dt><dd>{{formatDuration(job.queued, job.started ? job.started : Math.floor(Date.now()/1000))}}</dd>
<dt v-show="job.started">Started</dt><dd v-show="job.started">{{formatDate(job.started)}}</dd>
<dt v-show="runComplete(job)">Completed</dt><dd v-show="job.completed">{{formatDate(job.completed)}}</dd>
<dt>Duration</dt><dd>{{formatDuration(job.started, job.completed)}}</dd>
<dt v-show="job.started">Duration</dt><dd v-show="job.started">{{formatDuration(job.started, job.completed)}}</dd>
</dl>
<dl v-show="job.artifacts.length">
<dt>Artifacts</dt>

View File

@@ -23,10 +23,10 @@ const timeScale = function(max){
: { scale:function(v){return v;}, label:'Seconds' };
}
const ServerEventHandler = function() {
function setupEventSource(path, query, next, comp) {
const es = new EventSource(document.head.baseURI + path.substr(1) + query);
function setupEventSource(to, query, next, comp) {
const es = new EventSource(document.head.baseURI + to.path.substr(1) + query);
es.comp = comp;
es.path = path; // save for later in case we need to add query params
es.path = to.path; // save for later in case we need to add query params
es.onmessage = function(msg) {
msg = JSON.parse(msg.data);
// "status" is the first message the server always delivers.
@@ -55,7 +55,7 @@ const ServerEventHandler = function() {
comp.$root.clockSkew = msg.time - Math.floor((new Date()).getTime()/1000);
comp.$root.connected = true;
// Component-specific callback handler
comp[msg.type](msg.data);
comp[msg.type](msg.data, to.params);
});
} else {
// at this point, the component must be defined
@@ -72,7 +72,7 @@ const ServerEventHandler = function() {
es.onerror = function(e) {
this.comp.$root.connected = false;
setTimeout(() => {
this.comp.es = setupEventSource(path, query, null, this.comp);
this.comp.es = setupEventSource(to, query, null, this.comp);
}, this.comp.esReconnectInterval);
if(this.comp.esReconnectInterval < 7500)
this.comp.esReconnectInterval *= 1.5;
@@ -82,11 +82,11 @@ const ServerEventHandler = function() {
}
return {
beforeRouteEnter(to, from, next) {
setupEventSource(to.path, '', (fn) => { next(fn); });
setupEventSource(to, '', (fn) => { next(fn); });
},
beforeRouteUpdate(to, from, next) {
this.es.close();
setupEventSource(to.path, '', (fn) => { fn(this); next(); });
setupEventSource(to, '', (fn) => { fn(this); next(); });
},
beforeRouteLeave(to, from, next) {
this.es.close();
@@ -115,6 +115,11 @@ const Utils = {
21,-26 5,5 11,15 15,20 8,-2 15,-9 20,-15 -3,-3 -17,-18 -20,-24 3,-5 23,-26 30,-33 -3,-5 -8,-9
-12,-12 -6,5 -26,26 -29,30 -6,-8 -11,-15 -15,-23 -3,0 -12,5 -15,7 z" />
</svg>`
: (result == 'queued') ? /* clock */
`<svg class="status queued" viewBox="0 0 100 100">
<circle r="50" cy="50" cx="50" />
<path d="m 50,15 0,35 17,17" stroke-width="10" fill="none" />
</svg>`
: /* spinner */
`<svg class="status running" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke-width="15" fill="none" stroke-dasharray="175">
@@ -656,49 +661,46 @@ const Run = function() {
return state;
},
methods: {
status: function(data) {
// Check for the /latest endpoint. An intuitive check might be
// if(this.$route.params.number == 'latest'), but unfortunately
// after calling $router.replace, we re-enter status() before
// $route.params is updated. Instead, assume that if there is
// no 'started' field, we should redirect to the latest number
if(!('started' in data) && 'latestNum' in data)
return this.$router.replace('/jobs/' + this.$route.params.name + '/' + data.latestNum);
status: function(data, params) {
// Check for the /latest endpoint
if(params.number === 'latest')
return this.$router.replace('/jobs/' + params.name + '/' + data.latestNum);
state.number = parseInt(params.number);
state.jobsRunning = [];
state.job = data;
state.latestNum = data.latestNum;
state.jobsRunning = [data];
state.log = '';
if(this.logstream)
this.logstream.abort();
if(data.started)
this.logstream = logFetcher(this, params.name, params.number);
},
job_queued: function(data) {
state.latestNum = data.number;
this.$forceUpdate();
},
job_started: function(data) {
state.latestNum++;
this.$forceUpdate();
if(data.number === state.number) {
state.job = Object.assign(state.job, data);
state.job.result = 'running';
if(this.logstream)
this.logstream.abort();
this.logstream = logFetcher(this, data.name, data.number);
this.$forceUpdate();
}
},
job_completed: function(data) {
state.job = Object.assign(state.job, data);
state.jobsRunning = [];
this.$forceUpdate();
if(data.number === state.number) {
state.job = Object.assign(state.job, data);
state.jobsRunning = [];
this.$forceUpdate();
}
},
runComplete: function(run) {
return !!run && (run.result === 'aborted' || run.result === 'failed' || run.result === 'success');
},
},
beforeRouteEnter(to, from, next) {
next(vm => {
state.log = '';
vm.logstream = logFetcher(vm, to.params.name, to.params.number);
});
},
beforeRouteUpdate(to, from, next) {
var vm = this;
vm.logstream.abort();
state.log = '';
vm.logstream = logFetcher(vm, to.params.name, to.params.number);
next();
},
beforeRouteLeave(to, from, next) {
this.logstream.abort();
next();
}
};
}();

View File

@@ -84,7 +84,9 @@ canvas {
}
svg.success path { fill: var(--success); }
svg.failed path { fill: var(--failure); }
svg.running circle { stroke: var(--running); }
svg.running circle { stroke: var(--running); }
svg.queued circle { fill: var(--nav-fg); }
svg.queued path { stroke: white; }
/* sort indicators */
a.sort {