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";
export default {
name: "RunDetail",
name: "rundetail",
directives: {
clickOutside: vClickOutside.directive
},

View File

@ -7,41 +7,19 @@
>
<div>{{ fetchRunError }}</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.phase != 'setuperror'">
<div class="m-4 text-xl font-bold">Tasks</div>
<ul v-if="run">
<li
class="mb-2 border-l-5 rounded-l"
v-for="task in run.sortedTasks"
v-bind:key="task.id"
:class="taskClass(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 v-for="task in run.sortedTasks" v-bind:key="task.id">
<task
v-bind:task="task"
v-bind:link="runTaskLink(task)"
v-bind:waiting-approval="run.tasks_waiting_approval.includes(task.id)"
v-bind:parents="parents(task)"
/>
</li>
</ul>
</div>
@ -63,11 +41,12 @@
import { fetchRun } from "@/util/data.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 {
name: "run",
components: { RunDetail },
name: "runsummary",
components: { rundetail, task },
props: {
ownertype: String,
ownername: String,

View File

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

View File

@ -1,117 +1,69 @@
<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 class="mb-2 border-l-5 rounded-l" :class="taskClass(task)">
<div class="px-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="link">
<span class="w-1/3 font-bold">{{task.name}}</span>
</router-link>
<div class="w-1/4">
<span class="tag" v-if="waitingApproval">Waiting approval</span>
</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>
<Collapse
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">
<Collapse
v-bind:runid="runid"
v-bind:taskid="taskid"
v-bind:stepnum="index"
v-bind:step="step"
/>
<div class="w-1/4">
<span class="block" v-if="parents.length > 0">depends on: &nbsp;</span>
<span class="font-thin text-gray-600" v-for="dep in parents" v-bind:key="dep">{{dep}}</span>
</div>
<span class="w-16 text-right">{{ duration }}</span>
</div>
</div>
</template>
<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";
import RunDetail from "@/components/rundetail.vue";
momentDurationFormatSetup(moment);
export default {
components: {
Collapse,
RunDetail
},
name: "task",
props: {
ownertype: String,
ownername: String,
projectref: Array,
runid: String,
taskid: String
},
components: {},
data() {
return {
fetchRunError: null,
fetchTaskError: null,
run: null,
task: null,
polling: null
now: moment()
};
},
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: {
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";
if (task.status == "success") return "success";
if (task.status == "failed") return "failed";
if (task.status == "stopped") return "failed";
if (task.status == "running") return "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);
created() {
window.setInterval(() => {
this.now = moment();
}, 500);
}
};
</script>

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 orgmembers from "./components/orgmembers.vue";
import runs from "./components/runs.vue";
import run from "./components/run.vue";
import task from "./components/task.vue";
import runsummary from "./components/runsummary.vue";
import tasksummary from "./components/tasksummary.vue";
import Oauth2 from "./views/Oauth2.vue";
import Register from "./views/Register.vue";
import Login from "./views/Login.vue";
@ -87,13 +87,13 @@ const router = new VueRouter({
{
path: "runs/:runid",
name: "user local run",
component: run,
component: runsummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid })
},
{
path: "runs/:runid/tasks/:taskid",
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 })
},
{
@ -160,13 +160,13 @@ const router = new VueRouter({
{
path: "runs/:runid",
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 })
},
{
path: "runs/:runid/tasks/:taskid",
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 })
},
{
@ -298,13 +298,13 @@ const router = new VueRouter({
{
path: "runs/:runid",
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 })
},
{
path: "runs/:runid/tasks/:taskid",
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 })
},
{