diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 89227e632ec2..a1b56a23d96a 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1804,6 +1804,8 @@ export interface paths { get: operations["get_workflow_menu_api_workflows_menu_get"]; }; "/api/workflows/{workflow_id}": { + /** Displays information needed to run a workflow. */ + get: operations["show_workflow_api_workflows__workflow_id__get"]; /** Add the deleted flag to a workflow. */ delete: operations["delete_workflow_api_workflows__workflow_id__delete"]; }; @@ -1828,6 +1830,8 @@ export interface paths { "/api/workflows/{workflow_id}/invocations": { /** Get the list of a user's workflow invocations. */ get: operations["index_invocations_api_workflows__workflow_id__invocations_get"]; + /** Schedule the workflow specified by `workflow_id` to run. */ + post: operations["Invoke_workflow_api_workflows__workflow_id__invocations_post"]; }; "/api/workflows/{workflow_id}/invocations/{invocation_id}": { /** @@ -1888,6 +1892,10 @@ export interface paths { */ put: operations["publish_api_workflows__workflow_id__publish_put"]; }; + "/api/workflows/{workflow_id}/refactor": { + /** Updates the workflow stored with the given ID. */ + put: operations["refactor_api_workflows__workflow_id__refactor_put"]; + }; "/api/workflows/{workflow_id}/share_with_users": { /** * Share this item with specific users. @@ -1940,6 +1948,11 @@ export interface paths { * @deprecated */ get: operations["index_invocations_api_workflows__workflow_id__usage_get"]; + /** + * Schedule the workflow specified by `workflow_id` to run. + * @deprecated + */ + post: operations["Invoke_workflow_api_workflows__workflow_id__usage_post"]; }; "/api/workflows/{workflow_id}/usage/{invocation_id}": { /** @@ -2097,6 +2110,63 @@ export interface components { */ link: string; }; + /** AddInputAction */ + AddInputAction: { + /** + * Action Type + * @constant + */ + action_type: "add_input"; + /** Collection Type */ + collection_type?: string | null; + /** Default */ + default?: Record | null; + /** Label */ + label?: string | null; + /** + * Optional + * @default false + */ + optional?: boolean | null; + position?: components["schemas"]["Position"] | null; + /** Restrict On Connections */ + restrict_on_connections?: boolean | null; + /** Restrictions */ + restrictions?: string[] | null; + /** Suggestions */ + suggestions?: string[] | null; + /** Type */ + type: string; + }; + /** + * AddStepAction + * @description Add a new action to the workflow. + * + * After the workflow is updated, an order_index will be assigned + * and this step may cause other steps to have their output_index + * adjusted. + */ + AddStepAction: { + /** + * Action Type + * @constant + */ + action_type: "add_step"; + /** + * Label + * @description A unique label for the step being added, must be distinct from the labels already present in the workflow. + */ + label?: string | null; + /** @description The location of the step in the Galaxy workflow editor. */ + position?: components["schemas"]["Position"] | null; + /** Tool State */ + tool_state?: Record | null; + /** + * Type + * @description Module type of the step to add, see galaxy.workflow.modules for available types. + */ + type: string; + }; /** AnonUserModel */ AnonUserModel: { /** @@ -2956,6 +3026,20 @@ export interface components { */ source: string | null; }; + /** ConnectAction */ + ConnectAction: { + /** + * Action Type + * @constant + */ + action_type: "connect"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + }; /** ContentsObject */ ContentsObject: { /** @@ -4266,6 +4350,20 @@ export interface components { */ username: string; }; + /** DisconnectAction */ + DisconnectAction: { + /** + * Action Type + * @constant + */ + action_type: "disconnect"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + }; /** * DisplayApp * @description Basic linked information about an application that can display certain datatypes. @@ -4722,6 +4820,32 @@ export interface components { * @enum {string} */ ExtraFilesEntryClass: "Directory" | "File"; + /** ExtractInputAction */ + ExtractInputAction: { + /** + * Action Type + * @constant + */ + action_type: "extract_input"; + /** Input */ + input: components["schemas"]["InputReferenceByOrderIndex"] | components["schemas"]["InputReferenceByLabel"]; + /** Label */ + label?: string | null; + position?: components["schemas"]["Position"] | null; + }; + /** ExtractUntypedParameter */ + ExtractUntypedParameter: { + /** + * Action Type + * @constant + */ + action_type: "extract_untyped_parameter"; + /** Label */ + label?: string | null; + /** Name */ + name: string; + position?: components["schemas"]["Position"] | null; + }; /** FavoriteObject */ FavoriteObject: { /** @@ -4815,6 +4939,14 @@ export interface components { */ to_posix_lines?: boolean; }; + /** FileDefaultsAction */ + FileDefaultsAction: { + /** + * Action Type + * @constant + */ + action_type: "fill_defaults"; + }; /** FileLibraryFolderItem */ FileLibraryFolderItem: { /** Can Manage */ @@ -4925,6 +5057,16 @@ export interface components { | components["schemas"]["BrowsableFilesSourcePlugin"] | components["schemas"]["FilesSourcePlugin"] )[]; + /** FillStepDefaultsAction */ + FillStepDefaultsAction: { + /** + * Action Type + * @constant + */ + action_type: "fill_step_defaults"; + /** Step */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; /** FolderLibraryFolderItem */ FolderLibraryFolderItem: { /** Can Manage */ @@ -6674,6 +6816,168 @@ export interface components { */ to_posix_lines?: "Yes" | boolean | null; }; + /** InputDataCollectionStep */ + InputDataCollectionStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default data_collection_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** InputDataStep */ + InputDataStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default data_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** InputParameterStep */ + InputParameterStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default parameter_input + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** InputReferenceByLabel */ + InputReferenceByLabel: { + /** + * Input Name + * @description The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name: string; + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + }; + /** InputReferenceByOrderIndex */ + InputReferenceByOrderIndex: { + /** + * Input Name + * @description The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name: string; + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + }; + /** InputStep */ + InputStep: { + /** + * Source Step + * @description The identifier of the workflow step connected to this particular input. + */ + source_step: number; + /** + * Step Output + * @description The name of the output generated by the source step. + */ + step_output: string; + }; /** InstalledRepositoryToolShedStatus */ InstalledRepositoryToolShedStatus: { /** @@ -7368,6 +7672,138 @@ export interface components { */ action: boolean; }; + /** InvokeWorkflowPayload */ + InvokeWorkflowPayload: { + /** + * Allow tool state corrections + * @description Indicates if tool state corrections are allowed for workflow invocation. + * @default false + */ + allow_tool_state_corrections?: boolean | null; + /** + * Batch + * @description Indicates if the workflow is invoked as a batch. + * @default false + */ + batch?: boolean | null; + /** + * Dataset Map + * @description TODO + * @default {} + */ + ds_map?: { + [key: string]: Record | undefined; + } | null; + /** + * Effective Outputs + * @description TODO + */ + effective_outputs?: Record | null; + /** + * History + * @description The encoded history id - passed exactly like this 'hist_id=...' - into which to import. Or the name of the new history into which to import. + */ + history?: string | null; + /** + * History ID + * @description The encoded history id into which to import. + */ + history_id?: string | null; + /** + * Inputs + * @description TODO + */ + inputs?: Record | null; + /** + * Inputs By + * @description How inputs maps to inputs (datasets/collections) to workflows steps. + */ + inputs_by?: string | null; + /** + * Is instance + * @description True when fetching by Workflow ID, False when fetching by StoredWorkflow ID + * @default false + */ + instance?: boolean | null; + /** + * Legacy + * @description Indicating if to use legacy workflow invocation. + * @default false + */ + legacy?: boolean | null; + /** + * New History Name + * @description The name of the new history into which to import. + */ + new_history_name?: string | null; + /** + * No Add to History + * @description Indicates if the workflow invocation should not be added to the history. + * @default false + */ + no_add_to_history?: boolean | null; + /** + * Parameters + * @description The raw parameters for the workflow invocation. + * @default {} + */ + parameters?: Record | null; + /** + * Parameters Normalized + * @description Indicates if parameters are already normalized for workflow invocation. + * @default false + */ + parameters_normalized?: boolean | null; + /** + * Preferred Intermediate Object Store ID + * @description The ID of the ? object store that should be used to store ? datasets in this history. + */ + preferred_intermediate_object_store_id?: string | null; + /** + * Preferred Object Store ID + * @description The ID of the object store that should be used to store new datasets in this history. + */ + preferred_object_store_id?: string | null; + /** + * Preferred Outputs Object Store ID + * @description The ID of the object store that should be used to store ? datasets in this history. + */ + preferred_outputs_object_store_id?: string | null; + /** + * Replacement Parameters + * @description TODO + * @default {} + */ + replacement_params?: Record | null; + /** + * Require Exact Tool Versions + * @description If true, exact tool versions are required for workflow invocation. + * @default true + */ + require_exact_tool_versions?: boolean | null; + /** + * Resource Parameters + * @description TODO + * @default {} + */ + resource_params?: Record | null; + /** + * Scheduler + * @description Scheduler to use for workflow invocation. + */ + scheduler?: string | null; + /** + * Step Parameters + * @description TODO + */ + step_parameters?: Record | null; + /** + * Use cached job + * @description Indicated whether to use a cached job for workflow invocation. + * @default false + */ + use_cached_job?: boolean | null; + }; /** * ItemTagsCreatePayload * @description Payload schema for creating an item tag. @@ -8920,19 +9356,33 @@ export interface components { */ up_to_date: boolean; }; - /** Organization */ - Organization: { + /** OutputReferenceByLabel */ + OutputReferenceByLabel: { /** - * Name - * @description Name of the organization responsible for the service + * Label + * @description The unique label of the step being referenced. */ - name: string; + label: string; /** - * Url - * Format: uri - * @description URL of the website of the organization (RFC 3986 format) + * Output Name + * @description The output name as defined by the workflow module corresponding to the step being referenced. The default is 'output', corresponding to the output defined by input step types. + * @default output */ - url: string; + output_name?: string | null; + }; + /** OutputReferenceByOrderIndex */ + OutputReferenceByOrderIndex: { + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + /** + * Output Name + * @description The output name as defined by the workflow module corresponding to the step being referenced. The default is 'output', corresponding to the output defined by input step types. + * @default output + */ + output_name?: string | null; }; /** * PageContentFormat @@ -9234,6 +9684,80 @@ export interface components { */ to_posix_lines?: boolean; }; + /** PauseStep */ + PauseStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Type + * @description The type of workflow module. + * @default pause + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; + /** Person */ + Person: { + /** Address */ + address?: string | null; + /** Alternate Name */ + alternateName?: string | null; + /** + * Class + * @default Person + */ + class?: string; + /** Email */ + email?: string | null; + /** Family Name */ + familyName?: string | null; + /** Fax Number */ + faxNumber?: string | null; + /** Given Name */ + givenName?: string | null; + /** + * Honorific Prefix + * @description Honorific Prefix (e.g. Dr/Mrs/Mr) + */ + honorificPrefix?: string | null; + /** + * Honorific Suffix + * @description Honorific Suffix (e.g. M.D.) + */ + honorificSuffix?: string | null; + /** + * Identifier + * @description Identifier (typically an orcid.org ID) + */ + identifier?: string | null; + /** Image URL */ + image?: string | null; + /** Job Title */ + jobTitle?: string | null; + /** + * Name + * @description The name of the creator. + */ + name: string; + /** Telephone */ + telephone?: string | null; + /** URL */ + url?: string | null; + }; /** * PersonalNotificationCategory * @description These notification categories can be opt-out by the user and will be @@ -9247,6 +9771,13 @@ export interface components { * @enum {string} */ PluginKind: "rfs" | "drs" | "rdm" | "stock"; + /** Position */ + Position: { + /** Left */ + left: number; + /** Top */ + top: number; + }; /** PrepareStoreDownloadPayload */ PrepareStoreDownloadPayload: { /** @@ -9420,6 +9951,146 @@ export interface components { * @default [] */ QuotaSummaryList: components["schemas"]["QuotaSummary"][]; + /** RefactorActionExecution */ + RefactorActionExecution: { + /** Action */ + action: + | components["schemas"]["AddInputAction"] + | components["schemas"]["AddStepAction"] + | components["schemas"]["ConnectAction"] + | components["schemas"]["DisconnectAction"] + | components["schemas"]["ExtractInputAction"] + | components["schemas"]["ExtractUntypedParameter"] + | components["schemas"]["FileDefaultsAction"] + | components["schemas"]["FillStepDefaultsAction"] + | components["schemas"]["UpdateAnnotationAction"] + | components["schemas"]["UpdateCreatorAction"] + | components["schemas"]["UpdateNameAction"] + | components["schemas"]["UpdateLicenseAction"] + | components["schemas"]["UpdateOutputLabelAction"] + | components["schemas"]["UpdateReportAction"] + | components["schemas"]["UpdateStepLabelAction"] + | components["schemas"]["UpdateStepPositionAction"] + | components["schemas"]["UpgradeSubworkflowAction"] + | components["schemas"]["UpgradeToolAction"] + | components["schemas"]["UpgradeAllStepsAction"] + | components["schemas"]["RemoveUnlabeledWorkflowOutputs"]; + /** Messages */ + messages: components["schemas"]["RefactorActionExecutionMessage"][]; + }; + /** RefactorActionExecutionMessage */ + RefactorActionExecutionMessage: { + /** + * From Order Index + * @description For dropped connections these optional attributes refer to the output + * side of the connection that was dropped. + */ + from_order_index?: number | null; + /** + * From Step Label + * @description For dropped connections these optional attributes refer to the output + * side of the connection that was dropped. + */ + from_step_label?: string | null; + /** + * Input Name + * @description If this message is about an input to a step, + * this field describes the target input name. $The input name as defined by the workflow module corresponding to the step being referenced. For Galaxy tool steps these inputs should be normalized using '|' (e.g. 'cond|repeat_0|input'). + */ + input_name?: string | null; + /** Message */ + message: string; + message_type: components["schemas"]["RefactorActionExecutionMessageTypeEnum"]; + /** + * Order Index + * @description Reference to the step the message refers to. $ + * + * Messages don't have to be bound to a step, but if they are they will + * have a step_label and order_index included in the execution message. + * These are the label and order_index before applying the refactoring, + * the result of applying the action may change one or both of these. + * If connections are dropped this step reference will refer to the + * step with the previously connected input. + */ + order_index?: number | null; + /** + * Output Label + * @description If the message_type is workflow_output_drop_forced, this is the output label dropped. + */ + output_label?: string | null; + /** + * Output Name + * @description If this message is about an output to a step, + * this field describes the target output name. The output name as defined by the workflow module corresponding to the step being referenced. + */ + output_name?: string | null; + /** + * Step Label + * @description Reference to the step the message refers to. $ + * + * Messages don't have to be bound to a step, but if they are they will + * have a step_label and order_index included in the execution message. + * These are the label and order_index before applying the refactoring, + * the result of applying the action may change one or both of these. + * If connections are dropped this step reference will refer to the + * step with the previously connected input. + */ + step_label?: string | null; + }; + /** + * RefactorActionExecutionMessageTypeEnum + * @enum {string} + */ + RefactorActionExecutionMessageTypeEnum: + | "tool_version_change" + | "tool_state_adjustment" + | "connection_drop_forced" + | "workflow_output_drop_forced"; + /** RefactorRequest */ + RefactorRequest: { + /** Actions */ + actions: ( + | components["schemas"]["AddInputAction"] + | components["schemas"]["AddStepAction"] + | components["schemas"]["ConnectAction"] + | components["schemas"]["DisconnectAction"] + | components["schemas"]["ExtractInputAction"] + | components["schemas"]["ExtractUntypedParameter"] + | components["schemas"]["FileDefaultsAction"] + | components["schemas"]["FillStepDefaultsAction"] + | components["schemas"]["UpdateAnnotationAction"] + | components["schemas"]["UpdateCreatorAction"] + | components["schemas"]["UpdateNameAction"] + | components["schemas"]["UpdateLicenseAction"] + | components["schemas"]["UpdateOutputLabelAction"] + | components["schemas"]["UpdateReportAction"] + | components["schemas"]["UpdateStepLabelAction"] + | components["schemas"]["UpdateStepPositionAction"] + | components["schemas"]["UpgradeSubworkflowAction"] + | components["schemas"]["UpgradeToolAction"] + | components["schemas"]["UpgradeAllStepsAction"] + | components["schemas"]["RemoveUnlabeledWorkflowOutputs"] + )[]; + /** + * Dry Run + * @default false + */ + dry_run?: boolean; + /** + * Style + * @default export + */ + style?: string; + }; + /** RefactorResponse */ + RefactorResponse: { + /** Action Executions */ + action_executions: components["schemas"]["RefactorActionExecution"][]; + /** Dry Run */ + dry_run: boolean; + /** Workflow */ + workflow: string; + }; /** ReloadFeedback */ ReloadFeedback: { /** Failed */ @@ -9503,6 +10174,19 @@ export interface components { */ remote_user_email: string; }; + /** RemoveUnlabeledWorkflowOutputs */ + RemoveUnlabeledWorkflowOutputs: { + /** + * Action Type + * @constant + */ + action_type: "remove_unlabeled_workflow_outputs"; + }; + /** Report */ + Report: { + /** Markdown */ + markdown: string; + }; /** ReportJobErrorPayload */ ReportJobErrorPayload: { /** @@ -9720,7 +10404,7 @@ export interface components { */ name: string; /** @description Organization providing the service */ - organization: components["schemas"]["Organization"]; + organization: components["schemas"]["galaxy__schema__drs__Organization"]; type: components["schemas"]["ServiceType"]; /** * Updatedat @@ -10172,6 +10856,22 @@ export interface components { * @enum {string} */ Src: "url" | "pasted" | "files" | "path" | "composite" | "ftp_import" | "server_dir"; + /** StepReferenceByLabel */ + StepReferenceByLabel: { + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + }; + /** StepReferenceByOrderIndex */ + StepReferenceByOrderIndex: { + /** + * Order Index + * @description The order_index of the step being referenced. The order indices of a workflow start at 0. + */ + order_index: number; + }; /** StorageItemCleanupError */ StorageItemCleanupError: { /** Error */ @@ -10233,18 +10933,196 @@ export interface components { /** Type */ type: "history" | "dataset"; /** - * Update Time - * Format: date-time - * @description The last time and date this item was updated. + * Update Time + * Format: date-time + * @description The last time and date this item was updated. + */ + update_time: string; + }; + /** + * StoredItemOrderBy + * @description Available options for sorting Stored Items results. + * @enum {string} + */ + StoredItemOrderBy: "name-asc" | "name-dsc" | "size-asc" | "size-dsc" | "update_time-asc" | "update_time-dsc"; + /** StoredWorkflowDetailed */ + StoredWorkflowDetailed: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * Annotations + * @description An list of annotations to provide details or to help understand the purpose and usage of this workflow. + */ + annotations?: string[] | null; + /** + * Create Time + * Format: date-time + * @description The time and date this item was created. + */ + create_time: string; + /** + * Creator + * @description Additional information about the creator (or multiple creators) of this workflow. + */ + creator?: + | (components["schemas"]["Person"] | components["schemas"]["galaxy__schema__schema__Organization"])[] + | null; + /** + * Deleted + * @description Whether this item is marked as deleted. + */ + deleted: boolean; + /** + * Email Hash + * @description The hash of the email of the creator of this workflow + */ + email_hash: string | null; + /** + * Hidden + * @description TODO + */ + hidden: boolean; + /** + * Id + * @example 0123456789ABCDEF + */ + id: string; + /** + * Importable + * @description Indicates if the workflow is importable by the current user. + */ + importable: boolean | null; + /** + * Inputs + * @description A dictionary containing information about all the inputs of the workflow. + * @default {} + */ + inputs?: { + [key: string]: components["schemas"]["WorkflowInput"] | undefined; + }; + /** + * Latest workflow UUID + * Format: uuid4 + * @description TODO + */ + latest_workflow_uuid: string; + /** + * License + * @description SPDX Identifier of the license associated with this workflow. + */ + license?: string | null; + /** + * Model class + * @description The name of the database model class. + * @constant + */ + model_class: "StoredWorkflow"; + /** + * Name + * @description The name of the history. + */ + name: string; + /** + * Number of Steps + * @description The number of steps that make up this workflow. + */ + number_of_steps?: number | null; + /** + * Owner + * @description The name of the user who owns this workflow. + */ + owner: string; + /** + * Published + * @description Whether this workflow is currently publicly available to all users. + */ + published: boolean; + /** + * Show in Tool Panel + * @description Whether to display this workflow in the Tools Panel. + */ + show_in_tool_panel?: boolean | null; + /** + * Slug + * @description The slug of the workflow. + */ + slug: string | null; + /** + * Source Metadata + * @description The source metadata of the workflow. + */ + source_metadata: Record | null; + /** + * Steps + * @description A dictionary with information about all the steps of the workflow. + * @default {} + */ + steps?: { + [key: string]: + | ( + | components["schemas"]["InputDataStep"] + | components["schemas"]["InputDataCollectionStep"] + | components["schemas"]["InputParameterStep"] + | components["schemas"]["PauseStep"] + | components["schemas"]["ToolStep"] + | components["schemas"]["SubworkflowStep"] + ) + | undefined; + }; + tags: components["schemas"]["TagCollection"]; + /** + * Update Time + * Format: date-time + * @description The last time and date this item was updated. + */ + update_time: string; + /** + * URL + * @deprecated + * @description The relative URL to access this item. + */ + url: string; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version: number; + }; + /** SubworkflowStep */ + SubworkflowStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Type + * @description The type of workflow module. + * @default subworkflow + */ + type?: components["schemas"]["WorkflowModuleType"]; + /** + * Workflow ID + * @description The encoded ID of the workflow that will be run on this step. + * @example 0123456789ABCDEF */ - update_time: string; + workflow_id: string; }; - /** - * StoredItemOrderBy - * @description Available options for sorting Stored Items results. - * @enum {string} - */ - StoredItemOrderBy: "name-asc" | "name-dsc" | "size-asc" | "size-dsc" | "update_time-asc" | "update_time-dsc"; /** SuitableConverter */ SuitableConverter: { /** @@ -10388,6 +11266,47 @@ export interface components { */ values: string; }; + /** ToolStep */ + ToolStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Steps + * @description A dictionary containing information about the inputs connected to this workflow step. + */ + input_steps: { + [key: string]: components["schemas"]["InputStep"] | undefined; + }; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Inputs + * @description TODO + */ + tool_inputs?: Record; + /** + * Tool Version + * @description The version of the tool associated with this step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of workflow module. + * @default tool + */ + type?: components["schemas"]["WorkflowModuleType"]; + }; /** Tour */ Tour: { /** @@ -10500,6 +11419,16 @@ export interface components { */ ids: string[]; }; + /** UpdateAnnotationAction */ + UpdateAnnotationAction: { + /** + * Action Type + * @constant + */ + action_type: "update_annotation"; + /** Annotation */ + annotation: string; + }; /** * UpdateCollectionAttributePayload * @description Contains attributes that can be updated for all elements in a dataset collection. @@ -10528,6 +11457,16 @@ export interface components { id: string; [key: string]: unknown | undefined; }; + /** UpdateCreatorAction */ + UpdateCreatorAction: { + /** + * Action Type + * @constant + */ + action_type: "update_creator"; + /** Creator */ + creator?: Record; + }; /** * UpdateHistoryContentsBatchPayload * @description Contains property values that will be updated for all the history `items` provided. @@ -10616,6 +11555,26 @@ export interface components { */ synopsis?: string | null; }; + /** UpdateLicenseAction */ + UpdateLicenseAction: { + /** + * Action Type + * @constant + */ + action_type: "update_license"; + /** License */ + license: string; + }; + /** UpdateNameAction */ + UpdateNameAction: { + /** + * Action Type + * @constant + */ + action_type: "update_name"; + /** Name */ + name: string; + }; /** UpdateObjectStoreIdPayload */ UpdateObjectStoreIdPayload: { /** @@ -10624,6 +11583,20 @@ export interface components { */ object_store_id: string; }; + /** UpdateOutputLabelAction */ + UpdateOutputLabelAction: { + /** + * Action Type + * @constant + */ + action_type: "update_output_label"; + /** Output */ + output: + | components["schemas"]["OutputReferenceByOrderIndex"] + | components["schemas"]["OutputReferenceByLabel"]; + /** Output Label */ + output_label: string; + }; /** UpdateQuotaParams */ UpdateQuotaParams: { /** @@ -10663,6 +11636,47 @@ export interface components { */ operation?: components["schemas"]["QuotaOperation"]; }; + /** UpdateReportAction */ + UpdateReportAction: { + /** + * Action Type + * @constant + */ + action_type: "update_report"; + report: components["schemas"]["Report"]; + }; + /** UpdateStepLabelAction */ + UpdateStepLabelAction: { + /** + * Action Type + * @constant + */ + action_type: "update_step_label"; + /** + * Label + * @description The unique label of the step being referenced. + */ + label: string; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; + /** UpdateStepPositionAction */ + UpdateStepPositionAction: { + /** + * Action Type + * @constant + */ + action_type: "update_step_position"; + position_shift: components["schemas"]["Position"]; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; /** * UpdateUserNotificationPreferencesRequest * @description Contains the new notification preferences of a user. @@ -10676,6 +11690,44 @@ export interface components { [key: string]: components["schemas"]["NotificationCategorySettings"] | undefined; }; }; + /** UpgradeAllStepsAction */ + UpgradeAllStepsAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_all_steps"; + }; + /** UpgradeSubworkflowAction */ + UpgradeSubworkflowAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_subworkflow"; + /** Content Id */ + content_id?: string | null; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + }; + /** UpgradeToolAction */ + UpgradeToolAction: { + /** + * Action Type + * @constant + */ + action_type: "upgrade_tool"; + /** + * Step + * @description The target step for this action. + */ + step: components["schemas"]["StepReferenceByOrderIndex"] | components["schemas"]["StepReferenceByLabel"]; + /** Tool Version */ + tool_version?: string | null; + }; /** UrlDataElement */ UrlDataElement: { /** Md5 */ @@ -11050,6 +12102,25 @@ export interface components { * @default [] */ VisualizationSummaryList: components["schemas"]["VisualizationSummary"][]; + /** WorkflowInput */ + WorkflowInput: { + /** + * Label + * @description Label of the input. + */ + label: string; + /** + * UUID + * Format: uuid4 + * @description Universal unique identifier of the input. + */ + uuid: string; + /** + * Value + * @description TODO + */ + value: string; + }; /** WorkflowInvocationCollectionView */ WorkflowInvocationCollectionView: { /** @@ -11234,6 +12305,18 @@ export interface components { [key: string]: number | undefined; }; }; + /** + * WorkflowModuleType + * @description Available types of modules that represent a step in a Workflow. + * @enum {string} + */ + WorkflowModuleType: + | "data_input" + | "data_collection_input" + | "parameter_input" + | "subworkflow" + | "tool" + | "pause"; /** WriteInvocationStoreToPayload */ WriteInvocationStoreToPayload: { /** @@ -11352,6 +12435,52 @@ export interface components { */ namespace: string; }; + /** Organization */ + galaxy__schema__drs__Organization: { + /** + * Name + * @description Name of the organization responsible for the service + */ + name: string; + /** + * Url + * Format: uri + * @description URL of the website of the organization (RFC 3986 format) + */ + url: string; + }; + /** Organization */ + galaxy__schema__schema__Organization: { + /** Address */ + address?: string | null; + /** Alternate Name */ + alternateName?: string | null; + /** + * Class + * @default Organization + */ + class?: string; + /** Email */ + email?: string | null; + /** Fax Number */ + faxNumber?: string | null; + /** + * Identifier + * @description Identifier (typically an orcid.org ID) + */ + identifier?: string | null; + /** Image URL */ + image?: string | null; + /** + * Name + * @description The name of the creator. + */ + name: string; + /** Telephone */ + telephone?: string | null; + /** URL */ + url?: string | null; + }; }; responses: never; parameters: never; @@ -21580,6 +22709,40 @@ export interface operations { }; }; }; + show_workflow_api_workflows__workflow_id__get: { + /** Displays information needed to run a workflow. */ + parameters: { + /** @description Use the legacy workflow format. */ + /** @description The version of the workflow to fetch. */ + query?: { + instance?: boolean | null; + legacy?: boolean | null; + version?: number | null; + }; + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The encoded database identifier of the Stored Workflow. */ + path: { + workflow_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["StoredWorkflowDetailed"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; delete_workflow_api_workflows__workflow_id__delete: { /** Add the deleted flag to a workflow. */ parameters: { @@ -21749,6 +22912,40 @@ export interface operations { }; }; }; + Invoke_workflow_api_workflows__workflow_id__invocations_post: { + /** Schedule the workflow specified by `workflow_id` to run. */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The database identifier - UUID or encoded - of the Workflow. */ + path: { + workflow_id: string | string | string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InvokeWorkflowPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": + | components["schemas"]["WorkflowInvocationResponse"] + | components["schemas"]["WorkflowInvocationResponse"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; show_workflow_invocation_api_workflows__workflow_id__invocations__invocation_id__get: { /** * Get detailed description of a workflow invocation. @@ -22066,6 +23263,41 @@ export interface operations { }; }; }; + refactor_api_workflows__workflow_id__refactor_put: { + /** Updates the workflow stored with the given ID. */ + parameters: { + query?: { + instance?: boolean | null; + }; + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The encoded database identifier of the Stored Workflow. */ + path: { + workflow_id: string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RefactorRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RefactorResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; share_with_users_api_workflows__workflow_id__share_with_users_put: { /** * Share this item with specific users. @@ -22417,6 +23649,43 @@ export interface operations { }; }; }; + Invoke_workflow_api_workflows__workflow_id__usage_post: { + /** + * Schedule the workflow specified by `workflow_id` to run. + * @deprecated + */ + parameters: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + header?: { + "run-as"?: string | null; + }; + /** @description The database identifier - UUID or encoded - of the Workflow. */ + path: { + workflow_id: string | string | string; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InvokeWorkflowPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": + | components["schemas"]["WorkflowInvocationResponse"] + | components["schemas"]["WorkflowInvocationResponse"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; show_workflow_invocation_api_workflows__workflow_id__usage__invocation_id__get: { /** * Get detailed description of a workflow invocation. diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index f7342f60be9c..705438ee3f1c 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -2112,10 +2112,12 @@ class StoredWorkflowSummary(Model, WithModelClass): title="Published", description="Whether this workflow is currently publicly available to all users.", ) - annotations: List[str] = Field( # Inconsistency? Why workflows summaries use a list instead of an optional string? - ..., - title="Annotations", - description="An list of annotations to provide details or to help understand the purpose and usage of this workflow.", + annotations: Optional[List[str]] = ( + Field( # Inconsistency? Why workflows summaries use a list instead of an optional string? + None, + title="Annotations", + description="An list of annotations to provide details or to help understand the purpose and usage of this workflow.", + ) ) tags: TagCollection deleted: bool = Field( @@ -2138,13 +2140,13 @@ class StoredWorkflowSummary(Model, WithModelClass): title="Latest workflow UUID", description="TODO", ) - number_of_steps: int = Field( - ..., + number_of_steps: Optional[int] = Field( + None, title="Number of Steps", description="The number of steps that make up this workflow.", ) - show_in_tool_panel: bool = Field( - ..., + show_in_tool_panel: Optional[bool] = Field( + None, title="Show in Tool Panel", description="Whether to display this workflow in the Tools Panel.", ) @@ -2344,35 +2346,6 @@ class Person(Creator): ) -class StoredWorkflowDetailed(StoredWorkflowSummary): - annotation: Optional[str] = AnnotationField # Inconsistency? See comment on StoredWorkflowSummary.annotations - license: Optional[str] = Field( - None, title="License", description="SPDX Identifier of the license associated with this workflow." - ) - version: int = Field( - ..., title="Version", description="The version of the workflow represented by an incremental number." - ) - inputs: Dict[int, WorkflowInput] = Field( - {}, title="Inputs", description="A dictionary containing information about all the inputs of the workflow." - ) - creator: Optional[List[Union[Person, Organization]]] = Field( - None, - title="Creator", - description=("Additional information about the creator (or multiple creators) of this workflow."), - ) - steps: Dict[ - int, - Union[ - InputDataStep, - InputDataCollectionStep, - InputParameterStep, - PauseStep, - ToolStep, - SubworkflowStep, - ], - ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") - - class Input(Model): name: str = Field(..., title="Name", description="The name of the input.") description: str = Field(..., title="Description", description="The annotation or description of the input.") diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py new file mode 100644 index 000000000000..598878d749c2 --- /dev/null +++ b/lib/galaxy/schema/workflows.py @@ -0,0 +1,220 @@ +import json +from typing import ( + Any, + Dict, + List, + Optional, + Union, +) + +from pydantic import ( + Field, + field_validator, +) + +from galaxy.schema.schema import ( + AnnotationField, + InputDataCollectionStep, + InputDataStep, + InputParameterStep, + Model, + Organization, + PauseStep, + Person, + PreferredObjectStoreIdField, + StoredWorkflowSummary, + SubworkflowStep, + ToolStep, + WorkflowInput, +) + + +class GetTargetHistoryPayload(Model): + # TODO - Are the descriptions correct? + history: Optional[str] = Field( + None, + title="History", + # description="The encoded history id - passed exactly like this 'hist_id=...' - to import the workflow into. Or the name of the new history to import the workflow into.", + description="The encoded history id - passed exactly like this 'hist_id=...' - into which to import. Or the name of the new history into which to import.", + ) + history_id: Optional[str] = Field( + None, + title="History ID", + # description="The history to import the workflow into.", + description="The encoded history id into which to import.", + ) + new_history_name: Optional[str] = Field( + None, + title="New History Name", + # description="The name of the new history to import the workflow into.", + description="The name of the new history into which to import.", + ) + + +class InvokeWorkflowPayload(GetTargetHistoryPayload): + # TODO - Are the descriptions correct? + instance: Optional[bool] = Field( + False, + title="Is instance", + description="True when fetching by Workflow ID, False when fetching by StoredWorkflow ID", + ) + scheduler: Optional[str] = Field( + None, + title="Scheduler", + description="Scheduler to use for workflow invocation.", + ) + batch: Optional[bool] = Field( + False, + title="Batch", + description="Indicates if the workflow is invoked as a batch.", + ) + require_exact_tool_versions: Optional[bool] = Field( + True, + title="Require Exact Tool Versions", + description="If true, exact tool versions are required for workflow invocation.", + # description="TODO", + ) + allow_tool_state_corrections: Optional[bool] = Field( + False, + title="Allow tool state corrections", + description="Indicates if tool state corrections are allowed for workflow invocation.", + ) + use_cached_job: Optional[bool] = Field( + False, + title="Use cached job", + description="Indicated whether to use a cached job for workflow invocation.", + ) + parameters_normalized: Optional[bool] = Field( + False, + title="Parameters Normalized", + description="Indicates if parameters are already normalized for workflow invocation.", + ) + + @field_validator( + "parameters", + "inputs", + "ds_map", + "resource_params", + "replacement_params", + "step_parameters", + mode="before", + check_fields=False, + ) + @classmethod + def inputs_string_to_json(cls, v): + if isinstance(v, str): + return json.loads(v) + return v + + parameters: Optional[Dict[str, Any]] = Field( + {}, + title="Parameters", + description="The raw parameters for the workflow invocation.", + ) + inputs: Optional[Dict[str, Any]] = Field( + None, + title="Inputs", + description="TODO", + ) + ds_map: Optional[Dict[str, Dict[str, Any]]] = Field( + {}, + title="Dataset Map", + description="TODO", + ) + resource_params: Optional[Dict[str, Any]] = Field( + {}, + title="Resource Parameters", + description="TODO", + ) + replacement_params: Optional[Dict[str, Any]] = Field( + {}, + title="Replacement Parameters", + description="TODO", + ) + step_parameters: Optional[Dict[str, Any]] = Field( + None, + title="Step Parameters", + description="TODO", + ) + no_add_to_history: Optional[bool] = Field( + False, + title="No Add to History", + description="Indicates if the workflow invocation should not be added to the history.", + ) + legacy: Optional[bool] = Field( + False, + title="Legacy", + description="Indicating if to use legacy workflow invocation.", + ) + inputs_by: Optional[str] = Field( + None, + title="Inputs By", + # lib/galaxy/workflow/run_request.py - see line 60 + description="How inputs maps to inputs (datasets/collections) to workflows steps.", + ) + effective_outputs: Optional[Any] = Field( + None, + title="Effective Outputs", + # lib/galaxy/workflow/run_request.py - see line 455 + description="TODO", + ) + preferred_intermediate_object_store_id: Optional[str] = Field( + None, + title="Preferred Intermediate Object Store ID", + description="The ID of the ? object store that should be used to store ? datasets in this history.", + ) + preferred_outputs_object_store_id: Optional[str] = Field( + None, + title="Preferred Outputs Object Store ID", + description="The ID of the object store that should be used to store ? datasets in this history.", + ) + preferred_object_store_id: Optional[str] = PreferredObjectStoreIdField + + +class StoredWorkflowDetailed(StoredWorkflowSummary): + annotation: Optional[str] = AnnotationField # Inconsistency? See comment on StoredWorkflowSummary.annotations + license: Optional[str] = Field( + None, title="License", description="SPDX Identifier of the license associated with this workflow." + ) + version: int = Field( + ..., title="Version", description="The version of the workflow represented by an incremental number." + ) + inputs: Dict[int, WorkflowInput] = Field( + {}, title="Inputs", description="A dictionary containing information about all the inputs of the workflow." + ) + creator: Optional[List[Union[Person, Organization]]] = Field( + None, + title="Creator", + description=("Additional information about the creator (or multiple creators) of this workflow."), + ) + steps: Dict[ + int, + Union[ + InputDataStep, + InputDataCollectionStep, + InputParameterStep, + PauseStep, + ToolStep, + SubworkflowStep, + ], + ] = Field({}, title="Steps", description="A dictionary with information about all the steps of the workflow.") + importable: Optional[bool] = Field( + ..., + title="Importable", + description="Indicates if the workflow is importable by the current user.", + ) + email_hash: Optional[str] = Field( + ..., + title="Email Hash", + description="The hash of the email of the creator of this workflow", + ) + slug: Optional[str] = Field( + ..., + title="Slug", + description="The slug of the workflow.", + ) + source_metadata: Optional[Dict[str, Any]] = Field( + ..., + title="Source Metadata", + description="The source metadata of the workflow.", + ) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 7d4df8da2661..bf755e43f009 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -23,6 +23,10 @@ ) from gxformat2._yaml import ordered_dump from markupsafe import escape +from pydantic import ( + UUID1, + UUID4, +) from starlette.responses import StreamingResponse from typing_extensions import Annotated @@ -42,6 +46,7 @@ from galaxy.managers.workflows import ( MissingToolsException, RefactorRequest, + RefactorResponse, WorkflowCreateOptions, WorkflowUpdateOptions, ) @@ -53,7 +58,6 @@ CreateInvocationFromStore, CreateInvocationsFromStorePayload, InvocationJobsResponse, - InvocationMessageResponseModel, InvocationReport, InvocationSerializationParams, InvocationStep, @@ -74,6 +78,10 @@ SharingStatus, WorkflowSortByEnum, ) +from galaxy.schema.workflows import ( + InvokeWorkflowPayload, + StoredWorkflowDetailed, +) from galaxy.structured_app import StructuredApp from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager from galaxy.tools import recommendations @@ -83,7 +91,6 @@ from galaxy.version import VERSION from galaxy.web import ( expose_api, - expose_api_anonymous_and_sessionless, expose_api_raw_anonymous_and_sessionless, format_return_as_json, ) @@ -118,8 +125,6 @@ ) from galaxy.workflow.extract import extract_workflow from galaxy.workflow.modules import module_factory -from galaxy.workflow.run import queue_invoke -from galaxy.workflow.run_request import build_workflow_run_configs log = logging.getLogger(__name__) @@ -135,7 +140,6 @@ class WorkflowsAPIController( ConsumesModelStores, ): service: WorkflowsService = depends(WorkflowsService) - invocations_service: InvocationsService = depends(InvocationsService) def __init__(self, app: StructuredApp): super().__init__(app) @@ -184,38 +188,6 @@ def set_workflow_menu(self, trans: GalaxyWebTransaction, payload=None, **kwd): trans.set_message(message) return {"message": message, "status": "done"} - @expose_api_anonymous_and_sessionless - def show(self, trans: GalaxyWebTransaction, id, **kwd): - """ - GET /api/workflows/{encoded_workflow_id} - - :param instance: true if fetch by Workflow ID instead of StoredWorkflow id, false - by default. - :type instance: boolean - - Displays information needed to run a workflow. - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwd) - if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: - wf_count = 0 if not trans.user else trans.user.count_stored_workflow_user_assocs(stored_workflow) - if wf_count == 0: - message = "Workflow is neither importable, nor owned by or shared with current user" - raise exceptions.ItemAccessibilityException(message) - if kwd.get("legacy", False): - style = "legacy" - else: - style = "instance" - version = kwd.get("version") - if version is None and util.string_as_bool(kwd.get("instance", "false")): - # A Workflow instance may not be the latest workflow version attached to StoredWorkflow. - # This figures out the correct version so that we return the correct Workflow and version. - workflow_id = self.decode_id(id) - for i, workflow in enumerate(reversed(stored_workflow.workflows)): - if workflow.id == workflow_id: - version = i - break - return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) - @expose_api def create(self, trans: GalaxyWebTransaction, payload=None, **kwd): """ @@ -555,26 +527,6 @@ def update(self, trans: GalaxyWebTransaction, id, payload, **kwds): raise exceptions.RequestParameterInvalidException(message) return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style="instance") - @expose_api - def refactor(self, trans, id, payload, **kwds): - """ - * PUT /api/workflows/{id}/refactor - updates the workflow stored with ``id`` - - :type id: str - :param id: the encoded id of the workflow to update - :param instance: true if fetch by Workflow ID instead of StoredWorkflow id, false - by default. - :type instance: boolean - :type payload: dict - :param payload: a dictionary containing list of actions to apply. - :rtype: dict - :returns: serialized version of the workflow - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwds) - refactor_request = RefactorRequest(**payload) - return self.workflow_contents_manager.refactor(trans, stored_workflow, refactor_request) - @expose_api def build_module(self, trans: GalaxyWebTransaction, payload=None): """ @@ -607,12 +559,9 @@ def build_module(self, trans: GalaxyWebTransaction, payload=None): def get_tool_predictions(self, trans: ProvidesUserContext, payload, **kwd): """ POST /api/workflows/get_tool_predictions - Fetch predicted tools for a workflow - :type payload: dict :param payload: - a dictionary containing two parameters 'tool_sequence' - comma separated sequence of tool ids 'remote_model_url' - (optional) path to the deep learning model @@ -721,76 +670,6 @@ def __api_import_shared_workflow(self, trans: GalaxyWebTransaction, workflow_id, item["url"] = url_for("workflow", id=encoded_id) return item - @expose_api - def invoke(self, trans: GalaxyWebTransaction, workflow_id, payload, **kwd): - """ - POST /api/workflows/{encoded_workflow_id}/invocations - - Schedule the workflow specified by `workflow_id` to run. - - .. note:: This method takes the same arguments as - :func:`galaxy.webapps.galaxy.api.workflows.WorkflowsAPIController.create` above. - - :raises: exceptions.MessageException, exceptions.RequestParameterInvalidException - """ - # Get workflow + accessibility check. - stored_workflow = self.__get_stored_accessible_workflow(trans, workflow_id, instance=kwd.get("instance", False)) - workflow = stored_workflow.latest_workflow - run_configs = build_workflow_run_configs(trans, workflow, payload) - is_batch = payload.get("batch") - if not is_batch and len(run_configs) != 1: - raise exceptions.RequestParameterInvalidException("Must specify 'batch' to use batch parameters.") - - require_exact_tool_versions = util.string_as_bool(payload.get("require_exact_tool_versions", "true")) - tools = self.workflow_contents_manager.get_all_tools(workflow) - missing_tools = [ - tool - for tool in tools - if not self.app.toolbox.has_tool( - tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions - ) - ] - if missing_tools: - missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " - if require_exact_tool_versions: - missing_tools_message += ", ".join( - [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] - ) - else: - missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) - raise exceptions.MessageException(missing_tools_message) - - invocations = [] - for run_config in run_configs: - workflow_scheduler_id = payload.get("scheduler", None) - # TODO: workflow scheduler hints - work_request_params = dict(scheduler=workflow_scheduler_id) - workflow_invocation = queue_invoke( - trans=trans, - workflow=workflow, - workflow_run_config=run_config, - request_params=work_request_params, - flush=False, - ) - invocations.append(workflow_invocation) - - with transaction(trans.sa_session): - trans.sa_session.commit() - encoded_invocations = [] - for invocation in invocations: - as_dict = workflow_invocation.to_dict() - as_dict = self.encode_all_ids(trans, as_dict, recursive=True) - as_dict["messages"] = [ - InvocationMessageResponseModel.model_validate(message).model_dump(mode="json") - for message in invocation.messages - ] - encoded_invocations.append(as_dict) - - if is_batch: - return encoded_invocations - else: - return encoded_invocations[0] - def _workflow_from_dict(self, trans, data, workflow_create_options, source=None): """Creates a workflow from a dict. @@ -892,6 +771,15 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +MultiTypeWorkflowIDPathParam = Annotated[ + Union[UUID4, UUID1, DecodedDatabaseIdField], + Path( + ..., + title="Workflow ID", + description="The database identifier - UUID or encoded - of the Workflow.", + ), +] + DeletedQueryParam: bool = Query( default=False, title="Display deleted", description="Whether to restrict result to deleted workflows." ) @@ -938,6 +826,22 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +LegacyQueryParam = Annotated[ + Optional[bool], + Query( + title="Legacy", + description="Use the legacy workflow format.", + ), +] + +VersionQueryParam = Annotated[ + Optional[int], + Query( + title="Version", + description="The version of the workflow to fetch.", + ), +] + query_tags = [ IndexQueryTag("name", "The stored workflow's name.", "n"), IndexQueryTag( @@ -968,6 +872,24 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): description="Set this to true to skip joining workflow step counts and optimize the resulting index query. Response objects will not contain step counts.", ) +InvokeWorkflowBody = Annotated[ + InvokeWorkflowPayload, + Body( + default=..., + title="Invoke workflow", + description="The values to invoke a workflow.", + ), +] + +RefactorWorkflowBody = Annotated[ + RefactorRequest, + Body( + default=..., + title="Refactor workflow", + description="The values to refactor a workflow.", + ), +] + @router.cbv class FastAPIWorkflows: @@ -1048,6 +970,19 @@ def disable_link_access( """Makes this item inaccessible by a URL link and return the current sharing status.""" return self.service.shareable_service.disable_link_access(trans, workflow_id) + @router.put( + "/api/workflows/{workflow_id}/refactor", + summary="Updates the workflow stored with the given ID.", + ) + def refactor( + self, + workflow_id: StoredWorkflowIDPathParam, + payload: RefactorWorkflowBody, + instance: InstanceQueryParam = False, + trans: ProvidesUserContext = DependsOnTrans, + ) -> RefactorResponse: + return self.service.refactor(trans, workflow_id, payload, instance or False) + @router.put( "/api/workflows/{workflow_id}/publish", summary="Makes this item public and accessible by a URL link.", @@ -1124,6 +1059,25 @@ def undelete_workflow( self.service.undelete(trans, workflow_id) return Response(status_code=status.HTTP_204_NO_CONTENT) + @router.post( + "/api/workflows/{workflow_id}/invocations", + name="Invoke workflow", + summary="Schedule the workflow specified by `workflow_id` to run.", + ) + @router.post( + "/api/workflows/{workflow_id}/usage", + name="Invoke workflow", + summary="Schedule the workflow specified by `workflow_id` to run.", + deprecated=True, + ) + def invoke( + self, + payload: InvokeWorkflowBody, + workflow_id: MultiTypeWorkflowIDPathParam, + trans: ProvidesHistoryContext = DependsOnTrans, + ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: + return self.service.invoke_workflow(trans, workflow_id, payload) + @router.get( "/api/workflows/{workflow_id}/versions", summary="List all versions of a workflow.", @@ -1175,6 +1129,21 @@ def get_workflow_menu( payload=payload, ) + @router.get( + "/api/workflows/{workflow_id}", + summary="Displays information needed to run a workflow.", + name="show_workflow", + ) + def show_workflow( + self, + workflow_id: StoredWorkflowIDPathParam, + instance: InstanceQueryParam = False, + legacy: LegacyQueryParam = False, + version: VersionQueryParam = None, + trans: ProvidesHistoryContext = DependsOnTrans, + ) -> StoredWorkflowDetailed: + return self.service.show_workflow(trans, workflow_id, instance, legacy, version) + StepDetailQueryParam = Annotated[ bool, diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 2ff3ea8c2e1e..bad2c03b7ea3 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -593,9 +593,6 @@ def populate_api_routes(webapp, app): webapp.mapper.connect( "/api/workflows/menu", action="set_workflow_menu", controller="workflows", conditions=dict(method=["PUT"]) ) - webapp.mapper.connect( - "/api/workflows/{id}/refactor", action="refactor", controller="workflows", conditions=dict(method=["PUT"]) - ) webapp.mapper.resource("workflow", "workflows", path_prefix="/api") # ---- visualizations registry ---- generic template renderer @@ -689,23 +686,18 @@ def populate_api_routes(webapp, app): # action="import_tool_version", # conditions=dict(method=["POST"]), # ) - - # API refers to usages and invocations - these mean the same thing but the - # usage routes should be considered deprecated. - invoke_names = { - "invocations": "", - "usage": "_deprecated", - } - for noun, suffix in invoke_names.items(): - name = f"{noun}{suffix}" - webapp.mapper.connect( - f"workflow_{name}", - "/api/workflows/{workflow_id}/%s" % noun, - controller="workflows", - action="invoke", - conditions=dict(method=["POST"]), - ) - + webapp.mapper.connect( + "/api/workflows/{encoded_workflow_id}", + controller="workflows", + action="update", + conditions=dict(method=["PUT"]), + ) + webapp.mapper.connect( + "/api/workflows", + controller="workflows", + action="create", + conditions=dict(method=["POST"]), + ) # ================================ # ===== USERS API ===== # ================================ diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 7710d3479b56..52e35d96c8fe 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -5,24 +5,37 @@ List, Optional, Tuple, + Union, ) -from galaxy import web +from galaxy import ( + exceptions, + web, +) from galaxy.managers.context import ProvidesUserContext from galaxy.managers.notification import NotificationManager from galaxy.managers.workflows import ( + RefactorResponse, WorkflowContentsManager, WorkflowSerializer, WorkflowsManager, ) from galaxy.model import StoredWorkflow +from galaxy.model.base import transaction +from galaxy.schema.invocation import WorkflowInvocationResponse from galaxy.schema.schema import ( InvocationsStateCounts, WorkflowIndexQueryPayload, ) +from galaxy.schema.workflows import ( + InvokeWorkflowPayload, + StoredWorkflowDetailed, +) from galaxy.util.tool_shed.tool_shed_registry import Registry from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.sharable import ShareableService +from galaxy.workflow.run import queue_invoke +from galaxy.workflow.run_request import build_workflow_run_configs log = logging.getLogger(__name__) @@ -105,6 +118,62 @@ def index( return workflows, total_matches return rval, total_matches + def invoke_workflow( + self, + trans, + workflow_id, + payload: InvokeWorkflowPayload, + ) -> Union[WorkflowInvocationResponse, List[WorkflowInvocationResponse]]: + # Get workflow + accessibility check. + by_stored_id = not payload.instance + stored_workflow = self._workflows_manager.get_stored_accessible_workflow(trans, workflow_id, by_stored_id) + workflow = stored_workflow.latest_workflow + run_configs = build_workflow_run_configs(trans, workflow, payload.model_dump(exclude_unset=True)) + is_batch = payload.batch + if not is_batch and len(run_configs) != 1: + raise exceptions.RequestParameterInvalidException("Must specify 'batch' to use batch parameters.") + + require_exact_tool_versions = payload.require_exact_tool_versions + tools = self._workflow_contents_manager.get_all_tools(workflow) + missing_tools = [ + tool + for tool in tools + if not trans.app.toolbox.has_tool( + tool["tool_id"], tool_version=tool["tool_version"], exact=require_exact_tool_versions + ) + ] + if missing_tools: + missing_tools_message = "Workflow was not invoked; the following required tools are not installed: " + if require_exact_tool_versions: + missing_tools_message += ", ".join( + [f"{tool['tool_id']} (version {tool['tool_version']})" for tool in missing_tools] + ) + else: + missing_tools_message += ", ".join([tool["tool_id"] for tool in missing_tools]) + raise exceptions.MessageException(missing_tools_message) + + invocations = [] + for run_config in run_configs: + workflow_scheduler_id = payload.scheduler + # TODO: workflow scheduler hints + work_request_params = dict(scheduler=workflow_scheduler_id) + workflow_invocation = queue_invoke( + trans=trans, + workflow=workflow, + workflow_run_config=run_config, + request_params=work_request_params, + flush=False, + ) + invocations.append(workflow_invocation) + + with transaction(trans.sa_session): + trans.sa_session.commit() + encoded_invocations = [WorkflowInvocationResponse(**invocation.to_dict()) for invocation in invocations] + if is_batch: + return encoded_invocations + else: + return encoded_invocations[0] + def delete(self, trans, workflow_id): workflow_to_delete = self._workflows_manager.get_stored_workflow(trans, workflow_id) self._workflows_manager.check_security(trans, workflow_to_delete) @@ -138,6 +207,39 @@ def get_workflow_menu(self, trans, payload): ) return {"ids_in_menu": ids_in_menu, "workflows": workflows} + def refactor( + self, + trans, + workflow_id, + payload, + instance: bool, + ) -> RefactorResponse: + stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + return self._workflow_contents_manager.refactor(trans, stored_workflow, payload) + + def show_workflow(self, trans, workflow_id, instance, legacy, version) -> StoredWorkflowDetailed: + stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + if stored_workflow.importable is False and stored_workflow.user != trans.user and not trans.user_is_admin: + wf_count = 0 if not trans.user else trans.user.count_stored_workflow_user_assocs(stored_workflow) + if wf_count == 0: + message = "Workflow is neither importable, nor owned by or shared with current user" + raise exceptions.ItemAccessibilityException(message) + if legacy: + style = "legacy" + else: + style = "instance" + if version is None and instance: + # A Workflow instance may not be the latest workflow version attached to StoredWorkflow. + # This figures out the correct version so that we return the correct Workflow and version. + for i, workflow in enumerate(reversed(stored_workflow.workflows)): + if workflow.id == workflow_id: + version = i + break + detailed_workflow = StoredWorkflowDetailed( + **self._workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style=style, version=version) + ) + return detailed_workflow + def _get_workflows_list( self, trans: ProvidesUserContext, diff --git a/lib/galaxy_test/api/test_workflow_extraction.py b/lib/galaxy_test/api/test_workflow_extraction.py index d57554de6998..4d48887cc30d 100644 --- a/lib/galaxy_test/api/test_workflow_extraction.py +++ b/lib/galaxy_test/api/test_workflow_extraction.py @@ -478,7 +478,7 @@ def __copy_content_to_history(self, history_id, content): def __setup_and_run_cat1_workflow(self, history_id): workflow = self.workflow_populator.load_workflow(name="test_for_extract") workflow_request, history_id, workflow_id = self._setup_workflow_run(workflow, history_id=history_id) - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 200) invocation_response = run_workflow_response.json() self.workflow_populator.wait_for_invocation_and_jobs( diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index aa0d83b3c9e5..f1e1c9fe4e9c 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -5172,7 +5172,7 @@ def test_cannot_run_inaccessible_workflow(self): workflow = self.workflow_populator.load_workflow(name="test_for_run_cannot_access") workflow_request, _, workflow_id = self._setup_workflow_run(workflow) with self._different_user(): - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 403) def test_400_on_invalid_workflow_id(self): @@ -5187,7 +5187,7 @@ def test_cannot_run_against_other_users_history(self): with self._different_user(): other_history_id = self.dataset_populator.new_history() workflow_request["history"] = f"hist_id={other_history_id}" - run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request) + run_workflow_response = self._post(f"workflows/{workflow_id}/invocations", data=workflow_request, json=True) self._assert_status_code_is(run_workflow_response, 403) def test_cannot_run_bootstrap_admin_workflow(self): @@ -6538,7 +6538,7 @@ def test_run_batch(self): "parameters_normalized": True, "parameters": dumps(parameters), } - invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request) + invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request, json=True) self._assert_status_code_is(invocation_response, 200) time.sleep(5) self.dataset_populator.wait_for_history(history_id, assert_ok=True) @@ -6590,7 +6590,7 @@ def test_run_batch_inputs(self): "parameters_normalized": True, "parameters": dumps(parameters), } - invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request) + invocation_response = self._post(f"workflows/{workflow_id}/usage", data=workflow_request, json=True) self._assert_status_code_is(invocation_response, 200) time.sleep(5) self.dataset_populator.wait_for_history(history_id, assert_ok=True) @@ -6633,7 +6633,7 @@ def test_parameter_substitution_validation_value_errors_0(self): history=f"hist_id={history_id}", parameters=dumps(dict(validation_repeat={"r2_0|text": ""})) ) url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=workflow_request) + invocation_response = self._post(url, data=workflow_request, json=True) # Take a valid stat and make it invalid, assert workflow won't run. self._assert_status_code_is(invocation_response, 400) diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 3bad7b7f034f..d3cf5711f910 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -1821,7 +1821,7 @@ def validate_invocation_crate_directory(self, crate_directory): def invoke_workflow_raw(self, workflow_id: str, request: dict, assert_ok: bool = False) -> Response: url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=request) + invocation_response = self._post(url, data=request, json=True) if assert_ok: invocation_response.raise_for_status() return invocation_response diff --git a/test/integration/test_workflow_handler_configuration.py b/test/integration/test_workflow_handler_configuration.py index 3e2893a8d815..27cfeb5375d8 100644 --- a/test/integration/test_workflow_handler_configuration.py +++ b/test/integration/test_workflow_handler_configuration.py @@ -120,7 +120,7 @@ def _invoke_n_workflows(self, n, history_id: str): request["inputs_by"] = "step_index" url = f"workflows/{workflow_id}/invocations" for _ in range(n): - self._post(url, data=request) + self._post(url, data=request, json=True) def _get_workflow_invocations(self, history_id: str): # Consider exposing handler via the API to reduce breaking diff --git a/test/integration/test_workflow_scheduling_options.py b/test/integration/test_workflow_scheduling_options.py index cdea882fd79d..9ec950e0734c 100644 --- a/test/integration/test_workflow_scheduling_options.py +++ b/test/integration/test_workflow_scheduling_options.py @@ -36,7 +36,7 @@ def test(self): request["inputs"] = dumps(index_map) request["inputs_by"] = "step_index" url = f"workflows/{workflow_id}/invocations" - invocation_response = self._post(url, data=request) + invocation_response = self._post(url, data=request, json=True) invocation_url = url + "/" + invocation_response.json()["id"] time.sleep(5) state = self._get(invocation_url).json()["state"]