run/task: show live timer

This commit is contained in:
Simone Gotti 2019-05-27 10:38:05 +02:00
parent bcc00a3543
commit 6996917ff2
6 changed files with 222 additions and 170 deletions

View File

@ -135,7 +135,7 @@ import { userLocalRunLink, projectRunLink } from "@/util/link.js";
import { runStatus, runResultClass } from "@/util/run.js"; import { runStatus, runResultClass } from "@/util/run.js";
export default { export default {
name: "RunDetail", name: "rundetail",
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive
}, },
@ -155,7 +155,7 @@ export default {
}, },
methods: { methods: {
runStatus: runStatus, runStatus: runStatus,
runResultClass: runResultClass, runResultClass: runResultClass,
resetErrors() { resetErrors() {
this.stopRunError = null; this.stopRunError = null;
this.cancelRunError = null; this.cancelRunError = null;

View File

@ -7,41 +7,19 @@
> >
<div>{{ fetchRunError }}</div> <div>{{ fetchRunError }}</div>
</div> </div>
<RunDetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref"/> <rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref"/>
<div v-if="run"> <div v-if="run">
<div v-if="run.phase != 'setuperror'"> <div v-if="run.phase != 'setuperror'">
<div class="m-4 text-xl font-bold">Tasks</div> <div class="m-4 text-xl font-bold">Tasks</div>
<ul v-if="run"> <ul v-if="run">
<li <li v-for="task in run.sortedTasks" v-bind:key="task.id">
class="mb-2 border-l-5 rounded-l" <task
v-for="task in run.sortedTasks" v-bind:task="task"
v-bind:key="task.id" v-bind:link="runTaskLink(task)"
:class="taskClass(task)" v-bind:waiting-approval="run.tasks_waiting_approval.includes(task.id)"
> v-bind:parents="parents(task)"
<div class="pl-4 py-4 flex justify-between items-center border border-l-0 rounded-r"> />
<router-link class="w-1/3 font-bold" tag="a" :to="runTaskLink(task)">
<span class="w-1/3 font-bold">{{task.name}}</span>
</router-link>
<div class="column">
<span
class="tag"
v-if="run.tasks_waiting_approval.includes(task.id)"
>Waiting approval</span>
</div>
<div class="w-40">
<span class="block" v-if="parents(task).length > 0">depends on: &nbsp;</span>
<span
class="font-thin text-gray-600"
v-for="dep in parents(task)"
v-bind:key="dep"
>{{dep}}</span>
</div>
<!-- <span
class="duration"
v-if="duration && (step.Phase == 'success' || step.Phase == 'failed') "
>{{duration}}</span>-->
</div>
</li> </li>
</ul> </ul>
</div> </div>
@ -63,11 +41,12 @@
import { fetchRun } from "@/util/data.js"; import { fetchRun } from "@/util/data.js";
import { userLocalRunTaskLink, projectRunTaskLink } from "@/util/link.js"; import { userLocalRunTaskLink, projectRunTaskLink } from "@/util/link.js";
import RunDetail from "@/components/rundetail.vue"; import rundetail from "@/components/rundetail.vue";
import task from "@/components/task.vue";
export default { export default {
name: "run", name: "runsummary",
components: { RunDetail }, components: { rundetail, task },
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="mb-2 border-l-5 rounded-l" class="mb-2 border-l-5 rounded-l"
:class="stepClass(step)" :class="stepClass"
role="tab" role="tab"
:aria-expanded="active ? 'true' : 'false'" :aria-expanded="active ? 'true' : 'false'"
> >
@ -14,7 +14,7 @@
></i> ></i>
<span class="w-1/3 font-bold">{{step.name}}</span> <span class="w-1/3 font-bold">{{step.name}}</span>
</div> </div>
<span class>{{duration}}</span> <span class>{{ duration }}</span>
</div> </div>
<div class="p-1 log-container" v-show="active"> <div class="p-1 log-container" v-show="active">
<Log <Log
@ -38,14 +38,14 @@ import Log from "@/components/log.vue";
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
name: "Collapse", name: "step",
components: { components: {
Log Log
}, },
data() { data() {
return { return {
active: false, active: false,
duration: null now: moment()
}; };
}, },
props: { props: {
@ -55,42 +55,43 @@ export default {
stepnum: Number, stepnum: Number,
step: Object step: Object
}, },
created() { computed: {
this.updateDuration(this.step); duration() {
}, let formatString = "h:mm:ss[s]";
ready() { let start = moment(this.step.start_time);
if (this.active) { let end = moment(this.step.end_time);
this.$emit("collapse-open", this.index);
} if (this.step.start_time === null) {
}, return moment.duration(0).format(formatString);
watch: { }
step: function(step) { if (this.step.end_time === null) {
this.updateDuration(step); return moment.duration(this.now.diff(start)).format(formatString);
}
return moment.duration(end.diff(start)).format(formatString);
},
stepClass() {
if (this.step.phase == "success") return "success";
if (this.step.phase == "failed") return "failed";
if (this.step.phase == "stopped") return "failed";
if (this.step.phase == "running") return "running";
return "unknown";
} }
}, },
methods: { methods: {
stepClass(step) {
if (step.phase == "success") return "success";
if (step.phase == "failed") return "failed";
if (step.phase == "stopped") return "failed";
if (step.phase == "running") return "running";
return "unknown";
},
updateDuration(step) {
let start = moment(step.start_time);
let end = moment(step.end_time);
if (start === null || end === null) {
this.duration = null;
return;
}
this.duration = moment.duration(end.diff(start)).format("h:mm:ss[s]");
},
toggle() { toggle() {
this.active = !this.active; this.active = !this.active;
if (this.active) { if (this.active) {
this.$emit("collapse-open", this.index); this.$emit("step-open", this.index);
} }
} }
},
created() {
if (this.active) {
this.$emit("step-open", this.index);
}
window.setInterval(() => {
this.now = moment();
}, 500);
} }
}; };
</script> </script>

View File

@ -1,120 +1,72 @@
<template> <template>
<div> <div class="mb-2 border-l-5 rounded-l" :class="taskClass(task)">
<div <div class="px-4 py-4 flex justify-between items-center border border-l-0 rounded-r">
v-if="fetchRunError || fetchTaskError" <router-link class="w-1/3 font-bold" tag="a" :to="link">
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" <span class="w-1/3 font-bold">{{task.name}}</span>
role="alert" </router-link>
> <div class="w-1/4">
<div>Error fetching Run: {{ fetchRunError }}</div> <span class="tag" v-if="waitingApproval">Waiting approval</span>
<div>Error fetching Task: {{ fetchTaskError }}</div>
</div>
<RunDetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref"/>
<div v-if="task != null">
<div class="mt-8 mb-4 flex justify-between items-center">
<div class="flex items-center">
<span class="text-2xl mr-3">{{task.name}}</span>
<span
class="mr-3 rounded px-2 py-1 text-xs"
:class="taskClass(task)"
>{{ task.status | capitalize }}</span>
</div>
<button
v-if="task.waiting_approval"
class="btn btn-blue"
@click="approveTask(run.id, task.id)"
>Approve</button>
</div> </div>
<Collapse <div class="w-1/4">
v-bind:runid="runid" <span class="block" v-if="parents.length > 0">depends on: &nbsp;</span>
v-bind:taskid="taskid" <span class="font-thin text-gray-600" v-for="dep in parents" v-bind:key="dep">{{dep}}</span>
v-bind:setup="true"
v-bind:step="task.setup_step"
/>
<div v-for="(step, index) in task.steps" v-bind:key="index">
<Collapse
v-bind:runid="runid"
v-bind:taskid="taskid"
v-bind:stepnum="index"
v-bind:step="step"
/>
</div> </div>
<span class="w-16 text-right">{{ duration }}</span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { fetchRun, fetchTask, approveTask } from "@/util/data.js"; import * as moment from "moment";
import momentDurationFormatSetup from "moment-duration-format";
import Collapse from "@/components/collapse.vue"; momentDurationFormatSetup(moment);
import RunDetail from "@/components/rundetail.vue";
export default { export default {
components: {
Collapse,
RunDetail
},
name: "task", name: "task",
props: { components: {},
ownertype: String,
ownername: String,
projectref: Array,
runid: String,
taskid: String
},
data() { data() {
return { return {
fetchRunError: null, now: moment()
fetchTaskError: null,
run: null,
task: null,
polling: null
}; };
}, },
props: {
task: Object,
link: Object,
waitingApproval: Boolean,
parents: Array
},
computed: {
duration() {
let formatString = "h:mm:ss[s]";
let start = moment(this.task.start_time);
let end = moment(this.task.end_time);
if (this.task.start_time === null) {
return moment.duration(0).format(formatString);
}
if (this.task.end_time === null) {
return moment.duration(this.now.diff(start)).format(formatString);
}
return moment.duration(end.diff(start)).format(formatString);
}
},
methods: { methods: {
taskClass(task) { taskClass(task) {
if (task.status == "success") return "is-success"; if (task.status == "success") return "success";
if (task.status == "failed") return "is-failed"; if (task.status == "failed") return "failed";
if (task.status == "stopped") return "is-failed"; if (task.status == "stopped") return "failed";
if (task.status == "running") return "is-running"; if (task.status == "running") return "running";
return "unknown"; return "unknown";
}, }
async fetchRun() {
let { data, error } = await fetchRun(this.runid);
if (error) {
this.fetchRunError = error;
return;
}
this.fetchRunError = error;
this.run = data;
},
async fetchTask() {
let { data, error } = await fetchTask(this.runid, this.taskid);
if (error) {
this.fetchTaskError = error;
return;
}
this.fetchTaskError = error;
this.task = data;
},
pollData() {
this.polling = setInterval(() => {
this.fetchTask();
this.fetchRun();
}, 2000);
},
approveTask: approveTask
}, },
created: function() { created() {
this.fetchRun(); window.setInterval(() => {
this.fetchTask(); this.now = moment();
this.pollData(); }, 500);
},
beforeDestroy() {
clearInterval(this.polling);
} }
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
</style> </style>

View File

@ -0,0 +1,120 @@
<template>
<div>
<div
v-if="fetchRunError || fetchTaskError"
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<div>Error fetching Run: {{ fetchRunError }}</div>
<div>Error fetching Task: {{ fetchTaskError }}</div>
</div>
<rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref"/>
<div v-if="task != null">
<div class="mt-8 mb-4 flex justify-between items-center">
<div class="flex items-center">
<span class="text-2xl mr-3">{{task.name}}</span>
<span
class="mr-3 rounded px-2 py-1 text-xs"
:class="taskClass(task)"
>{{ task.status | capitalize }}</span>
</div>
<button
v-if="task.waiting_approval"
class="btn btn-blue"
@click="approveTask(run.id, task.id)"
>Approve</button>
</div>
<step
v-bind:runid="runid"
v-bind:taskid="taskid"
v-bind:setup="true"
v-bind:step="task.setup_step"
/>
<div v-for="(step, index) in task.steps" v-bind:key="index">
<step
v-bind:runid="runid"
v-bind:taskid="taskid"
v-bind:stepnum="index"
v-bind:step="step"
/>
</div>
</div>
</div>
</template>
<script>
import { fetchRun, fetchTask, approveTask } from "@/util/data.js";
import step from "@/components/step.vue";
import rundetail from "@/components/rundetail.vue";
export default {
components: {
step,
rundetail
},
name: "tasksummary",
props: {
ownertype: String,
ownername: String,
projectref: Array,
runid: String,
taskid: String
},
data() {
return {
fetchRunError: null,
fetchTaskError: null,
run: null,
task: null,
polling: null
};
},
methods: {
taskClass(task) {
if (task.status == "success") return "is-success";
if (task.status == "failed") return "is-failed";
if (task.status == "stopped") return "is-failed";
if (task.status == "running") return "is-running";
return "unknown";
},
async fetchRun() {
let { data, error } = await fetchRun(this.runid);
if (error) {
this.fetchRunError = error;
return;
}
this.fetchRunError = error;
this.run = data;
},
async fetchTask() {
let { data, error } = await fetchTask(this.runid, this.taskid);
if (error) {
this.fetchTaskError = error;
return;
}
this.fetchTaskError = error;
this.task = data;
},
pollData() {
this.polling = setInterval(() => {
this.fetchTask();
this.fetchRun();
}, 2000);
},
approveTask: approveTask
},
created: function() {
this.fetchRun();
this.fetchTask();
this.pollData();
},
beforeDestroy() {
clearInterval(this.polling);
}
};
</script>
<style scoped lang="scss">
</style>

View File

@ -15,8 +15,8 @@ import createprojectgroup from "./components/createprojectgroup.vue";
import createorganization from "./components/createorganization.vue"; import createorganization from "./components/createorganization.vue";
import orgmembers from "./components/orgmembers.vue"; import orgmembers from "./components/orgmembers.vue";
import runs from "./components/runs.vue"; import runs from "./components/runs.vue";
import run from "./components/run.vue"; import runsummary from "./components/runsummary.vue";
import task from "./components/task.vue"; import tasksummary from "./components/tasksummary.vue";
import Oauth2 from "./views/Oauth2.vue"; import Oauth2 from "./views/Oauth2.vue";
import Register from "./views/Register.vue"; import Register from "./views/Register.vue";
import Login from "./views/Login.vue"; import Login from "./views/Login.vue";
@ -87,13 +87,13 @@ const router = new VueRouter({
{ {
path: "runs/:runid", path: "runs/:runid",
name: "user local run", name: "user local run",
component: run, component: runsummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid }) props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid })
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: "runs/:runid/tasks/:taskid",
name: "user local run task", name: "user local run task",
component: task, component: tasksummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid, taskid: route.params.taskid })
}, },
{ {
@ -160,13 +160,13 @@ const router = new VueRouter({
{ {
path: "runs/:runid", path: "runs/:runid",
name: "user project run", name: "user project run",
component: run, component: runsummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid }) props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid })
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: "runs/:runid/tasks/:taskid",
name: "user project run task", name: "user project run task",
component: task, component: tasksummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid })
}, },
{ {
@ -298,13 +298,13 @@ const router = new VueRouter({
{ {
path: "runs/:runid", path: "runs/:runid",
name: "org project run", name: "org project run",
component: run, component: runsummary,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid }) props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid })
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: "runs/:runid/tasks/:taskid",
name: "org project run task", name: "org project run task",
component: task, component: tasksummary,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid })
}, },
{ {