From 387398727fa64d9d479d09a85bfeb3e2af1eec08 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 11 Mar 2024 14:04:52 -0400 Subject: [PATCH 01/12] Refactor progress bars out of invocation summary. --- .../InvocationJobsProgressBar.vue | 63 +++++++++++ .../InvocationStepsProgressBar.vue | 68 ++++++++++++ .../WorkflowInvocationOverview.vue | 103 ++---------------- 3 files changed, 143 insertions(+), 91 deletions(-) create mode 100644 client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue create mode 100644 client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue diff --git a/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue new file mode 100644 index 000000000000..962262095e3d --- /dev/null +++ b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue @@ -0,0 +1,63 @@ + + + diff --git a/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue new file mode 100644 index 000000000000..e7fe24d39fe6 --- /dev/null +++ b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue @@ -0,0 +1,68 @@ + + + diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue index 20084eb4ff58..1c3734e63568 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue @@ -2,24 +2,19 @@ import { BAlert, BButton } from "bootstrap-vue"; import { computed } from "vue"; -import { type InvocationJobsSummary, type InvocationStep, type WorkflowInvocationElementView } from "@/api/invocations"; +import { type InvocationJobsSummary, type WorkflowInvocationElementView } from "@/api/invocations"; import { useWorkflowInstance } from "@/composables/useWorkflowInstance"; import { getRootFromIndexLink } from "@/onload"; import { withPrefix } from "@/utils/redirect"; -import { - errorCount as jobStatesSummaryErrorCount, - jobCount as jobStatesSummaryJobCount, - numTerminal, - okCount as jobStatesSummaryOkCount, - runningCount as jobStatesSummaryRunningCount, -} from "./util"; +import { runningCount as jobStatesSummaryRunningCount } from "./util"; import ExternalLink from "../ExternalLink.vue"; import HelpText from "../Help/HelpText.vue"; import InvocationGraph from "../Workflow/Invocation/Graph/InvocationGraph.vue"; +import InvocationJobsProgressBar from "./InvocationJobsProgressBar.vue"; +import InvocationStepsProgressBar from "./InvocationStepsProgressBar.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; -import ProgressBar from "@/components/ProgressBar.vue"; import InvocationMessage from "@/components/WorkflowInvocationState/InvocationMessage.vue"; function getUrl(path: string): string { @@ -72,30 +67,6 @@ const disabledReportTooltip = computed(() => { } }); -const stepCount = computed(() => { - return props.invocation.steps.length || 0; -}); - -type StepStateType = { [state: string]: number }; - -const stepStates = computed(() => { - const stepStates: StepStateType = {}; - const steps: InvocationStep[] = props.invocation.steps || []; - for (const step of steps) { - if (!step) { - continue; - } - // the API defined state here allowing null and undefined is odd... - const stepState: string = step.state || "unknown"; - if (!stepStates[stepState]) { - stepStates[stepState] = 1; - } else { - stepStates[stepState] += 1; - } - } - return stepStates; -}); - const invocationPdfLink = computed(() => { const id = invocationId.value; if (id) { @@ -111,38 +82,10 @@ const uniqueMessages = computed(() => { return Array.from(uniqueMessagesSet).map((message) => JSON.parse(message)) as typeof messages; }); -const stepStatesStr = computed(() => { - return `${stepStates.value?.scheduled || 0} of ${stepCount.value} steps successfully scheduled.`; -}); - -const okCount = computed(() => { - return jobStatesSummaryOkCount(props.jobStatesSummary); -}); - const runningCount = computed(() => { return jobStatesSummaryRunningCount(props.jobStatesSummary); }); -const jobCount = computed(() => { - return jobStatesSummaryJobCount(props.jobStatesSummary); -}); - -const errorCount = computed(() => { - return jobStatesSummaryErrorCount(props.jobStatesSummary); -}); - -const newCount = computed(() => { - return jobCount.value - okCount.value - runningCount.value - errorCount.value; -}); - -const jobStatesStr = computed(() => { - let jobStr = `${numTerminal(props.jobStatesSummary) || 0} of ${jobCount.value} jobs complete`; - if (!props.invocationSchedulingTerminal) { - jobStr += " (total number of jobs will change until all steps fully scheduled)"; - } - return `${jobStr}.`; -}); - const emit = defineEmits<{ (e: "invocation-cancelled"): void; }>(); @@ -188,11 +131,6 @@ function onCancel() {
- - - - - + :invocation="invocation" + :invocation-state="invocationState" + :invocation-scheduling-terminal="invocationSchedulingTerminal" /> +
From 3f0b0067e63d3f4077d6bebde09745fd7ff84aaa Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 11 Mar 2024 14:15:34 -0400 Subject: [PATCH 02/12] Tighten up typing in workflow invocation component. --- .../WorkflowInvocationState/InvocationJobsProgressBar.vue | 1 - .../WorkflowInvocationState/WorkflowInvocationOverview.vue | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue index 962262095e3d..9610656b71c6 100644 --- a/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue +++ b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue @@ -12,7 +12,6 @@ import { runningCount as jobStatesSummaryRunningCount, } from "./util"; - interface Props { jobStatesSummary: InvocationJobsSummary; invocationSchedulingTerminal: boolean; diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue index 1c3734e63568..75d1957902fd 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationOverview.vue @@ -37,7 +37,7 @@ const generatePdfTooltip = "Generate PDF report for this workflow invocation"; const { workflow, loading, error } = useWorkflowInstance(props.invocation.workflow_id); -const invocationId = computed(() => props.invocation.id); +const invocationId = computed(() => props.invocation.id); const indexStr = computed(() => { if (props.index == undefined) { From aca29c91c30314360038d0c2b08d74dac339a13b Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 12 Mar 2024 12:29:48 -0400 Subject: [PATCH 03/12] Assume invocation step state is not null. --- client/src/api/schema/schema.ts | 2 +- .../WorkflowInvocationState/InvocationJobsProgressBar.vue | 3 ++- .../WorkflowInvocationState/InvocationStepsProgressBar.vue | 4 ++-- lib/galaxy/schema/invocation.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 8f9bee6a6572..f79514cf8618 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -12239,7 +12239,7 @@ export interface components { * State of the invocation step * @description Describes where in the scheduling process the workflow invocation step is. */ - state?: components["schemas"]["InvocationStepState"] | components["schemas"]["JobState"] | null; + state: components["schemas"]["InvocationStepState"] | components["schemas"]["JobState"]; /** Subworkflow Invocation Id */ subworkflow_invocation_id: string | null; /** diff --git a/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue index 9610656b71c6..82546ea39559 100644 --- a/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue +++ b/client/src/components/WorkflowInvocationState/InvocationJobsProgressBar.vue @@ -2,7 +2,6 @@ import { computed } from "vue"; import { InvocationJobsSummary } from "@/api/invocations"; -import ProgressBar from "@/components/ProgressBar.vue"; import { errorCount as jobStatesSummaryErrorCount, @@ -12,6 +11,8 @@ import { runningCount as jobStatesSummaryRunningCount, } from "./util"; +import ProgressBar from "@/components/ProgressBar.vue"; + interface Props { jobStatesSummary: InvocationJobsSummary; invocationSchedulingTerminal: boolean; diff --git a/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue index e7fe24d39fe6..f2ad0ba277b0 100644 --- a/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue +++ b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue @@ -2,6 +2,7 @@ import { computed } from "vue"; import { InvocationStep, WorkflowInvocationElementView } from "@/api/invocations"; + import ProgressBar from "@/components/ProgressBar.vue"; interface Props { @@ -28,8 +29,7 @@ const stepStates = computed(() => { if (!step) { continue; } - // the API defined state here allowing null and undefined is odd... - const stepState: string = step.state || "unknown"; + const stepState: string = step.state; if (!stepStates[stepState]) { stepStates[stepState] = 1; } else { diff --git a/lib/galaxy/schema/invocation.py b/lib/galaxy/schema/invocation.py index 8872889ac078..1304d60165af 100644 --- a/lib/galaxy/schema/invocation.py +++ b/lib/galaxy/schema/invocation.py @@ -375,8 +375,8 @@ class InvocationStep(Model, WithModelClass): ), ] ] - state: Optional[Union[InvocationStepState, JobState]] = Field( - default=None, + state: Union[InvocationStepState, JobState] = Field( + ..., title="State of the invocation step", description="Describes where in the scheduling process the workflow invocation step is.", ) From f954c24fb253df78a96afe339fb8ccb786ceaf50 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 13 Mar 2024 14:11:57 -0400 Subject: [PATCH 04/12] more precise invocation response models --- client/src/api/schema/schema.ts | 183 +++++++++++++++++- lib/galaxy/schema/invocation.py | 72 +++++-- .../webapps/galaxy/services/invocations.py | 2 +- 3 files changed, 236 insertions(+), 21 deletions(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index f79514cf8618..bf7059adc008 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -12239,7 +12239,7 @@ export interface components { * State of the invocation step * @description Describes where in the scheduling process the workflow invocation step is. */ - state: components["schemas"]["InvocationStepState"] | components["schemas"]["JobState"]; + state: components["schemas"]["InvocationStepState"]; /** Subworkflow Invocation Id */ subworkflow_invocation_id: string | null; /** @@ -13113,6 +13113,82 @@ export interface components { * @enum {string} */ LandingRequestState: "unclaimed" | "claimed"; + /** LegacyInvocationStep */ + LegacyInvocationStep: { + /** + * Action + * @description Whether to take action on the invocation step. + */ + action: boolean | null; + /** + * Invocation Step ID + * @example 0123456789ABCDEF + */ + id: string; + /** Job Id */ + job_id: string | null; + /** + * Jobs + * @description Jobs associated with the workflow invocation step. + * @default [] + */ + jobs?: components["schemas"]["JobBaseModel"][]; + /** + * Model class + * @description The name of the database model class. + * @constant + */ + model_class: "WorkflowInvocationStep"; + /** + * Order index + * @description The index of the workflow step in the workflow. + */ + order_index: number; + /** + * Output collections + * @description The dataset collection outputs of the workflow invocation step. + * @default {} + */ + output_collections?: { + [key: string]: components["schemas"]["InvocationStepCollectionOutput"] | undefined; + }; + /** + * Outputs + * @description The outputs of the workflow invocation step. + * @default {} + */ + outputs?: { + [key: string]: components["schemas"]["InvocationStepOutput"] | undefined; + }; + /** + * State of the invocation step + * @description Describes the job state corresponding to this invocation step. + */ + state: components["schemas"]["JobState"] | null; + /** Subworkflow Invocation Id */ + subworkflow_invocation_id: string | null; + /** + * Update Time + * @description The last time and date this item was updated. + */ + update_time: string | null; + /** + * Workflow step ID + * @description The encoded ID of the workflow step associated with this workflow invocation step. + * @example 0123456789ABCDEF + */ + workflow_step_id: string; + /** + * Step label + * @description The label of the workflow step + */ + workflow_step_label?: string | null; + /** + * UUID + * @description Universal unique identifier of the workflow step. + */ + workflow_step_uuid?: string | null; + }; /** LegacyLibraryPermissionsPayload */ LegacyLibraryPermissionsPayload: { /** @@ -13140,6 +13216,110 @@ export interface components { */ LIBRARY_MODIFY_in: string[] | string | null; }; + /** LegacyWorkflowInvocationElementView */ + LegacyWorkflowInvocationElementView: { + /** + * 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; + /** + * Input step parameters + * @description Input step parameters of the workflow invocation. + */ + input_step_parameters: { + [key: string]: components["schemas"]["InvocationInputParameter"] | undefined; + }; + /** + * Inputs + * @description Input datasets/dataset collections of the workflow invocation. + */ + inputs: { + [key: string]: components["schemas"]["InvocationInput"] | undefined; + }; + /** + * 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"] + )[]; + /** + * Model class + * @description The name of the database model class. + * @constant + */ + model_class: "WorkflowInvocation"; + /** + * Output collections + * @description Output dataset collections of the workflow invocation. + */ + output_collections: { + [key: string]: components["schemas"]["InvocationOutputCollection"] | undefined; + }; + /** + * Output values + * @description Output values of the workflow invocation. + */ + output_values: Record; + /** + * Outputs + * @description Output datasets of the workflow invocation. + */ + outputs: { + [key: string]: components["schemas"]["InvocationOutput"] | undefined; + }; + /** + * Invocation state + * @description State of workflow invocation. + */ + state: components["schemas"]["InvocationState"]; + /** + * Steps + * @description Steps of the workflow invocation (legacy view using job state). + */ + steps: components["schemas"]["LegacyInvocationStep"][]; + /** + * 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 | string | null; + /** + * Workflow ID + * @description The encoded Workflow ID associated with the invocation. + * @example 0123456789ABCDEF + */ + workflow_id: string; + }; /** LibraryAvailablePermissions */ LibraryAvailablePermissions: { /** @@ -18317,6 +18497,7 @@ export interface components { /** WorkflowInvocationResponse */ WorkflowInvocationResponse: | components["schemas"]["WorkflowInvocationElementView"] + | components["schemas"]["LegacyWorkflowInvocationElementView"] | components["schemas"]["WorkflowInvocationCollectionView"]; /** WorkflowInvocationStateSummary */ WorkflowInvocationStateSummary: { diff --git a/lib/galaxy/schema/invocation.py b/lib/galaxy/schema/invocation.py index 1304d60165af..08346cd65506 100644 --- a/lib/galaxy/schema/invocation.py +++ b/lib/galaxy/schema/invocation.py @@ -341,22 +341,12 @@ class InvocationStepCollectionOutput(Model): ) -class InvocationStep(Model, WithModelClass): +class BaseInvocationStep(Model, WithModelClass): """Information about workflow invocation step""" model_class: INVOCATION_STEP_MODEL_CLASS = ModelClassField(INVOCATION_STEP_MODEL_CLASS) id: Annotated[EncodedDatabaseIdField, Field(..., title="Invocation Step ID")] update_time: Optional[datetime] = schema.UpdateTimeField - job_id: Optional[ - Annotated[ - EncodedDatabaseIdField, - Field( - default=None, - title="Job ID", - description="The encoded ID of the job associated with this workflow invocation step.", - ), - ] - ] workflow_step_id: Annotated[ EncodedDatabaseIdField, Field( @@ -375,11 +365,6 @@ class InvocationStep(Model, WithModelClass): ), ] ] - state: Union[InvocationStepState, JobState] = Field( - ..., - title="State of the invocation step", - description="Describes where in the scheduling process the workflow invocation step is.", - ) action: Optional[bool] = InvocationStepActionField order_index: int = Field( ..., @@ -418,6 +403,32 @@ class InvocationStep(Model, WithModelClass): ) +class LegacyInvocationStep(BaseInvocationStep): + job_id: Optional[ + Annotated[ + EncodedDatabaseIdField, + Field( + default=None, + title="Job ID", + description="The encoded ID of the job associated with this workflow invocation step.", + ), + ] + ] + state: Optional[JobState] = Field( + ..., + title="State of the invocation step", + description="Describes the job state corresponding to this invocation step.", + ) + + +class InvocationStep(BaseInvocationStep): + state: InvocationStepState = Field( + ..., + title="State of the invocation step", + description="Describes where in the scheduling process the workflow invocation step is.", + ) + + class InvocationReport(Model, WithModelClass): """Report describing workflow invocation""" @@ -567,8 +578,7 @@ class WorkflowInvocationCollectionView(Model, WithModelClass): model_class: INVOCATION_MODEL_CLASS = ModelClassField(INVOCATION_MODEL_CLASS) -class WorkflowInvocationElementView(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." ) @@ -593,11 +603,35 @@ class WorkflowInvocationElementView(WorkflowInvocationCollectionView): ) +class LegacyWorkflowInvocationElementView(BaseWorkflowInvocationElementView): + steps: List[LegacyInvocationStep] = Field( + default=..., title="Steps", description="Steps of the workflow invocation (legacy view using job state)." + ) + + +class WorkflowInvocationElementView(BaseWorkflowInvocationElementView): + steps: List[InvocationStep] = Field(default=..., title="Steps", description="Steps of the workflow invocation.") + + class WorkflowInvocationResponse(RootModel): root: Annotated[ - Union[WorkflowInvocationElementView, WorkflowInvocationCollectionView], Field(union_mode="left_to_right") + Union[WorkflowInvocationElementView, LegacyWorkflowInvocationElementView, WorkflowInvocationCollectionView], + Field(union_mode="left_to_right"), ] + @staticmethod + def from_dict(as_dict: Dict[str, Any], view: "InvocationSerializationView", legacy_job_state: bool): + # WorkflowInvocationResponse(**as_dict) does the same thing but I think it is better to + # be explicit here since we know what model we're trying to serialize - this is safer, more + # performant, and will likely yield clearer error messages. + if view == InvocationSerializationView.collection: + root = WorkflowInvocationCollectionView(**as_dict) + elif legacy_job_state: + root = LegacyWorkflowInvocationElementView(**as_dict) + else: + root = WorkflowInvocationElementView(**as_dict) + return WorkflowInvocationResponse(root=root) + class WorkflowInvocationRequestModel(Model): """Model a workflow invocation request (InvokeWorkflowPayload) for an existing invocation.""" diff --git a/lib/galaxy/webapps/galaxy/services/invocations.py b/lib/galaxy/webapps/galaxy/services/invocations.py index 4db9a0d5f1a0..16e940073d2d 100644 --- a/lib/galaxy/webapps/galaxy/services/invocations.py +++ b/lib/galaxy/webapps/galaxy/services/invocations.py @@ -260,7 +260,7 @@ def serialize_workflow_invocation( legacy_job_state = params.legacy_job_state as_dict = invocation.to_dict(view.value, step_details=step_details, legacy_job_state=legacy_job_state) as_dict["messages"] = invocation.messages - return WorkflowInvocationResponse(**as_dict) + return WorkflowInvocationResponse.from_dict(as_dict, view, legacy_job_state) def serialize_workflow_invocations( self, From e6d7815784e6399f3c9a13e7d4f66cb2bc03707c Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 13 Mar 2024 15:21:05 -0400 Subject: [PATCH 05/12] Slight improvement (to my eye) of invocation step progress bar --- .../WorkflowInvocationState/InvocationStepsProgressBar.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue index f2ad0ba277b0..02e8a3406722 100644 --- a/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue +++ b/client/src/components/WorkflowInvocationState/InvocationStepsProgressBar.vue @@ -46,9 +46,9 @@ const stepStatesStr = computed(() => {