Skip to content

Commit 62dff33

Browse files
add a navbar to JobStep which filters jobs by their states
1 parent 27b5ff2 commit 62dff33

4 files changed

Lines changed: 90 additions & 35 deletions

File tree

client/src/api/jobs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { components } from "@/api/schema";
33
export type JobDestinationParams = components["schemas"]["JobDestinationParams"];
44
export type ShowFullJobResponse = components["schemas"]["ShowFullJobResponse"];
55
export type JobBaseModel = components["schemas"]["JobBaseModel"];
6+
export type JobState = components["schemas"]["JobState"];
67
export type JobDetails = components["schemas"]["ShowFullJobResponse"] | components["schemas"]["EncodedJobDetails"];
78
export type JobInputSummary = components["schemas"]["JobInputSummary"];
89
export type JobDisplayParametersSummary = components["schemas"]["JobDisplayParametersSummary"];

client/src/components/Workflow/Editor/NodeInvocationText.vue

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
44
import { BFormInput } from "bootstrap-vue";
55
import { sanitize } from "dompurify";
66
7+
import type { JobState } from "@/api/jobs";
78
import { isWorkflowInput } from "@/components/Workflow/constants";
8-
import { type GraphStep, iconClasses, statePlaceholders } from "@/composables/useInvocationGraph";
9+
import type { GraphStep } from "@/composables/useInvocationGraph";
10+
11+
import InvocationStepStateDisplay from "@/components/WorkflowInvocationState/InvocationStepStateDisplay.vue";
912
1013
const props = defineProps<{
1114
invocationStep: GraphStep;
@@ -18,21 +21,17 @@ function isColor(value?: string): boolean {
1821
function textHtml(value: string): string {
1922
return sanitize(value, { ALLOWED_TAGS: ["b"] });
2023
}
24+
25+
// Helper function to convert the key to JobState type
26+
function toJobState(key: string | number): JobState {
27+
return key as JobState;
28+
}
2129
</script>
2230
<template>
2331
<div class="p-1 unselectable">
2432
<div v-if="props.invocationStep.jobs">
2533
<div v-for="(value, key) in props.invocationStep.jobs" :key="key">
26-
<span v-if="value !== undefined" class="d-flex align-items-center">
27-
<FontAwesomeIcon
28-
v-if="iconClasses[key]"
29-
:icon="iconClasses[key]?.icon"
30-
:class="iconClasses[key]?.class"
31-
:spin="iconClasses[key]?.spin"
32-
size="sm"
33-
class="mr-1" />
34-
{{ value }} job{{ value > 1 ? "s" : "" }} {{ statePlaceholders[key] || key }}.
35-
</span>
34+
<InvocationStepStateDisplay v-if="value !== undefined" :state="toJobState(key)" :job-count="value" />
3635
</div>
3736
</div>
3837
<div v-else-if="isWorkflowInput(props.invocationStep.type)" class="truncate w-100">
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
3+
4+
import type { JobState } from "@/api/jobs";
5+
import { iconClasses, statePlaceholders } from "@/composables/useInvocationGraph";
6+
7+
const props = defineProps<{
8+
state: JobState;
9+
jobCount: number;
10+
}>();
11+
</script>
12+
13+
<template>
14+
<span class="d-flex align-items-center">
15+
<FontAwesomeIcon
16+
v-if="iconClasses[props.state]"
17+
:icon="iconClasses[props.state]?.icon"
18+
:class="iconClasses[props.state]?.class"
19+
:spin="iconClasses[props.state]?.spin"
20+
size="sm"
21+
class="mr-1" />
22+
{{ props.jobCount }} job{{ props.jobCount > 1 ? "s" : "" }} {{ statePlaceholders[props.state] || props.state }}
23+
</span>
24+
</template>
Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<script setup lang="ts">
22
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
3-
import { BAlert, BTab, BTabs } from "bootstrap-vue";
4-
import { computed } from "vue";
3+
import { BAlert, BNav, BNavItem, BTab, BTabs } from "bootstrap-vue";
4+
import { computed, ref } from "vue";
55
6-
import type { JobBaseModel } from "@/api/jobs";
7-
import { getHeaderClass, iconClasses } from "@/composables/useInvocationGraph";
6+
import type { JobBaseModel, JobState } from "@/api/jobs";
7+
import { getHeaderClass, iconClasses, statePlaceholders } from "@/composables/useInvocationGraph";
88
99
import JobDetailsDisplayed from "../JobInformation/JobDetails.vue";
10+
import InvocationStepStateDisplay from "./InvocationStepStateDisplay.vue";
1011
1112
interface Props {
1213
jobs: JobBaseModel[];
@@ -19,6 +20,21 @@ const props = withDefaults(defineProps<Props>(), {
1920
const firstJob = computed(() => props.jobs[0]);
2021
const jobCount = computed(() => props.jobs.length);
2122
23+
/** Jobs grouped by their state */
24+
const jobsByState = computed(() => {
25+
const jobsMap: { [key: string]: JobBaseModel[] } = {};
26+
props.jobs.forEach((job) => {
27+
if (!jobsMap[job.state]) {
28+
jobsMap[job.state] = [];
29+
}
30+
jobsMap[job.state]?.push(job);
31+
});
32+
return jobsMap as Record<JobState, JobBaseModel[]>;
33+
});
34+
35+
/** Currently selected/filtered job state */
36+
const currentState = ref<JobState | null>(firstJob.value?.state || null);
37+
2238
function getIcon(job: JobBaseModel) {
2339
return iconClasses[job.state];
2440
}
@@ -37,24 +53,39 @@ function getTabClass(job: JobBaseModel) {
3753
<div v-else-if="jobCount === 1 && firstJob">
3854
<JobDetailsDisplayed :job-id="firstJob.id" />
3955
</div>
40-
<BTabs v-else lazy vertical pills card nav-class="p-0" active-tab-class="p-0">
41-
<BTab
42-
v-for="job in jobs"
43-
:key="job.id"
44-
data-description="workflow invocation job"
45-
:title-item-class="getTabClass(job)"
46-
title-link-class="w-100">
47-
<template v-slot:title>
48-
{{ job.state }}
49-
<FontAwesomeIcon
50-
v-if="getIcon(job)"
51-
:class="getIcon(job)?.class"
52-
:icon="getIcon(job)?.icon"
53-
:spin="getIcon(job)?.spin" />
54-
</template>
55-
<div>
56-
<JobDetailsDisplayed :job-id="job.id" />
57-
</div>
58-
</BTab>
59-
</BTabs>
56+
<div v-else>
57+
<BNav justified pills class="mb-2 p-2">
58+
<BNavItem
59+
v-for="(stateJobs, state) in jobsByState"
60+
:key="state"
61+
:title="`Click to view ${statePlaceholders[state] || state} jobs`"
62+
:active="currentState === state"
63+
link-classes="d-flex justify-content-center"
64+
@click="currentState = state">
65+
<InvocationStepStateDisplay :state="state" :job-count="stateJobs.length" />
66+
</BNavItem>
67+
</BNav>
68+
69+
<BAlert v-if="!currentState" variant="info" show> Please select a job state to view jobs. </BAlert>
70+
<BTabs v-else lazy vertical pills card nav-class="p-0" active-tab-class="p-0">
71+
<BTab
72+
v-for="job in jobsByState[currentState]"
73+
:key="job.id"
74+
data-description="workflow invocation job"
75+
:title-item-class="getTabClass(job)"
76+
title-link-class="w-100">
77+
<template v-slot:title>
78+
{{ job.state }}
79+
<FontAwesomeIcon
80+
v-if="getIcon(job)"
81+
:class="getIcon(job)?.class"
82+
:icon="getIcon(job)?.icon"
83+
:spin="getIcon(job)?.spin" />
84+
</template>
85+
<div class="m-3">
86+
<JobDetailsDisplayed :job-id="job.id" />
87+
</div>
88+
</BTab>
89+
</BTabs>
90+
</div>
6091
</template>

0 commit comments

Comments
 (0)