Merge pull request #20369 from ahmedhamidawan/sync_invocation_error_below_graph

[25.0] Make invocation errors more compact
This commit is contained in:
Marius van den Beek
2025-06-03 16:29:06 +02:00
committed by GitHub
3 changed files with 121 additions and 38 deletions
@@ -1,12 +1,16 @@
<script setup lang="ts">
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import type { InvocationMessage } from "@/api/invocations";
import type { InvocationMessage, WorkflowInvocationElementView } from "@/api/invocations";
import { useWorkflowInstance } from "@/composables/useWorkflowInstance";
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
import GCard from "../Common/GCard.vue";
import WorkflowStepTitle from "./WorkflowStepTitle.vue";
import GenericHistoryItem from "@/components/History/Content/GenericItem.vue";
import JobInformation from "@/components/JobInformation/JobInformation.vue";
import WorkflowInvocationStep from "@/components/WorkflowInvocationState/WorkflowInvocationStep.vue";
type ReasonToLevel = {
history_deleted: "cancel";
@@ -44,17 +48,18 @@ const levelClasses = {
error: "errormessage",
};
interface Invocation {
workflow_id: string;
state: string;
}
interface InvocationMessageProps {
invocationMessage: InvocationMessage;
invocation: Invocation;
invocation: WorkflowInvocationElementView;
storeId: string;
}
const props = defineProps<InvocationMessageProps>();
const emit = defineEmits<{
(e: "view-step", stepId: number): void;
}>();
const levelClass = computed(() => levelClasses[level[props.invocationMessage.reason]]);
const workflow = computed(() => {
@@ -76,6 +81,12 @@ const workflowStep = computed(() => {
}
return undefined;
});
const invocationStep = computed(() => {
if (workflowStep.value) {
return props.invocation.steps[workflowStep.value.id];
}
return undefined;
});
const dependentWorkflowStep = computed(() => {
if ("dependent_workflow_step_id" in props.invocationMessage && workflow.value) {
@@ -86,6 +97,15 @@ const dependentWorkflowStep = computed(() => {
}
return undefined;
});
const dependentInvocationStep = computed(() => {
if (dependentWorkflowStep.value) {
return props.invocation.steps[dependentWorkflowStep.value.id];
}
return undefined;
});
// This is used to indicate on the step cards whether the step is currently active in the invocation graph.\
const { activeNodeId } = storeToRefs(useWorkflowStateStore(props.storeId));
const jobId = computed(() => "job_id" in props.invocationMessage && props.invocationMessage.job_id);
const HdaId = computed(() => "hda_id" in props.invocationMessage && props.invocationMessage.hda_id);
@@ -175,6 +195,11 @@ const infoString = computed(() => {
return reason;
}
});
function openJobInNewTab(jobId: string) {
const url = `/jobs/${jobId}/view`;
window.open(url, "_blank");
}
</script>
<template>
@@ -182,31 +207,68 @@ const infoString = computed(() => {
<div :class="levelClass" style="text-align: center">
{{ infoString }}
</div>
<div v-if="dependentWorkflowStep">
Problem occurred at this step:
<WorkflowInvocationStep
:invocation="invocation"
:workflow="workflow"
:workflow-step="dependentWorkflowStep"></WorkflowInvocationStep>
</div>
<div v-if="workflowStep">
{{ stepDescription }}
<WorkflowInvocationStep
:invocation="invocation"
:workflow="workflow"
:workflow-step="workflowStep"></WorkflowInvocationStep>
</div>
<div v-if="HdaId">
This dataset failed:
<GenericHistoryItem :item-id="HdaId" item-src="hda" />
</div>
<div v-if="HdcaId">
This dataset collection failed:
<GenericHistoryItem :item-id="HdcaId" item-src="hdca" />
</div>
<div v-if="jobId">
This job failed:
<JobInformation :job_id="jobId" />
<div class="invocation-error-grid d-flex flex-wrap">
<GCard
v-if="dependentWorkflowStep"
clickable
:current="activeNodeId === dependentWorkflowStep.id"
grid-view
@click="emit('view-step', dependentWorkflowStep.id)">
Problem occurred at this step:
<strong>
<WorkflowStepTitle
:step-index="dependentWorkflowStep.id"
:step-label="
dependentInvocationStep?.workflow_step_label || `Step ${dependentWorkflowStep.id + 1}`
"
:step-type="dependentWorkflowStep.type"
:step-tool-id="dependentWorkflowStep.tool_id"
:step-subworkflow-id="
'workflow_id' in dependentWorkflowStep ? dependentWorkflowStep.workflow_id : null
" />
</strong>
</GCard>
<GCard
v-if="workflowStep"
clickable
:current="activeNodeId === workflowStep.id"
grid-view
@click="emit('view-step', workflowStep.id)">
{{ stepDescription }}:
<strong>
<WorkflowStepTitle
:step-index="workflowStep.id"
:step-label="invocationStep?.workflow_step_label || `Step ${workflowStep.id + 1}`"
:step-type="workflowStep.type"
:step-tool-id="workflowStep.tool_id"
:step-subworkflow-id="'workflow_id' in workflowStep ? workflowStep.workflow_id : null" />
</strong>
</GCard>
<GCard v-if="HdaId" grid-view>
This dataset failed:
<GenericHistoryItem :item-id="HdaId" item-src="hda" />
</GCard>
<GCard v-if="HdcaId" grid-view>
This dataset collection failed:
<GenericHistoryItem :item-id="HdcaId" item-src="hdca" />
</GCard>
<GCard v-if="jobId" clickable grid-view @click="openJobInNewTab(jobId)">
<span>
This job failed: <strong> {{ jobId }} </strong>
</span>
<i>
Click to view job details in a new tab
<FontAwesomeIcon :icon="faExternalLinkAlt" />
</i>
</GCard>
</div>
</div>
</template>
<style scoped lang="scss">
@import "_breakpoints.scss";
.invocation-error-grid {
container: cards-list / inline-size;
}
</style>
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { BAlert } from "bootstrap-vue";
import { computed } from "vue";
import { storeToRefs } from "pinia";
import { computed, nextTick, ref } from "vue";
import type { WorkflowInvocationElementView } from "@/api/invocations";
import { useWorkflowInstance } from "@/composables/useWorkflowInstance";
import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore";
import { withPrefix } from "@/utils/redirect";
import ExternalLink from "../ExternalLink.vue";
@@ -23,11 +25,27 @@ const props = defineProps<Props>();
const { workflow, loading, error } = useWorkflowInstance(props.invocation.workflow_id);
const invocationGraph = ref<InstanceType<typeof InvocationGraph> | null>(null);
const uniqueMessages = computed(() => {
const messages = props.invocation.messages || [];
const uniqueMessagesSet = new Set(messages.map((message) => JSON.stringify(message)));
return Array.from(uniqueMessagesSet).map((message) => JSON.parse(message)) as typeof messages;
});
// TODO: Refactor so that `storeId` is only defined here, and then used in all children components/composables.
const storeId = computed(() => `invocation-${props.invocation.id}`);
const stateStore = useWorkflowStateStore(storeId.value);
const { activeNodeId } = storeToRefs(stateStore);
async function showStep(stepId: number) {
if (invocationGraph.value) {
activeNodeId.value = stepId;
await nextTick();
const graphSelector = invocationGraph.value?.$el?.querySelector(".invocation-graph");
graphSelector?.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
</script>
<template>
@@ -44,7 +62,9 @@ const uniqueMessages = computed(() => {
:key="message.reason"
class="steps-progress my-1 w-100"
:invocation-message="message"
:invocation="invocation">
:invocation="invocation"
:store-id="storeId"
@view-step="showStep">
</InvocationMessage>
</div>
<!-- Once the workflow for the invocation has been loaded, display the graph -->
@@ -56,6 +76,7 @@ const uniqueMessages = computed(() => {
</BAlert>
<div v-else-if="workflow && !isSubworkflow">
<InvocationGraph
ref="invocationGraph"
class="mt-1"
data-description="workflow invocation graph"
:invocation="invocation"
@@ -11,8 +11,8 @@ interface WorkflowInvocationStepTitleProps {
stepIndex: number;
stepLabel?: string;
stepType: string;
stepToolId?: string;
stepSubworkflowId?: string;
stepToolId?: string | null;
stepSubworkflowId?: string | null;
}
const props = defineProps<WorkflowInvocationStepTitleProps>();