Split Job into Vue component

This commit is contained in:
Adam Goldsmith 2019-12-30 12:29:34 -05:00
parent 7731b37c12
commit 1e954a0afc
2 changed files with 133 additions and 116 deletions

View File

@ -1,40 +1,12 @@
<template>
<div>
<div v-for="(job, jid) in jobs" :key="jid">
<h3>
<span v-if="job.startEvent">
{{ job.startEvent.data.fun }}
{{ job.startEvent.data.arg }}
</span>
<span class="header-jid"> {{ jid }}</span>
</h3>
<div v-for="(events, minion) in job.minionEvents" :key="minion">
<details class="minion">
<summary>
{{ minion }}
<span v-for="(event, index) in events" :key="index">
{{ event_symbol(event) }}
</span>
</summary>
<details class="event" v-for="(event, index) in events" :key="index">
<summary>{{ event_name(event) }}</summary>
<pre v-if="rawEvents">{{ JSON.stringify(event, null, 2) }}</pre>
<div v-if="isJobEventType(event, 'prog')">
{{ JSON.stringify(event.data.data, null, 2) }}
</div>
<div v-else-if="isJobEventType(event, 'ret')">
{{ event.data.jid }}: {{ event.data.fun }}
{{ event.data.fun_args }} => {{ event.data.retcode }}
<pre>{{
typeof event.data.return === 'string'
? event.data.return
: JSON.stringify(event.data.return, null, 2)
}}</pre>
</div>
</details>
</details>
</div>
</div>
<Job
v-for="(jobEvents, jid) in jobs"
:key="jid"
:jid="jid"
:showRawEvents="showRawEvents"
:events="jobEvents"
></Job>
</div>
</template>
@ -44,34 +16,16 @@ import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
import credentials from './credentials.json';
import * as salt from './salt';
import Job from './Job';
const BASE_URL = 'https://salt.sawtooth.claremontmakerspace.org:8000/';
type MinionJobEvent = salt.JobProgEvent | salt.JobRetEvent;
class Job {
startEvent?: salt.JobNewEvent;
minionEvents: { [key: string]: MinionJobEvent[] } = {};
jid: salt.JobID;
constructor(jid: salt.JobID, startEvent?: salt.JobNewEvent) {
this.jid = jid;
this.startEvent = startEvent;
}
addEvent(event: MinionJobEvent) {
if (!(event.data.id in this.minionEvents))
Vue.set(this.minionEvents, event.data.id, []);
this.minionEvents[event.data.id].push(event);
}
}
@Component
@Component({ components: { Job } })
export default class App extends Vue {
evtSource: EventSource | null = null;
events: salt.SaltEvent[] = [];
jobs: { [key: string]: Job } = {};
rawEvents: boolean = false;
jobs: { [key: string]: salt.JobEvent[] } = {};
showRawEvents: boolean = false;
mounted(): void {
fetch(BASE_URL + 'login', {
@ -89,17 +43,9 @@ export default class App extends Vue {
this.events.push(event);
if (this.isEventType(event, 'job')) {
if (this.isJobEventType(event, 'new')) {
this.$set(
this.jobs,
event.data.jid,
new Job(event.data.jid, event)
);
} else {
if (!(event.data.jid in this.jobs))
this.$set(this.jobs, event.data.jid, new Job(event.data.jid));
this.jobs[event.data.jid].addEvent(event);
}
if (!(event.data.jid in this.jobs))
this.$set(this.jobs, event.data.jid, []);
this.jobs[event.data.jid].push(event);
}
};
});
@ -111,53 +57,5 @@ export default class App extends Vue {
): event is Extract<salt.SaltEvent, { splitTag: ['salt', T, ...any[]] }> {
return event.splitTag[1] === type;
}
isJobEventType<T extends salt.SaltEvent['splitTag'][3]>(
event: salt.SaltEvent,
type: T
): event is Extract<
salt.SaltEvent,
{ splitTag: ['salt', 'job', any, T, ...any[]] }
> {
return event.splitTag[3] === type;
}
event_name(event: salt.JobEvent) {
if (this.isJobEventType(event, 'prog')) {
return `${this.event_symbol(event)} Progress: ${
event.data.data.ret.name
}|${event.data.data.ret.duration} `;
} else if (this.isJobEventType(event, 'ret')) {
return `${this.event_symbol(event)} Return: ${event.data.fun} `;
}
}
event_symbol(event: salt.JobEvent) {
if (this.isJobEventType(event, 'prog')) {
const ret = event.data.data.ret;
if (!ret.result) return '✗';
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
else return '✓';
} else if (this.isJobEventType(event, 'ret')) {
return event.data.success ? '✓' : '✗';
}
return '?';
}
}
</script>
<style>
.minion .event {
margin-left: 2ex;
}
.header-jid {
font-size: x-small;
font-weight: normal;
}
pre {
white-space: pre-wrap;
}
</style>

119
src/Job.vue Normal file
View File

@ -0,0 +1,119 @@
<template>
<div class="job">
<h3>
<span v-if="startEvent">
{{ startEvent.data.fun }}
{{ startEvent.data.arg }}
</span>
<span class="header-jid"> {{ jid }}</span>
</h3>
<div v-for="(events, minion) in minionEvents" :key="minion">
<details class="minion">
<summary>
{{ minion }}
<span v-for="(event, index) in events" :key="index">
{{ event_symbol(event) }}
</span>
</summary>
<details class="event" v-for="(event, index) in events" :key="index">
<summary>{{ event_name(event) }}</summary>
<pre v-if="showRawEvents">{{ JSON.stringify(event, null, 2) }}</pre>
<div v-if="isJobEventType(event, 'prog')">
{{ JSON.stringify(event.data.data, null, 2) }}
</div>
<div v-else-if="isJobEventType(event, 'ret')">
{{ event.data.jid }}: {{ event.data.fun }}
{{ event.data.fun_args }} => {{ event.data.retcode }}
<pre>{{
typeof event.data.return === 'string'
? event.data.return
: JSON.stringify(event.data.return, null, 2)
}}</pre>
</div>
</details>
</details>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
import * as salt from './salt';
type MinionJobEvent = salt.JobProgEvent | salt.JobRetEvent;
@Component
export default class Job extends Vue {
@Prop(Number) readonly jid: salt.JobID;
@Prop(Boolean) readonly showRawEvents: boolean;
@Prop() readonly events: salt.JobEvent[];
isJobEventType<T extends salt.JobEvent['splitTag'][3]>(
event: salt.JobEvent,
type: T
): event is Extract<
salt.JobEvent,
{ splitTag: ['salt', 'job', any, T, ...any[]] }
> {
return event.splitTag[3] === type;
}
get startEvent(): salt.JobNewEvent {
return this.events.find((event): event is salt.JobNewEvent =>
this.isJobEventType(event, 'new')
);
}
get minionEvents(): { [key: string]: MinionJobEvent[] } {
return this.events
.filter(
event =>
this.isJobEventType(event, 'prog') ||
this.isJobEventType(event, 'ret')
)
.reduce((acc, event: MinionJobEvent) => {
if (!(event.data.id in acc)) acc[event.data.id] = [];
acc[event.data.id].push(event);
return acc;
}, {});
}
event_name(event: salt.JobEvent) {
if (this.isJobEventType(event, 'prog')) {
return `${this.event_symbol(event)} Progress: ${
event.data.data.ret.name
}|${event.data.data.ret.duration} `;
} else if (this.isJobEventType(event, 'ret')) {
return `${this.event_symbol(event)} Return: ${event.data.fun} `;
}
}
event_symbol(event: salt.JobEvent) {
if (this.isJobEventType(event, 'prog')) {
const ret = event.data.data.ret;
if (!ret.result) return '✗';
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
else return '✓';
} else if (this.isJobEventType(event, 'ret')) {
return event.data.success ? '✓' : '✗';
}
return '?';
}
}
</script>
<style>
.minion .event {
margin-left: 2ex;
}
.header-jid {
font-size: x-small;
font-weight: normal;
}
pre {
white-space: pre-wrap;
}
</style>