diff --git a/client/src/api/invocations.ts b/client/src/api/invocations.ts index a6cb5957030f..935fa7463bff 100644 --- a/client/src/api/invocations.ts +++ b/client/src/api/invocations.ts @@ -1,6 +1,3 @@ -import { rethrowSimple } from "@/utils/simple-error"; - -import { GalaxyApi } from "./client"; import { type components } from "./schema"; export type WorkflowInvocationElementView = components["schemas"]["WorkflowInvocationElementView"]; @@ -15,15 +12,3 @@ export type StepJobSummary = | components["schemas"]["InvocationStepJobsResponseCollectionJobsModel"]; export type WorkflowInvocation = components["schemas"]["WorkflowInvocationResponse"]; - -export async function cancelWorkflowScheduling(invocationId: string) { - const { data, error } = await GalaxyApi().DELETE("/api/invocations/{invocation_id}", { - params: { - path: { invocation_id: invocationId }, - }, - }); - if (error) { - rethrowSimple(error); - } - return data; -} diff --git a/client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue b/client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue index 4d6f6e68717d..448cacdf3989 100644 --- a/client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue +++ b/client/src/components/Workflow/Invocation/Graph/InvocationGraph.vue @@ -34,8 +34,6 @@ interface Props { workflow: Workflow; /** Whether the invocation is terminal */ isTerminal: boolean; - /** Whether the invocation is scheduled */ - isScheduled: boolean; /** The zoom level for the graph */ zoom?: number; /** Whether to show the minimap */ diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue index d076e639daf0..fb79d5bb3d9e 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue @@ -15,7 +15,6 @@ import InvocationMessage from "@/components/WorkflowInvocationState/InvocationMe interface Props { invocation: WorkflowInvocationElementView; invocationAndJobTerminal: boolean; - invocationSchedulingTerminal: boolean; isFullPage?: boolean; isSubworkflow?: boolean; } @@ -62,7 +61,6 @@ const uniqueMessages = computed(() => { :invocation="invocation" :workflow="workflow" :is-terminal="invocationAndJobTerminal" - :is-scheduled="invocationSchedulingTerminal" :is-full-page="isFullPage" :show-minimap="isFullPage" /> diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue index 8eaf178ee656..7ab995fe87b8 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue @@ -5,7 +5,6 @@ import { BAlert, BBadge, BButton, BTab, BTabs } from "bootstrap-vue"; import { computed, onUnmounted, ref, watch } from "vue"; import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocationElementView } from "@/api/invocations"; -import { cancelWorkflowScheduling } from "@/api/invocations"; import { useAnimationFrameResizeObserver } from "@/composables/sensors/animationFrameResizeObserver"; import { useInvocationStore } from "@/stores/invocationStore"; import { useWorkflowStore } from "@/stores/workflowStore"; @@ -200,6 +199,16 @@ watch( const storeId = computed(() => (invocation.value ? `invocation-${invocation.value.id}` : undefined)); +watch( + () => invocationSchedulingTerminal.value, + async (newVal, oldVal) => { + if (oldVal && !newVal) { + // If the invocation was terminal and now is not, start polling again + await pollStepStatesUntilTerminal(); + } + } +); + onUnmounted(() => { clearTimeout(stepStatesInterval.value); clearTimeout(jobStatesInterval.value); @@ -223,18 +232,12 @@ function onError(e: any) { async function onCancel() { try { cancellingInvocation.value = true; - await cancelWorkflowScheduling(props.invocationId); + await invocationStore.cancelWorkflowScheduling(props.invocationId); } catch (e) { onError(e); } finally { emit("invocation-cancelled"); - - // Update the invocation state to reflect the cancellation - setTimeout(async () => { - await invocationStore.fetchInvocationForId({ id: props.invocationId }); - await invocationStore.fetchInvocationJobsSummaryForId({ id: props.invocationId }); - cancellingInvocation.value = false; - }, 3000); + cancellingInvocation.value = false; } } @@ -255,7 +258,7 @@ async function onCancel() { size="sm" class="text-decoration-none" variant="link" - :disabled="cancellingInvocation" + :disabled="cancellingInvocation || invocationState == 'cancelling'" @click="onCancel"> Cancel @@ -314,7 +317,6 @@ async function onCancel() { :invocation="invocation" :is-full-page="props.isFullPage" :invocation-and-job-terminal="invocationAndJobTerminal" - :invocation-scheduling-terminal="invocationSchedulingTerminal" :is-subworkflow="isSubworkflow" /> diff --git a/client/src/composables/useInvocationGraph.ts b/client/src/composables/useInvocationGraph.ts index 89e3570ce459..211db6cd7979 100644 --- a/client/src/composables/useInvocationGraph.ts +++ b/client/src/composables/useInvocationGraph.ts @@ -69,7 +69,7 @@ export const statePlaceholders: Record = { }; /** Only one job needs to be in one of these states for the graph step to be in that state */ -const SINGLE_INSTANCE_STATES = ["error", "running", "paused"]; +const SINGLE_INSTANCE_STATES = ["error", "running", "paused", "deleting"]; /** All jobs need to be in one of these states for the graph step to be in that state */ const ALL_INSTANCES_STATES = ["deleted", "skipped", "new", "queued"]; @@ -293,6 +293,9 @@ export function useInvocationGraph( function getStepStateFromJobStates(jobStates: string[]): GraphStep["state"] | undefined { for (const state of SINGLE_INSTANCE_STATES) { if (jobStates.includes(state)) { + if (state === "deleting") { + return "deleted"; + } return state as GraphStep["state"]; } } diff --git a/client/src/stores/invocationStore.ts b/client/src/stores/invocationStore.ts index 0ce8a6337fba..d212f8c6e644 100644 --- a/client/src/stores/invocationStore.ts +++ b/client/src/stores/invocationStore.ts @@ -42,8 +42,24 @@ export const useInvocationStore = defineStore("invocationStore", () => { return data; } - const { getItemById: getInvocationById, fetchItemById: fetchInvocationForId } = - useKeyedCache(fetchInvocationDetails); + async function cancelWorkflowScheduling(invocationId: string) { + const { data, error } = await GalaxyApi().DELETE("/api/invocations/{invocation_id}", { + params: { + path: { invocation_id: invocationId }, + }, + }); + if (error) { + rethrowSimple(error); + } + storedInvocations.value[invocationId] = data; + return data; + } + + const { + getItemById: getInvocationById, + fetchItemById: fetchInvocationForId, + storedItems: storedInvocations, + } = useKeyedCache(fetchInvocationDetails); const { getItemById: getInvocationJobsSummaryById, fetchItemById: fetchInvocationJobsSummaryForId } = useKeyedCache(fetchInvocationJobsSummary); @@ -58,6 +74,7 @@ export const useInvocationStore = defineStore("invocationStore", () => { fetchInvocationJobsSummaryForId, getInvocationStepById, fetchInvocationStepById, + cancelWorkflowScheduling, graphStepsByStoreId, }; });