diff --git a/client/src/api/invocations.ts b/client/src/api/invocations.ts index 935fa7463bff..5895a6a11b5a 100644 --- a/client/src/api/invocations.ts +++ b/client/src/api/invocations.ts @@ -2,6 +2,7 @@ import { type components } from "./schema"; export type WorkflowInvocationElementView = components["schemas"]["WorkflowInvocationElementView"]; export type WorkflowInvocationCollectionView = components["schemas"]["WorkflowInvocationCollectionView"]; +export type WorkflowInvocationStepStatesView = components["schemas"]["WorkflowInvocationStepStatesView"]; export type InvocationJobsSummary = components["schemas"]["InvocationJobsResponse"]; export type InvocationStep = components["schemas"]["InvocationStep"]; export type InvocationMessage = components["schemas"]["InvocationMessageResponseUnion"]; diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index bf7059adc008..41ed25fa4a61 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -12161,7 +12161,7 @@ export interface components { * InvocationSerializationView * @enum {string} */ - InvocationSerializationView: "element" | "collection"; + InvocationSerializationView: "element" | "collection" | "step_states"; /** * InvocationSortByEnum * @enum {string} @@ -12179,10 +12179,7 @@ export interface components { | "cancelled" | "cancelling" | "failed"; - /** - * InvocationStep - * @description Information about workflow invocation step - */ + /** InvocationStep */ InvocationStep: { /** * Action @@ -12199,8 +12196,6 @@ export interface components { * @description The implicit collection job ID associated with the workflow invocation step. */ implicit_collection_jobs_id?: string | null; - /** Job Id */ - job_id: string | null; /** * Jobs * @description Jobs associated with the workflow invocation step. @@ -13125,6 +13120,11 @@ export interface components { * @example 0123456789ABCDEF */ id: string; + /** + * Implicit Collection Jobs ID + * @description The implicit collection job ID associated with the workflow invocation step. + */ + implicit_collection_jobs_id?: string | null; /** Job Id */ job_id: string | null; /** @@ -13132,11 +13132,12 @@ export interface components { * @description Jobs associated with the workflow invocation step. * @default [] */ - jobs?: components["schemas"]["JobBaseModel"][]; + jobs: components["schemas"]["JobBaseModel"][]; /** * Model class * @description The name of the database model class. * @constant + * @enum {string} */ model_class: "WorkflowInvocationStep"; /** @@ -13149,16 +13150,16 @@ export interface components { * @description The dataset collection outputs of the workflow invocation step. * @default {} */ - output_collections?: { - [key: string]: components["schemas"]["InvocationStepCollectionOutput"] | undefined; + output_collections: { + [key: string]: components["schemas"]["InvocationStepCollectionOutput"]; }; /** * Outputs * @description The outputs of the workflow invocation step. * @default {} */ - outputs?: { - [key: string]: components["schemas"]["InvocationStepOutput"] | undefined; + outputs: { + [key: string]: components["schemas"]["InvocationStepOutput"]; }; /** * State of the invocation step @@ -13241,36 +13242,25 @@ export interface components { * @description Input step parameters of the workflow invocation. */ input_step_parameters: { - [key: string]: components["schemas"]["InvocationInputParameter"] | undefined; + [key: string]: components["schemas"]["InvocationInputParameter"]; }; /** * Inputs * @description Input datasets/dataset collections of the workflow invocation. */ inputs: { - [key: string]: components["schemas"]["InvocationInput"] | undefined; + [key: string]: components["schemas"]["InvocationInput"]; }; /** * Messages * @description A list of messages about why the invocation did not succeed. */ - messages: ( - | components["schemas"]["InvocationCancellationReviewFailedResponse"] - | components["schemas"]["InvocationCancellationHistoryDeletedResponse"] - | components["schemas"]["InvocationCancellationUserRequestResponse"] - | components["schemas"]["InvocationFailureDatasetFailedResponse"] - | components["schemas"]["InvocationFailureCollectionFailedResponse"] - | components["schemas"]["InvocationFailureJobFailedResponse"] - | components["schemas"]["InvocationFailureOutputNotFoundResponse"] - | components["schemas"]["InvocationFailureExpressionEvaluationFailedResponse"] - | components["schemas"]["InvocationFailureWhenNotBooleanResponse"] - | components["schemas"]["InvocationUnexpectedFailureResponse"] - | components["schemas"]["InvocationEvaluationWarningWorkflowOutputNotFoundResponse"] - )[]; + messages: components["schemas"]["InvocationMessageResponseUnion"][]; /** * Model class * @description The name of the database model class. * @constant + * @enum {string} */ model_class: "WorkflowInvocation"; /** @@ -13278,7 +13268,7 @@ export interface components { * @description Output dataset collections of the workflow invocation. */ output_collections: { - [key: string]: components["schemas"]["InvocationOutputCollection"] | undefined; + [key: string]: components["schemas"]["InvocationOutputCollection"]; }; /** * Output values @@ -13290,7 +13280,7 @@ export interface components { * @description Output datasets of the workflow invocation. */ outputs: { - [key: string]: components["schemas"]["InvocationOutput"] | undefined; + [key: string]: components["schemas"]["InvocationOutput"]; }; /** * Invocation state @@ -13312,7 +13302,7 @@ export interface components { * UUID * @description Universal unique identifier of the workflow invocation. */ - uuid?: string | string | null; + uuid?: string | null; /** * Workflow ID * @description The encoded Workflow ID associated with the invocation. @@ -18498,7 +18488,8 @@ export interface components { WorkflowInvocationResponse: | components["schemas"]["WorkflowInvocationElementView"] | components["schemas"]["LegacyWorkflowInvocationElementView"] - | components["schemas"]["WorkflowInvocationCollectionView"]; + | components["schemas"]["WorkflowInvocationCollectionView"] + | components["schemas"]["WorkflowInvocationStepStatesView"]; /** WorkflowInvocationStateSummary */ WorkflowInvocationStateSummary: { /** @@ -18525,6 +18516,61 @@ export interface components { [key: string]: number; }; }; + /** WorkflowInvocationStepStatesView */ + WorkflowInvocationStepStatesView: { + /** + * Create Time + * Format: date-time + * @description The time and date this item was created. + */ + create_time: string; + /** + * History ID + * @description The encoded ID of the history associated with the invocation. + * @example 0123456789ABCDEF + */ + history_id: string; + /** + * ID + * @description The encoded ID of the workflow invocation. + * @example 0123456789ABCDEF + */ + id: string; + /** + * Model class + * @description The name of the database model class. + * @constant + * @enum {string} + */ + model_class: "WorkflowInvocation"; + /** + * Invocation state + * @description State of workflow invocation. + */ + state: components["schemas"]["InvocationState"]; + /** + * Steps + * @description Steps of the workflow invocation. + */ + steps: components["schemas"]["InvocationStep"][]; + /** + * Update Time + * Format: date-time + * @description The last time and date this item was updated. + */ + update_time: string; + /** + * UUID + * @description Universal unique identifier of the workflow invocation. + */ + uuid?: string | null; + /** + * Workflow ID + * @description The encoded Workflow ID associated with the invocation. + * @example 0123456789ABCDEF + */ + workflow_id: string; + }; /** * WorkflowJobMetric * @example { @@ -27127,6 +27173,8 @@ export interface operations { show_invocation_api_invocations__invocation_id__get: { parameters: { query?: { + /** @description View to be passed to the serializer */ + view?: string | null; /** @description Include details for individual invocation steps and populate a steps attribute in the resulting dictionary. */ step_details?: boolean; /** @description Populate the invocation step state with the job state instead of the invocation step state. diff --git a/client/src/components/Workflow/InvocationsListState.vue b/client/src/components/Workflow/InvocationsListState.vue index 676b31476071..e03326c43e56 100644 --- a/client/src/components/Workflow/InvocationsListState.vue +++ b/client/src/components/Workflow/InvocationsListState.vue @@ -23,7 +23,7 @@ const { jobStatesSummary, monitorState, clearStateMonitor, -} = useInvocationState(toRef(props, "invocationId")); +} = useInvocationState(toRef(props, "invocationId"), true); onMounted(monitorState); onBeforeUnmount(clearStateMonitor); diff --git a/client/src/components/WorkflowInvocationState/usesInvocationState.ts b/client/src/components/WorkflowInvocationState/usesInvocationState.ts index bd16c7b29978..67cb9a25ac41 100644 --- a/client/src/components/WorkflowInvocationState/usesInvocationState.ts +++ b/client/src/components/WorkflowInvocationState/usesInvocationState.ts @@ -7,11 +7,15 @@ import { isTerminal, jobCount } from "./util"; type OptionalInterval = ReturnType | null; -export function useInvocationState(invocationId: Ref) { +export function useInvocationState(invocationId: Ref, fetchMinimal: boolean = false) { const invocationStore = useInvocationStore(); const invocation = computed(() => { - return invocationStore.getInvocationById(invocationId.value); + if (fetchMinimal) { + return invocationStore.getInvocationWithStepStatesById(invocationId.value); + } else { + return invocationStore.getInvocationById(invocationId.value); + } }); let stepStatesInterval: OptionalInterval = null; @@ -47,7 +51,11 @@ export function useInvocationState(invocationId: Ref) { async function pollStepStatesUntilTerminal() { if (!invocation.value || !invocationSchedulingTerminal.value) { - await invocationStore.fetchInvocationForId({ id: invocationId.value }); + if (fetchMinimal) { + await invocationStore.fetchInvocationWithStepStatesForId({ id: invocationId.value }); + } else { + await invocationStore.fetchInvocationForId({ id: invocationId.value }); + } stepStatesInterval = setTimeout(pollStepStatesUntilTerminal, 3000); } } diff --git a/client/src/stores/invocationStore.ts b/client/src/stores/invocationStore.ts index ba4dedbb0016..8ce614728606 100644 --- a/client/src/stores/invocationStore.ts +++ b/client/src/stores/invocationStore.ts @@ -1,7 +1,12 @@ import { defineStore } from "pinia"; import { GalaxyApi } from "@/api"; -import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocation } from "@/api/invocations"; +import { + type InvocationJobsSummary, + type InvocationStep, + type WorkflowInvocation, + type WorkflowInvocationStepStatesView, +} from "@/api/invocations"; import { type FetchParams, useKeyedCache } from "@/composables/keyedCache"; import { rethrowSimple } from "@/utils/simple-error"; @@ -36,9 +41,22 @@ export const useInvocationStore = defineStore("invocationStore", () => { return data; } + async function fetchInvocationStepStateDetails(params: FetchParams): Promise { + const { data, error } = await GalaxyApi().GET("/api/invocations/{invocation_id}", { + params: { path: { invocation_id: params.id }, view: "step_states" }, + }); + if (error) { + rethrowSimple(error); + } + return data; + } + const { getItemById: getInvocationById, fetchItemById: fetchInvocationForId } = useKeyedCache(fetchInvocationDetails); + const { getItemById: getInvocationWithStepStatesById, fetchItemById: fetchInvocationWithStepStatesForId } = + useKeyedCache(fetchInvocationStepStateDetails); + const { getItemById: getInvocationJobsSummaryById, fetchItemById: fetchInvocationJobsSummaryForId } = useKeyedCache(fetchInvocationJobsSummary); @@ -52,5 +70,7 @@ export const useInvocationStore = defineStore("invocationStore", () => { fetchInvocationJobsSummaryForId, getInvocationStepById, fetchInvocationStepById, + getInvocationWithStepStatesById, + fetchInvocationWithStepStatesForId, }; }); diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index b3fa7318f95a..d3cabe2af8ec 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -9020,11 +9020,12 @@ def _serialize(self, id_encoder, serialization_options): return invocation_attrs def to_dict(self, view="collection", value_mapper=None, step_details=False, legacy_job_state=False): - rval = super().to_dict(view=view, value_mapper=value_mapper) + base_view = view if view != "step_states" else "collection" + rval = super().to_dict(view=base_view, value_mapper=value_mapper) if rval["state"] is None: # bugs could result in no state being set rval["state"] = self.states.FAILED - if view == "element": + if view in ["element", "step_states"]: steps = [] for step in self.steps: if step_details: @@ -9046,7 +9047,7 @@ def to_dict(self, view="collection", value_mapper=None, step_details=False, lega v["implicit_collection_jobs_id"] = step.implicit_collection_jobs_id steps.append(v) rval["steps"] = steps - + if view == "element": inputs = {} for input_item_association in self.input_datasets + self.input_dataset_collections: if input_item_association.history_content_type == "dataset": diff --git a/lib/galaxy/schema/invocation.py b/lib/galaxy/schema/invocation.py index 08346cd65506..87a3a13a83b9 100644 --- a/lib/galaxy/schema/invocation.py +++ b/lib/galaxy/schema/invocation.py @@ -578,6 +578,10 @@ class WorkflowInvocationCollectionView(Model, WithModelClass): model_class: INVOCATION_MODEL_CLASS = ModelClassField(INVOCATION_MODEL_CLASS) +class WorkflowInvocationStepStatesView(WorkflowInvocationCollectionView): + steps: List[InvocationStep] = Field(default=..., title="Steps", description="Steps of the workflow invocation.") + + class BaseWorkflowInvocationElementView(WorkflowInvocationCollectionView): inputs: Dict[str, InvocationInput] = Field( default=..., title="Inputs", description="Input datasets/dataset collections of the workflow invocation." @@ -615,7 +619,12 @@ class WorkflowInvocationElementView(BaseWorkflowInvocationElementView): class WorkflowInvocationResponse(RootModel): root: Annotated[ - Union[WorkflowInvocationElementView, LegacyWorkflowInvocationElementView, WorkflowInvocationCollectionView], + Union[ + WorkflowInvocationElementView, + LegacyWorkflowInvocationElementView, + WorkflowInvocationCollectionView, + WorkflowInvocationStepStatesView, + ], Field(union_mode="left_to_right"), ] @@ -626,6 +635,8 @@ def from_dict(as_dict: Dict[str, Any], view: "InvocationSerializationView", lega # performant, and will likely yield clearer error messages. if view == InvocationSerializationView.collection: root = WorkflowInvocationCollectionView(**as_dict) + elif view == InvocationSerializationView.step_states: + root = WorkflowInvocationStepStatesView(**as_dict) elif legacy_job_state: root = LegacyWorkflowInvocationElementView(**as_dict) else: @@ -722,6 +733,7 @@ class CreateInvocationFromStore(StoreContentSource): class InvocationSerializationView(str, Enum): element = "element" collection = "collection" + step_states = "step_states" # collection + steps - for monitoring, lighter than element class InvocationSerializationParams(BaseModel): diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 9348e25a977b..d5a3a0268a39 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -1445,11 +1445,12 @@ def show_invocation( self, invocation_id: InvocationIDPathParam, trans: ProvidesUserContext = DependsOnTrans, + view: SerializationViewQueryParam = None, step_details: StepDetailQueryParam = False, legacy_job_state: LegacyJobStateQueryParam = False, ) -> WorkflowInvocationResponse: serialization_params = InvocationSerializationParams( - step_details=step_details, legacy_job_state=legacy_job_state + view=view, step_details=step_details, legacy_job_state=legacy_job_state ) return self.invocations_service.show(trans, invocation_id, serialization_params, eager=True)