From 455e0b862f2336f2b1522bb521ea8cb4a89229d0 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Tue, 20 Aug 2024 16:02:28 -0500 Subject: [PATCH] [24.1] Add error handling and initial loading state to Invocation State Fixes https://github.com/galaxyproject/galaxy/issues/18723 Without this fix, if you get to the `WorkflowInvocationState` route for another user's invocation, there is a 404 error which is unhandled since there is a computed `invocation` ref that we constantly poll. Adding a `initialLoading` ref that ensures the first fetch for the invocation was valid (or the error is caught) fixes this. --- .../WorkflowInvocationState.vue | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue index c71970e3691c..002ba22ed833 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationState.vue @@ -11,6 +11,7 @@ import { useAnimationFrameResizeObserver } from "@/composables/sensors/animation import { useInvocationStore } from "@/stores/invocationStore"; import { useWorkflowStore } from "@/stores/workflowStore"; import localize from "@/utils/localization"; +import { errorMessageAsString } from "@/utils/simple-error"; import { cancelWorkflowScheduling } from "./services"; import { isTerminal, jobCount, runningCount } from "./util"; @@ -49,6 +50,8 @@ const invocationStore = useInvocationStore(); const stepStatesInterval = ref(undefined); const jobStatesInterval = ref(undefined); +const initialLoading = ref(true); +const errorMessage = ref(null); // after the report tab is first activated, no longer lazy-render it from then on const reportActive = ref(false); @@ -69,8 +72,10 @@ useAnimationFrameResizeObserver(scrollableDiv, ({ clientSize, scrollSize }) => { isScrollable.value = scrollSize.height >= clientSize.height + 1; }); -const invocation = computed( - () => invocationStore.getInvocationById(props.invocationId) as WorkflowInvocationElementView +const invocation = computed(() => + !initialLoading.value + ? (invocationStore.getInvocationById(props.invocationId) as WorkflowInvocationElementView) + : null ); const invocationState = computed(() => invocation.value?.state || "new"); const invocationAndJobTerminal = computed(() => invocationSchedulingTerminal.value && jobStatesTerminal.value); @@ -105,9 +110,17 @@ const workflowStore = useWorkflowStore(); const isDeletedWorkflow = computed(() => getWorkflow()?.deleted === true); const workflowVersion = computed(() => getWorkflow()?.version); -onMounted(() => { - pollStepStatesUntilTerminal(); - pollJobStatesUntilTerminal(); +onMounted(async () => { + try { + await invocationStore.fetchInvocationForId({ id: props.invocationId }); + initialLoading.value = false; + if (invocation.value) { + await pollStepStatesUntilTerminal(); + await pollJobStatesUntilTerminal(); + } + } catch (e) { + errorMessage.value = errorMessageAsString(e); + } }); onUnmounted(() => { @@ -116,7 +129,7 @@ onUnmounted(() => { }); async function pollStepStatesUntilTerminal() { - if (!invocation.value || !invocationSchedulingTerminal.value) { + if (!invocationSchedulingTerminal.value) { await invocationStore.fetchInvocationForId({ id: props.invocationId }); stepStatesInterval.value = setTimeout(pollStepStatesUntilTerminal, 3000); } @@ -152,7 +165,7 @@ function getWorkflowId() { } function getWorkflowName() { - return workflowStore.getStoredWorkflowNameByInstanceId(invocation.value?.workflow_id); + return workflowStore.getStoredWorkflowNameByInstanceId(invocation.value?.workflow_id || ""); } @@ -261,6 +274,9 @@ function getWorkflowName() { + + {{ errorMessage }} +