diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 373d5d14556b..bdad3cf43b2e 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -4867,6 +4867,24 @@ export interface paths { */ get: operations["index_api_workflows_get"]; put?: never; + /** Create workflows in various ways */ + post: operations["create_workflow_api_workflows_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/workflows/download/{workflow_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Returns a selected workflow. */ + get: operations["workflow_dict_api_workflows_download__workflow_id__get"]; + put?: never; post?: never; delete?: never; options?: never; @@ -4883,7 +4901,8 @@ export interface paths { }; /** Get workflows present in the tools panel. */ get: operations["get_workflow_menu_api_workflows_menu_get"]; - put?: never; + /** Save workflow menu to be shown in the tool panel */ + put: operations["set_workflow_menu_api_workflows_menu_put"]; post?: never; delete?: never; options?: never; @@ -4900,7 +4919,8 @@ export interface paths { }; /** Displays information needed to run a workflow. */ get: operations["show_workflow_api_workflows__workflow_id__get"]; - put?: never; + /** Update the workflow stored with the ``id`` */ + put: operations["update_workflow_api_workflows__workflow_id__put"]; post?: never; /** Add the deleted flag to a workflow. */ delete: operations["delete_workflow_api_workflows__workflow_id__delete"]; @@ -4946,6 +4966,23 @@ export interface paths { patch?: never; trace?: never; }; + "/api/workflows/{workflow_id}/download": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Returns a selected workflow. */ + get: operations["workflow_dict_api_workflows__workflow_id__download_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/workflows/{workflow_id}/enable_link_access": { parameters: { query?: never; @@ -8936,6 +8973,97 @@ export interface components { */ update_time: string; }; + /** FrameComment */ + FrameComment: { + /** + * Child Comments + * @description A list of ids (see `id`) of all Comments which are encompassed by this Frame + */ + child_comments?: number[] | null; + /** + * Child Steps + * @description A list of ids of all Steps (see WorkflowStep.id) which are encompassed by this Frame + */ + child_steps?: number[] | null; + /** + * Color + * @description Color this comment is displayed as. The exact color hex is determined by the client + * @enum {string} + */ + color: "none" | "black" | "blue" | "turquoise" | "green" | "lime" | "orange" | "yellow" | "red" | "pink"; + data: components["schemas"]["FrameCommentData"]; + /** + * Id + * @description Unique identifier for this comment. Determined by the comments order + */ + id: number; + /** + * Position + * @description [x, y] position of this comment in the Workflow + */ + position: [number, number]; + /** + * Size + * @description [width, height] size of this comment + */ + size: [number, number]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "frame"; + }; + /** FrameCommentData */ + FrameCommentData: { + /** + * Title + * @description The Frames title + */ + title: string; + }; + /** FreehandComment */ + FreehandComment: { + /** + * Color + * @description Color this comment is displayed as. The exact color hex is determined by the client + * @enum {string} + */ + color: "none" | "black" | "blue" | "turquoise" | "green" | "lime" | "orange" | "yellow" | "red" | "pink"; + data: components["schemas"]["FreehandCommentData"]; + /** + * Id + * @description Unique identifier for this comment. Determined by the comments order + */ + id: number; + /** + * Position + * @description [x, y] position of this comment in the Workflow + */ + position: [number, number]; + /** + * Size + * @description [width, height] size of this comment + */ + size: [number, number]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "freehand"; + }; + /** FreehandCommentData */ + FreehandCommentData: { + /** + * Line + * @description List of [x, y] coordinates determining the unsmoothed line. Smoothing is done client-side using Catmull-Rom + */ + line: [number, number][]; + /** + * Thickness + * @description Width of the Line in pixels + */ + thickness: number; + }; /** FtpImportElement */ FtpImportElement: { /** Md5 */ @@ -11135,6 +11263,21 @@ export interface components { */ uri: string; }; + /** InputConnection */ + InputConnection: { + /** + * ID + * @description The order index of the step. + */ + id: number; + /** Input Subworkflow Step ID */ + input_subworkflow_step_id?: number | null; + /** + * Output Name + * @description The output name of the input step that serves as the source for this connection. + */ + output_name: string; + }; /** InputDataCollectionStep */ InputDataCollectionStep: { /** @@ -12042,16 +12185,12 @@ export interface components { batch: boolean | null; /** * Dataset Map - * @description TODO * @default {} */ ds_map: { [key: string]: Record; } | null; - /** - * Effective Outputs - * @description TODO - */ + /** Effective Outputs */ effective_outputs?: unknown | null; /** * History @@ -12063,10 +12202,7 @@ export interface components { * @description The encoded history id into which to import. */ history_id?: string | null; - /** - * Inputs - * @description TODO - */ + /** Inputs */ inputs?: Record | null; /** * Inputs By @@ -12125,7 +12261,6 @@ export interface components { preferred_outputs_object_store_id?: string | null; /** * Replacement Parameters - * @description TODO * @default {} */ replacement_params: Record | null; @@ -12137,7 +12272,6 @@ export interface components { require_exact_tool_versions: boolean | null; /** * Resource Parameters - * @description TODO * @default {} */ resource_params: Record | null; @@ -12146,10 +12280,7 @@ export interface components { * @description Scheduler to use for workflow invocation. */ scheduler?: string | null; - /** - * Step Parameters - * @description TODO - */ + /** Step Parameters */ step_parameters?: Record | null; /** * Use cached job @@ -13250,6 +13381,44 @@ export interface components { * @enum {string} */ MandatoryNotificationCategory: "broadcast"; + /** MarkdownComment */ + MarkdownComment: { + /** + * Color + * @description Color this comment is displayed as. The exact color hex is determined by the client + * @enum {string} + */ + color: "none" | "black" | "blue" | "turquoise" | "green" | "lime" | "orange" | "yellow" | "red" | "pink"; + data: components["schemas"]["MarkdownCommentData"]; + /** + * Id + * @description Unique identifier for this comment. Determined by the comments order + */ + id: number; + /** + * Position + * @description [x, y] position of this comment in the Workflow + */ + position: [number, number]; + /** + * Size + * @description [width, height] size of this comment + */ + size: [number, number]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "markdown"; + }; + /** MarkdownCommentData */ + MarkdownCommentData: { + /** + * Text + * @description The unrendered source Markdown for this Comment + */ + text: string; + }; /** MaterializeDatasetInstanceAPIRequest */ MaterializeDatasetInstanceAPIRequest: { /** @@ -14227,6 +14396,29 @@ export interface components { /** Top */ top: number; }; + /** PostJobAction */ + PostJobAction: { + /** + * Action Arguments + * @description Any additional arguments needed by the action. + */ + action_arguments: Record; + /** + * Action Type + * @description The type of action to run. + */ + action_type: string; + /** + * Output Name + * @description The name of the output that will be affected by the action. + */ + output_name: string; + /** + * Short String + * @description A short string representation of the action. + */ + short_str?: string | null; + }; /** PrepareStoreDownloadPayload */ PrepareStoreDownloadPayload: { /** @@ -14899,6 +15091,27 @@ export interface components { */ new_slug: string; }; + /** SetWorkflowMenuPayload */ + SetWorkflowMenuPayload: { + /** + * Workflow IDs + * @description The list of workflow IDs to set the menu entry for. + */ + workflow_ids: string[] | string; + }; + /** SetWorkflowMenuSummary */ + SetWorkflowMenuSummary: { + /** + * Message + * @description The message of the operation. + */ + message: unknown | null; + /** + * Status + * @description The status of the operation. + */ + status: string; + }; /** ShareHistoryExtra */ ShareHistoryExtra: { /** @@ -15314,6 +15527,11 @@ export interface components { * @enum {string} */ Src: "url" | "pasted" | "files" | "path" | "composite" | "ftp_import" | "server_dir"; + /** StepIn */ + StepIn: { + /** Default */ + default: unknown; + }; /** StepReferenceByLabel */ StepReferenceByLabel: { /** @@ -15744,6 +15962,59 @@ export interface components { */ type: "string"; }; + /** TextComment */ + TextComment: { + /** + * Color + * @description Color this comment is displayed as. The exact color hex is determined by the client + * @enum {string} + */ + color: "none" | "black" | "blue" | "turquoise" | "green" | "lime" | "orange" | "yellow" | "red" | "pink"; + data: components["schemas"]["TextCommentData"]; + /** + * Id + * @description Unique identifier for this comment. Determined by the comments order + */ + id: number; + /** + * Position + * @description [x, y] position of this comment in the Workflow + */ + position: [number, number]; + /** + * Size + * @description [width, height] size of this comment + */ + size: [number, number]; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "text"; + }; + /** TextCommentData */ + TextCommentData: { + /** + * Bold + * @description If the Comments text is bold. Absent is interpreted as false + */ + bold?: boolean | null; + /** + * Italic + * @description If the Comments text is italic. Absent is interpreted as false + */ + italic?: boolean | null; + /** + * Size + * @description Relative size (1 -> 100%) of the text compared to the default text sitz + */ + size: number; + /** + * Text + * @description The plaintext text of this comment + */ + text: string; + }; /** ToolDataDetails */ ToolDataDetails: { /** @@ -15824,6 +16095,26 @@ export interface components { */ values: string; }; + /** ToolShedRepositorySummary */ + ToolShedRepositorySummary: { + /** Changeset Revision */ + changeset_revision: string; + /** + * Name + * @description The name of the repository. + */ + name: string; + /** + * Owner + * @description The owner of the repository. + */ + owner: string; + /** + * Tool Shed + * @description The Tool Shed base URL. + */ + tool_shed: string; + }; /** ToolStep */ ToolStep: { /** @@ -16880,100 +17171,1223 @@ export interface components { * @default [] */ VisualizationSummaryList: components["schemas"]["VisualizationSummary"][]; - /** WorkflowInput */ - WorkflowInput: { + /** WorkflowCommentModel */ + WorkflowCommentModel: + | components["schemas"]["TextComment"] + | components["schemas"]["MarkdownComment"] + | components["schemas"]["FrameComment"] + | components["schemas"]["FreehandComment"]; + /** WorkflowCreatePayload */ + WorkflowCreatePayload: { + /** Allow Missing Tools */ + allow_missing_tools?: boolean | null; /** - * Label - * @description Label of the input. + * Archive File + * @description A file containing a workflow archive to be imported. */ - label: string | null; + archive_file?: unknown | null; /** - * UUID - * @description Universal unique identifier of the input. + * Archive Source + * @description A URL or file path pointing to a workflow archive to be imported. */ - uuid: string | null; + archive_source?: string | null; /** - * Value - * @description TODO + * Dataset Collection IDs + * @description If from_history_id is set, this is an optional list of HDCA 'hid's corresponding to workflow inputs when extracting a workflow from history. */ - value: unknown | null; - }; - /** WorkflowInvocationCollectionView */ - WorkflowInvocationCollectionView: { + dataset_collection_ids?: (string | number)[] | null; /** - * Create Time - * Format: date-time - * @description The time and date this item was created. + * Dataset IDs + * @description If from_history_id is set, this is an optional list of HDA 'hid's corresponding to workflow inputs when extracting a workflow from history. */ - create_time: string; + dataset_ids?: (string | number)[] | null; + /** Dry Run */ + dry_run?: boolean | null; /** - * History ID - * @description The encoded ID of the history associated with the invocation. - * @example 0123456789ABCDEF + * Exact Tools + * @description If False, allow running with less exact tool versions */ - history_id: string; + exact_tools?: boolean | null; /** - * ID - * @description The encoded ID of the workflow invocation. - * @example 0123456789ABCDEF + * Fill Defaults + * @description Fill in default tool state when updating, may change tool_state */ - id: string; + fill_defaults?: boolean | null; /** - * Model class - * @description The name of the database model class. - * @constant - * @enum {string} + * From History ID + * @description The ID of a history from which to extract a workflow. */ - model_class: "WorkflowInvocation"; + from_history_id?: string | null; /** - * Invocation state - * @description State of workflow invocation. + * From Path + * @description A path from which to import a workflow. */ - state: components["schemas"]["InvocationState"]; + from_path?: string | null; /** - * Update Time - * Format: date-time - * @description The last time and date this item was updated. + * From Tool Form + * @description If True, assume all tool state coming from generated form instead of potentially simpler json stored in DB/exported */ - update_time: string; + from_tool_form?: boolean | null; + /** Import Tools */ + import_tools?: boolean | null; + /** Importable */ + importable?: boolean | null; + /** Install Repository Dependencies */ + install_repository_dependencies?: boolean | null; + /** Install Resolver Dependencies */ + install_resolver_dependencies?: boolean | null; + /** Install Tool Dependencies */ + install_tool_dependencies?: boolean | null; /** - * UUID - * @description Universal unique identifier of the workflow invocation. + * Job IDs + * @description If from_history_id is set, this is an optional list of job IDs to include when extracting a workflow from history. */ - uuid?: string | null; + job_ids?: string[] | null; + /** New Tool Panel Section Label */ + new_tool_panel_section_label?: string | null; /** - * Workflow ID - * @description The encoded Workflow ID associated with the invocation. - * @example 0123456789ABCDEF + * Object ID + * @description If from_path is set, this is an optional object ID to include when importing a workflow from a path. */ - workflow_id: string; - }; - /** WorkflowInvocationElementView */ - WorkflowInvocationElementView: { + object_id?: string | null; + /** Publish */ + publish?: boolean | null; /** - * Create Time - * Format: date-time - * @description The time and date this item was created. + * Shared Workflow ID + * @description The ID of a shared workflow to import. */ - create_time: string; + shared_workflow_id?: string | null; + /** Shed Tool Conf */ + shed_tool_conf?: string | null; + /** Tool Panel Section Id */ + tool_panel_section_id?: string | null; + /** Tool Panel Section Mapping */ + tool_panel_section_mapping?: Record | null; /** - * History ID - * @description The encoded ID of the history associated with the invocation. - * @example 0123456789ABCDEF + * TRS Server + * @description If archive_source is set to 'trs_tool', this is the server of the Tool Registry Service (TRS) from which to import a workflow. */ - history_id: string; + trs_server?: string | null; /** - * ID - * @description The encoded ID of the workflow invocation. - * @example 0123456789ABCDEF + * TRS Tool ID + * @description If archive_source is set to 'trs_tool', this is the ID of the tool in the Tool Registry Service (TRS) from which to import a workflow. */ - id: string; + trs_tool_id?: string | null; /** - * Input step parameters - * @description Input step parameters of the workflow invocation. + * TRS URL + * @description If archive_source is set to 'trs_tool', this is the URL of the Tool Registry Service (TRS) from which to import a workflow. */ - input_step_parameters: { - [key: string]: components["schemas"]["InvocationInputParameter"]; + trs_url?: string | null; + /** + * TRS Version ID + * @description If archive_source is set to 'trs_tool', this is the version ID of the tool in the Tool Registry Service (TRS) from which to import a workflow. + */ + trs_version_id?: string | null; + /** Update Stored Workflow Attributes */ + update_stored_workflow_attributes?: boolean | null; + /** + * Workflow + * @description A dictionary containing information about a new workflow to import. + */ + workflow?: Record | null; + /** + * Workflow Name + * @description If from_history_id is set, this is the name of the workflow to create when extracting a workflow from history. + */ + workflow_name?: string | null; + }; + /** WorkflowDictEditorStep */ + WorkflowDictEditorStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * Config Form + * @description The configuration form for the step. + */ + config_form?: Record | null; + /** + * Content ID + * @description The content ID of the step. + */ + content_id?: string | null; + /** + * Errors + * @description An message indicating possible errors in the step. + */ + errors?: string[] | string | Record | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** + * Input Connections + * @description The input connections for the step. + */ + input_connections?: Record | null; + /** + * Inputs + * @description The inputs of the step. + */ + inputs?: Record[] | null; + /** + * Label + * @description The label of the step. + */ + label?: string | null; + /** + * Name + * @description The descriptive name of the module or step. + */ + name?: string | null; + /** + * Outputs + * @description The outputs of the step. + */ + outputs?: Record[] | null; + /** + * Position + * @description Layout position of this step in the graph + */ + position?: components["schemas"]["WorkflowStepLayoutPosition"] | null; + /** + * Post Job Actions + * @description Set of actions that will be run when the job finishes. + */ + post_job_actions?: + | components["schemas"]["PostJobAction"][] + | { + [key: string]: components["schemas"]["PostJobAction"]; + } + | null; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool State + * @description The state of the tool associated with the step + */ + tool_state?: Record | string | null; + /** + * Tool Version + * @description The version of the tool associated with the step. + */ + tool_version?: string | null; + /** + * Tooltip + * @description The tooltip for the step. + */ + tooltip?: string | null; + /** + * Type + * @description The type of the module that represents a step in the workflow. + */ + type: string; + /** + * UUID + * @description Universal unique identifier of the workflow. + */ + uuid?: string | null; + /** + * When + * @description The when expression for the step. + */ + when?: string | null; + /** + * Workflow Outputs + * @description Workflow outputs associated with this step. + */ + workflow_outputs?: components["schemas"]["WorkflowOutput"][] | null; + }; + /** WorkflowDictEditorSummary */ + WorkflowDictEditorSummary: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * Comments + * @description Comments on the workflow. + */ + comments: components["schemas"]["WorkflowCommentModel"][]; + /** + * Creator + * @description Additional information about the creator (or multiple creators) of this workflow. + */ + creator?: + | (components["schemas"]["Person"] | components["schemas"]["galaxy__schema__schema__Organization"])[] + | null; + /** + * License + * @description SPDX Identifier of the license associated with this workflow. + */ + license: string | null; + /** + * Name + * @description The name of the workflow. + */ + name: string; + /** + * Report + * @description The reports configuration for the workflow. + */ + report: Record; + /** + * Source Metadata + * @description Metadata about the source of the workflow + */ + source_metadata: Record | null; + /** + * Steps + * @description Information about all the steps of the workflow. + */ + steps: { + [key: string]: components["schemas"]["WorkflowDictEditorStep"]; + }; + /** + * Upgrade Messages + * @description Upgrade messages for each step in the workflow. + */ + upgrade_messages: { + [key: string]: + | string + | { + [key: string]: + | string + | { + [key: string]: string; + }; + }; + }; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version: number; + }; + /** WorkflowDictExportStep */ + WorkflowDictExportStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation?: string | null; + /** + * Content ID + * @description The content ID of the step. + */ + content_id?: string | null; + /** + * Errors + * @description An message indicating possible errors in the step. + */ + errors?: string[] | string | Record | null; + /** + * ID + * @description The identifier of the step. It matches the index order of the step inside the workflow. + */ + id: number; + /** In */ + in?: { + [key: string]: components["schemas"]["StepIn"]; + } | null; + /** + * Input Connections + * @description The input connections of the step. + */ + input_connections?: { + [key: string]: components["schemas"]["InputConnection"] | components["schemas"]["InputConnection"][]; + } | null; + /** + * Inputs + * @description The inputs of the step. + */ + inputs?: components["schemas"]["WorkflowDictExportStepInput"][] | null; + /** + * Label + * @description The label of the step. + */ + label?: string | null; + /** + * Name + * @description The descriptive name of the module or step. + */ + name: string; + /** + * Outputs + * @description The outputs of the step. + */ + outputs?: Record[] | null; + /** + * Position + * @description Layout position of this step in the graph + */ + position?: components["schemas"]["WorkflowStepLayoutPosition"] | null; + /** + * Post Job Actions + * @description Set of actions that will be run when the job finishes. + */ + post_job_actions?: + | components["schemas"]["PostJobAction"][] + | { + [key: string]: components["schemas"]["PostJobAction"]; + } + | null; + /** + * Sub Workflow + * @description Full information about the subworkflow associated with this step. + */ + subworkflow?: components["schemas"]["WorkflowDictExportSummary"] | null; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Representation + * @description The representation of the tool associated with the step. + */ + tool_representation?: Record | null; + /** + * Tool Shed Repository + * @description Information about the tool shed repository associated with the tool. + */ + tool_shed_repository?: components["schemas"]["ToolShedRepositorySummary"] | null; + /** + * Tool State + * @description The state of the tool associated with the step + */ + tool_state?: Record | string | null; + /** + * Tool Version + * @description The version of the tool associated with the step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of the module that represents a step in the workflow. + */ + type: string; + /** + * UUID + * Format: uuid4 + * @description Universal unique identifier of the workflow. + */ + uuid: string; + /** + * When + * @description The when expression for the step. + */ + when?: string | null; + /** + * Workflow Outputs + * @description Workflow outputs associated with this step. + */ + workflow_outputs?: components["schemas"]["WorkflowOutput"][] | null; + }; + /** WorkflowDictExportStepInput */ + WorkflowDictExportStepInput: { + /** + * Description + * @description The annotation or description of the input. + */ + description: string; + /** + * Name + * @description The name of the input. + */ + name: string; + }; + /** WorkflowDictExportSummary */ + WorkflowDictExportSummary: { + /** + * A Galaxy Workflow + * @description Whether this workflow is a Galaxy Workflow. + * @constant + * @enum {string} + */ + a_galaxy_workflow: "true"; + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation: string | null; + /** + * Comments + * @description Comments associated with the workflow. + */ + comments: Record[]; + /** + * Creator + * @description Additional information about the creator (or multiple creators) of this workflow. + */ + creator?: + | (components["schemas"]["Person"] | components["schemas"]["galaxy__schema__schema__Organization"])[] + | null; + /** + * Format Version + * @description The version of the workflow format being used. + * @constant + * @enum {string} + */ + "format-version": "0.1"; + /** + * License + * @description SPDX Identifier of the license associated with this workflow. + */ + license?: string | null; + /** + * Name + * @description The name of the workflow. + */ + name: string; + /** + * Report + * @description The configuration for generating a report for the workflow. + */ + report?: Record | null; + /** + * Source Metadata + * @description Metadata about the source of the workflow. + */ + source_metadata?: Record | null; + /** + * Steps + * @description Information about all the steps of the workflow. + */ + steps: { + [key: string]: components["schemas"]["WorkflowDictExportStep"]; + }; + /** + * Tags + * @description The tags associated with the workflow. + */ + tags: components["schemas"]["TagCollection"] | ""; + /** + * UUID + * @description The UUID (Universally Unique Identifier) of the workflow. + */ + uuid?: string | null; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version?: number | null; + }; + /** WorkflowDictFormat2Summary */ + WorkflowDictFormat2Summary: { + /** + * Class + * @description The class of the workflow. + * @constant + * @enum {string} + */ + class: "GalaxyWorkflow"; + /** + * Creator + * @description Additional information about the creator (or multiple creators) of this workflow. + */ + creator?: + | (components["schemas"]["Person"] | components["schemas"]["galaxy__schema__schema__Organization"])[] + | null; + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + doc?: string | null; + /** + * Inputs + * @description The inputs of the workflow. + */ + inputs: Record; + /** + * Label + * @description The label or name of the workflow. + */ + label?: string | null; + /** + * License + * @description SPDX Identifier of the license associated with this workflow. + */ + license?: string | null; + /** + * Outputs + * @description The outputs of the workflow. + */ + outputs: Record; + /** + * Release + * @description The release information for the workflow. + */ + release?: string | null; + /** + * Report + * @description The configuration for generating a report for the workflow. + */ + report?: Record | null; + /** + * Steps + * @description Information about all the steps of the workflow. + */ + steps: Record; + /** + * Tags + * @description The tags associated with the workflow. + */ + tags?: components["schemas"]["TagCollection"] | null; + /** + * UUID + * @description The UUID (Universally Unique Identifier) of the workflow. + */ + uuid?: string | null; + }; + /** WorkflowDictFormat2WrappedYamlSummary */ + WorkflowDictFormat2WrappedYamlSummary: { + /** + * YAML Content + * @description The content of the workflow in YAML . + */ + yaml_content: unknown; + }; + /** WorkflowDictPreviewStep */ + WorkflowDictPreviewStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation?: string | null; + /** + * Content ID + * @description The content ID of the step. + */ + content_id?: string | null; + /** + * Errors + * @description An message indicating possible errors in the step. + */ + errors?: string[] | string | Record | null; + /** + * Inputs + * @description The inputs of the step. + */ + inputs: Record[]; + /** + * Label + * @description The label of the step. + */ + label: string; + /** + * Order Index + * @description The order index of the step. + */ + order_index: number; + /** + * Outputs + * @description The outputs of the step. + */ + outputs?: Record[] | null; + /** + * Position + * @description Layout position of this step in the graph + */ + position?: components["schemas"]["WorkflowStepLayoutPosition"] | null; + /** + * Post Job Actions + * @description Set of actions that will be run when the job finishes. + */ + post_job_actions?: + | components["schemas"]["PostJobAction"][] + | { + [key: string]: components["schemas"]["PostJobAction"]; + } + | null; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool State + * @description The state of the tool associated with the step + */ + tool_state?: Record | string | null; + /** + * Tool Version + * @description The version of the tool associated with the step. + */ + tool_version?: string | null; + /** + * Type + * @description The type of the module that represents a step in the workflow. + */ + type: string; + /** + * When + * @description The when expression for the step. + */ + when?: string | null; + /** + * Workflow Outputs + * @description Workflow outputs associated with this step. + */ + workflow_outputs?: components["schemas"]["WorkflowOutput"][] | null; + }; + /** WorkflowDictPreviewSummary */ + WorkflowDictPreviewSummary: { + /** + * Name + * @description The name of the workflow. + */ + name: string; + /** + * Steps + * @description Information about all the steps of the workflow. + */ + steps: components["schemas"]["WorkflowDictPreviewStep"][]; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version: number; + }; + /** WorkflowDictRunStep */ + WorkflowDictRunStep: { + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation?: string | null; + /** + * Content ID + * @description The content ID of the step. + */ + content_id?: string | null; + /** + * Errors + * @description An message indicating possible errors in the step. + */ + errors?: string[] | string | Record | null; + /** + * Inputs + * @description The inputs of the step. + */ + inputs: Record[]; + /** + * Messages + * @description Upgrade messages for the step. + */ + messages?: string[] | null; + /** + * Output Connections + * @description The output connections of the step. + */ + output_connections: Record[]; + /** + * Outputs + * @description The outputs of the step. + */ + outputs?: Record[] | null; + /** + * Position + * @description Layout position of this step in the graph + */ + position?: components["schemas"]["WorkflowStepLayoutPosition"] | null; + /** + * Post Job Actions + * @description Set of actions that will be run when the job finishes. + */ + post_job_actions?: + | components["schemas"]["PostJobAction"][] + | { + [key: string]: components["schemas"]["PostJobAction"]; + } + | null; + /** + * Replacement Parameters + * @description Informal replacement parameters for the step. + */ + replacement_parameters?: (string | Record)[] | null; + /** + * Step Index + * @description The order index of the step. + */ + step_index: number; + /** + * Step Label + * @description The label of the step. + */ + step_label?: string | null; + /** + * Step Name + * @description The descriptive name of the module or step. + */ + step_name: string; + /** + * Step Type + * @description The type of the step. + */ + step_type: string; + /** + * Step Version + * @description The version of the step's module. + */ + step_version?: string | null; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool State + * @description The state of the tool associated with the step + */ + tool_state?: Record | string | null; + /** + * Tool Version + * @description The version of the tool associated with the step. + */ + tool_version?: string | null; + /** + * When + * @description The when expression for the step. + */ + when?: string | null; + /** + * Workflow Outputs + * @description Workflow outputs associated with this step. + */ + workflow_outputs?: components["schemas"]["WorkflowOutput"][] | null; + }; + /** WorkflowDictRunSummary */ + WorkflowDictRunSummary: { + /** + * Has Upgrade Messages + * @description Whether the workflow has upgrade messages. + */ + has_upgrade_messages: boolean; + /** + * History ID + * @description The encoded ID of the history associated with the workflow. + */ + history_id?: string | null; + /** + * ID + * @description The encoded ID of the stored workflow. + */ + id: string; + /** + * Name + * @description The name of the workflow. + */ + name: string; + /** + * Step Version Changes + * @description Version changes for the workflow steps. + */ + step_version_changes: (string | Record)[]; + /** + * Steps + * @description Information about all the steps of the workflow. + */ + steps: (components["schemas"]["WorkflowDictRunToolStep"] | components["schemas"]["WorkflowDictRunStep"])[]; + /** + * Version + * @description The version of the workflow represented by an incremental number. + */ + version: number; + /** + * Workflow Resource Parameters + * @description The resource parameters of the workflow. + */ + workflow_resource_parameters: Record | null; + }; + /** WorkflowDictRunToolStep */ + WorkflowDictRunToolStep: { + /** + * Action + * @description The action of the tool step. + */ + action: string; + /** + * Annotation + * @description An annotation to provide details or to help understand the purpose and usage of this item. + */ + annotation?: string | null; + /** + * Citations + * @description The citations of the tool step. + */ + citations: boolean; + /** + * Content ID + * @description The content ID of the step. + */ + content_id?: string | null; + /** + * Creator + * @description The creator of the tool step. + */ + creator?: string | null; + /** + * Description + * @description The description of the tool step. + */ + description: string; + /** + * Display + * @description Indicates if the tool step should be displayed. + */ + display: boolean; + /** + * EDAM Operations + * @description The EDAM operations of the tool step. + */ + edam_operations: string[]; + /** + * EDAM Topics + * @description The EDAM topics of the tool step. + */ + edam_topics: string[]; + /** + * Enctype + * @description The enctype of the tool step. + */ + enctype: string; + /** + * Errors + * @description An message indicating possible errors in the step. + */ + errors?: string[] | string | Record | null; + /** + * Form Style + * @description The form style of the tool step. + */ + form_style: string; + /** + * Help + * @description The help of the tool step. + */ + help: string; + /** + * Hidden + * @description The hidden status of the tool step. + */ + hidden: string; + /** + * History ID + * @description The ID of the history associated with the tool step. + */ + history_id: string; + /** + * ID + * @description The identifier of the tool step. + */ + id: string; + /** + * Inputs + * @description The inputs of the step. + */ + inputs: Record[]; + /** + * Is Workflow Compatible + * @description Indicates if the tool step is compatible with workflows. + */ + is_workflow_compatible: boolean; + /** + * Job ID + * @description The ID of the job associated with the tool step. + */ + job_id?: string | null; + /** + * Job Remap + * @description The remap of the job associated with the tool step. + */ + job_remap?: string | null; + /** + * Labels + * @description The labels of the tool step. + */ + labels: string[]; + /** + * License + * @description The license of the tool step. + */ + license?: string | null; + /** + * Link + * @description The link of the tool step. + */ + link?: string | null; + /** + * Message + * @description The message of the tool step. + */ + message: string; + /** + * Messages + * @description Upgrade messages for the step. + */ + messages?: string[] | null; + /** + * Method + * @description The method of the tool step. + */ + method: string; + /** + * Min Width + * @description The minimum width of the tool step. + */ + min_width?: unknown | null; + /** + * Model Class + * @description The model class of the tool step. + * @constant + * @enum {string} + */ + model_class: "tool"; + /** + * Name + * @description The name of the tool step. + */ + name: string; + /** + * Output Connections + * @description The output connections of the step. + */ + output_connections: Record[]; + /** + * Outputs + * @description The outputs of the step. + */ + outputs?: Record[] | null; + /** + * Panel Section ID + * @description The panel section ID of the tool step. + */ + panel_section_id: string; + /** + * Panel Section Name + * @description The panel section name of the tool step. + */ + panel_section_name: string; + /** + * Position + * @description Layout position of this step in the graph + */ + position?: components["schemas"]["WorkflowStepLayoutPosition"] | null; + /** + * Post Job Actions + * @description Set of actions that will be run when the job finishes. + */ + post_job_actions?: + | components["schemas"]["PostJobAction"][] + | { + [key: string]: components["schemas"]["PostJobAction"]; + } + | null; + /** + * Replacement Parameters + * @description Informal replacement parameters for the step. + */ + replacement_parameters?: (string | Record)[] | null; + /** + * Requirements + * @description The requirements of the tool step. + */ + requirements: string[]; + /** + * Sharable URL + * @description The sharable URL of the tool step. + */ + sharable_url?: string | null; + /** + * State Inputs + * @description The state inputs of the tool step. + */ + state_inputs: Record; + /** + * Step Index + * @description The order index of the step. + */ + step_index: number; + /** + * Step Label + * @description The label of the step. + */ + step_label?: string | null; + /** + * Step Name + * @description The descriptive name of the module or step. + */ + step_name: string; + /** + * Step Type + * @description The type of the step. + */ + step_type: string; + /** + * Step Version + * @description The version of the step's module. + */ + step_version?: string | null; + /** + * Target + * @description The target of the tool step. + */ + target?: unknown | null; + /** + * Tool Errors + * @description An message indicating possible errors in the tool step. + */ + tool_errors?: string | null; + /** + * Tool ID + * @description The unique name of the tool associated with this step. + */ + tool_id?: string | null; + /** + * Tool Shed Repository + * @description Information about the tool shed repository associated with the tool. + */ + tool_shed_repository?: components["schemas"]["ToolShedRepositorySummary"] | null; + /** + * Tool State + * @description The state of the tool associated with the step + */ + tool_state?: Record | string | null; + /** + * Tool Version + * @description The version of the tool associated with the step. + */ + tool_version?: string | null; + /** + * Version + * @description The version of the tool step. + */ + version: string; + /** + * Versions + * @description The versions of the tool step. + */ + versions: string[]; + /** + * Warnings + * @description The warnings of the tool step. + */ + warnings?: string | null; + /** + * When + * @description The when expression for the step. + */ + when?: string | null; + /** + * Workflow Outputs + * @description Workflow outputs associated with this step. + */ + workflow_outputs?: components["schemas"]["WorkflowOutput"][] | null; + /** + * XRefs + * @description The cross-references of the tool step. + */ + xrefs: string[]; + }; + /** WorkflowInput */ + WorkflowInput: { + /** + * Label + * @description Label of the input. + */ + label: string | null; + /** + * UUID + * @description Universal unique identifier of the input. + */ + uuid: string | null; + /** Value */ + value: unknown | null; + }; + /** WorkflowInvocationCollectionView */ + WorkflowInvocationCollectionView: { + /** + * Create Time + * Format: date-time + * @description The time and date this item was created. + */ + create_time: string; + /** + * History ID + * @description The encoded ID of the history associated with the invocation. + * @example 0123456789ABCDEF + */ + history_id: string; + /** + * ID + * @description The encoded ID of the workflow invocation. + * @example 0123456789ABCDEF + */ + id: string; + /** + * Model class + * @description The name of the database model class. + * @constant + * @enum {string} + */ + model_class: "WorkflowInvocation"; + /** + * Invocation state + * @description State of workflow invocation. + */ + state: components["schemas"]["InvocationState"]; + /** + * Update Time + * Format: date-time + * @description The last time and date this item was updated. + */ + update_time: string; + /** + * UUID + * @description Universal unique identifier of the workflow invocation. + */ + uuid?: string | null; + /** + * Workflow ID + * @description The encoded Workflow ID associated with the invocation. + * @example 0123456789ABCDEF + */ + workflow_id: string; + }; + /** WorkflowInvocationElementView */ + WorkflowInvocationElementView: { + /** + * 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"]; }; /** * Inputs @@ -17048,28 +18462,129 @@ export interface components { /** WorkflowInvocationStateSummary */ WorkflowInvocationStateSummary: { /** - * Id - * @example 0123456789ABCDEF + * Id + * @example 0123456789ABCDEF + */ + id: string; + /** + * @description The name of the database model class. (enum property replaced by openapi-typescript) + * @enum {string} + */ + model: "WorkflowInvocation"; + /** + * Populated State + * @description Indicates the general state of the elements in the dataset collection:- 'new': new dataset collection, unpopulated elements.- 'ok': collection elements populated (HDAs may or may not have errors).- 'failed': some problem populating, won't be populated. + */ + populated_state: components["schemas"]["DatasetCollectionPopulatedState"]; + /** + * States + * @description A dictionary of job states and the number of jobs in that state. + * @default {} + */ + states: { + [key: string]: number; + }; + }; + /** WorkflowOutput */ + WorkflowOutput: { + /** + * Label + * @description Label of the output. + */ + label?: string | null; + /** + * Output Name + * @description The name of the step output. + */ + output_name: string; + /** + * UUID + * @description Universal unique identifier of the output. + */ + uuid?: string | null; + }; + /** + * WorkflowStepLayoutPosition + * @description Position and dimensions of the workflow step represented by a box on the graph. + */ + WorkflowStepLayoutPosition: { + /** + * Bottom + * @description Position of the bottom of the box. + */ + bottom?: number | null; + /** + * Height + * @description Height of the box. + */ + height?: number | null; + /** + * Left + * @description Left margin or left-most position of the box. + */ + left: number; + /** + * Right + * @description Right margin or right-most position of the box. + */ + right?: number | null; + /** + * Top + * @description Position of the top of the box. + */ + top: number; + /** + * Width + * @description Width of the box. + */ + width?: number | null; + /** + * X + * @description Horizontal coordinate of the top right corner of the box. + */ + x?: number | null; + /** + * Y + * @description Vertical coordinate of the top right corner of the box. + */ + y?: number | null; + }; + /** WorkflowUpdatePayload */ + WorkflowUpdatePayload: { + /** + * Annotation + * @description Annotation for the workflow, if not present in payload, annotation defaults to existing annotation + */ + annotation?: string | null; + /** + * From Tool Form + * @description True iff encoded state coming in is encoded for the tool form. */ - id: string; + from_tool_form?: boolean | null; + /** Importable */ + importable?: boolean | null; /** - * @description The name of the database model class. (enum property replaced by openapi-typescript) - * @enum {string} + * Menu Entry + * @description Flag indicating if the workflow should appear in the user's menu, if not present, workflow menu entries are not modified */ - model: "WorkflowInvocation"; + menu_entry?: boolean | null; /** - * Populated State - * @description Indicates the general state of the elements in the dataset collection:- 'new': new dataset collection, unpopulated elements.- 'ok': collection elements populated (HDAs may or may not have errors).- 'failed': some problem populating, won't be populated. + * Name + * @description Name for the workflow, if not present in payload, name defaults to existing name */ - populated_state: components["schemas"]["DatasetCollectionPopulatedState"]; + name?: string | null; + /** Published */ + published?: boolean | null; /** - * States - * @description A dictionary of job states and the number of jobs in that state. - * @default {} + * Tags + * @description List containing list of tags to add to the workflow (overwriting existing tags), if not present, tags are not modified */ - states: { - [key: string]: number; - }; + tags?: string[] | null; + /** + * Workflow + * @description The json description of the workflow as would be produced by GET workflows//download or given to `POST workflows`. The workflow contents will be updated to target this. + */ + workflow?: unknown | null; }; /** WriteInvocationStoreToPayload */ WriteInvocationStoreToPayload: { @@ -32750,6 +34265,112 @@ export interface operations { }; }; }; + create_workflow_api_workflows_post: { + parameters: { + query?: never; + header?: { + /** @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. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WorkflowCreatePayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; + workflow_dict_api_workflows_download__workflow_id__get: { + parameters: { + query?: { + /** @description The history id to import a workflow from. */ + history_id?: string | null; + /** @description The default is 'export', which is meant to be used with workflow import endpoints. Other formats such as 'instance', 'editor', 'run' are tied to the GUI and should not be considered stable APIs. The default format for 'export' is specified by the admin with the `default_workflow_export_format` config option. Style can be specified as either 'ga' or 'format2' directly to be explicit about which format to download. */ + style?: string | null; + /** @description The format to download the workflow in. */ + format?: string | null; + /** @description The version of the workflow to fetch. */ + version?: number | null; + instance?: boolean | null; + }; + header?: { + /** @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. */ + "run-as"?: string | null; + }; + path: { + /** @description The encoded database identifier of the Stored Workflow. */ + workflow_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | components["schemas"]["WorkflowDictEditorSummary"] + | components["schemas"]["StoredWorkflowDetailed"] + | components["schemas"]["WorkflowDictRunSummary"] + | components["schemas"]["WorkflowDictPreviewSummary"] + | components["schemas"]["WorkflowDictFormat2Summary"] + | components["schemas"]["WorkflowDictExportSummary"] + | components["schemas"]["WorkflowDictFormat2WrappedYamlSummary"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; get_workflow_menu_api_workflows_menu_get: { parameters: { query?: { @@ -32800,6 +34421,51 @@ export interface operations { }; }; }; + set_workflow_menu_api_workflows_menu_put: { + parameters: { + query?: never; + header?: { + /** @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. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": components["schemas"]["SetWorkflowMenuPayload"] | null; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SetWorkflowMenuSummary"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; show_workflow_api_workflows__workflow_id__get: { parameters: { query?: { @@ -32850,6 +34516,56 @@ export interface operations { }; }; }; + update_workflow_api_workflows__workflow_id__put: { + parameters: { + query?: { + instance?: boolean | null; + }; + header?: { + /** @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. */ + "run-as"?: string | null; + }; + path: { + /** @description The encoded database identifier of the Stored Workflow. */ + workflow_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WorkflowUpdatePayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StoredWorkflowDetailed"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; delete_workflow_api_workflows__workflow_id__delete: { parameters: { query?: never; @@ -32985,6 +34701,67 @@ export interface operations { }; }; }; + workflow_dict_api_workflows__workflow_id__download_get: { + parameters: { + query?: { + /** @description The history id to import a workflow from. */ + history_id?: string | null; + /** @description The default is 'export', which is meant to be used with workflow import endpoints. Other formats such as 'instance', 'editor', 'run' are tied to the GUI and should not be considered stable APIs. The default format for 'export' is specified by the admin with the `default_workflow_export_format` config option. Style can be specified as either 'ga' or 'format2' directly to be explicit about which format to download. */ + style?: string | null; + /** @description The format to download the workflow in. */ + format?: string | null; + /** @description The version of the workflow to fetch. */ + version?: number | null; + instance?: boolean | null; + }; + header?: { + /** @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. */ + "run-as"?: string | null; + }; + path: { + /** @description The encoded database identifier of the Stored Workflow. */ + workflow_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | components["schemas"]["WorkflowDictEditorSummary"] + | components["schemas"]["StoredWorkflowDetailed"] + | components["schemas"]["WorkflowDictRunSummary"] + | components["schemas"]["WorkflowDictPreviewSummary"] + | components["schemas"]["WorkflowDictFormat2Summary"] + | components["schemas"]["WorkflowDictExportSummary"] + | components["schemas"]["WorkflowDictFormat2WrappedYamlSummary"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; enable_link_access_api_workflows__workflow_id__enable_link_access_put: { parameters: { query?: never; diff --git a/lib/galaxy/schema/schema.py b/lib/galaxy/schema/schema.py index 427777fa319d..a47d1fb63c0c 100644 --- a/lib/galaxy/schema/schema.py +++ b/lib/galaxy/schema/schema.py @@ -23,7 +23,6 @@ BeforeValidator, ConfigDict, Field, - Json, model_validator, RootModel, UUID4, @@ -2277,42 +2276,6 @@ class StoredWorkflowSummary(Model, WithModelClass): ) -class WorkflowInput(Model): - label: Optional[str] = Field( - ..., - title="Label", - description="Label of the input.", - ) - value: Optional[Any] = Field( - ..., - title="Value", - description="TODO", - ) - uuid: Optional[UUID4] = Field( - ..., - title="UUID", - description="Universal unique identifier of the input.", - ) - - -class WorkflowOutput(Model): - label: Optional[str] = Field( - None, - title="Label", - description="Label of the output.", - ) - output_name: str = Field( - ..., - title="Output Name", - description="The name assigned to the output.", - ) - uuid: Optional[UUID4] = Field( - None, - title="UUID", - description="Universal unique identifier of the output.", - ) - - class InputStep(Model): source_step: int = Field( ..., @@ -2451,201 +2414,9 @@ class Person(Creator): ) -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.") - - -class Output(Model): - name: str = Field(..., title="Name", description="The name of the output.") - type: str = Field(..., title="Type", description="The extension or type of output.") - - -class InputConnection(Model): - id: int = Field(..., title="ID", description="The identifier of the input.") - output_name: str = Field( - ..., - title="Output Name", - description="The name assigned to the output.", - ) - input_subworkflow_step_id: Optional[int] = Field( - None, - title="Input Subworkflow Step ID", - description="TODO", - ) - - -class WorkflowStepLayoutPosition(Model): - """Position and dimensions of the workflow step represented by a box on the graph.""" - - bottom: int = Field(..., title="Bottom", description="Position in pixels of the bottom of the box.") - top: int = Field(..., title="Top", description="Position in pixels of the top of the box.") - left: int = Field(..., title="Left", description="Left margin or left-most position of the box.") - right: int = Field(..., title="Right", description="Right margin or right-most position of the box.") - x: int = Field(..., title="X", description="Horizontal pixel coordinate of the top right corner of the box.") - y: int = Field(..., title="Y", description="Vertical pixel coordinate of the top right corner of the box.") - height: int = Field(..., title="Height", description="Height of the box in pixels.") - width: int = Field(..., title="Width", description="Width of the box in pixels.") - - InvocationsStateCounts = RootModel[Dict[str, int]] -class WorkflowStepToExportBase(Model): - id: int = Field( - ..., - title="ID", - description="The identifier of the step. It matches the index order of the step inside the workflow.", - ) - type: str = Field(..., title="Type", description="The type of workflow module.") - name: str = Field(..., title="Name", description="The descriptive name of the module or step.") - annotation: Optional[str] = AnnotationField - tool_id: Optional[str] = Field( # Duplicate of `content_id` or viceversa? - None, title="Tool ID", description="The unique name of the tool associated with this step." - ) - uuid: UUID4 = Field( - ..., - title="UUID", - description="Universal unique identifier of the workflow.", - ) - label: Optional[str] = Field( - None, - title="Label", - ) - inputs: List[Input] = Field( - ..., - title="Inputs", - description="TODO", - ) - outputs: List[Output] = Field( - ..., - title="Outputs", - description="TODO", - ) - input_connections: Dict[str, InputConnection] = Field( - {}, - title="Input Connections", - description="TODO", - ) - position: WorkflowStepLayoutPosition = Field( - ..., - title="Position", - description="Layout position of this step in the graph", - ) - workflow_outputs: List[WorkflowOutput] = Field( - [], title="Workflow Outputs", description="Workflow outputs associated with this step." - ) - - -class WorkflowStepToExport(WorkflowStepToExportBase): - content_id: Optional[str] = Field( # Duplicate of `tool_id` or viceversa? - None, title="Content ID", description="TODO" - ) - tool_version: Optional[str] = Field( - None, title="Tool Version", description="The version of the tool associated with this step." - ) - tool_state: Json = Field( - ..., - title="Tool State", - description="JSON string containing the serialized representation of the persistable state of the step.", - ) - errors: Optional[str] = Field( - None, - title="Errors", - description="An message indicating possible errors in the step.", - ) - - -class ToolShedRepositorySummary(Model): - name: str = Field( - ..., - title="Name", - description="The name of the repository.", - ) - owner: str = Field( - ..., - title="Owner", - description="The owner of the repository.", - ) - changeset_revision: str = Field( - ..., - title="Changeset Revision", - description="TODO", - ) - tool_shed: str = Field( - ..., - title="Tool Shed", - description="The Tool Shed base URL.", - ) - - -class PostJobAction(Model): - action_type: str = Field( - ..., - title="Action Type", - description="The type of action to run.", - ) - output_name: str = Field( - ..., - title="Output Name", - description="The name of the output that will be affected by the action.", - ) - action_arguments: Dict[str, Any] = Field( - ..., - title="Action Arguments", - description="Any additional arguments needed by the action.", - ) - - -class WorkflowToolStepToExport(WorkflowStepToExportBase): - tool_shed_repository: ToolShedRepositorySummary = Field( - ..., title="Tool Shed Repository", description="Information about the origin repository of this tool." - ) - post_job_actions: Dict[str, PostJobAction] = Field( - ..., title="Post-job Actions", description="Set of actions that will be run when the job finish." - ) - - -class SubworkflowStepToExport(WorkflowStepToExportBase): - subworkflow: "WorkflowToExport" = Field( - ..., title="Subworkflow", description="Full information about the subworkflow associated with this step." - ) - - -class WorkflowToExport(Model): - a_galaxy_workflow: str = Field( # Is this meant to be a bool instead? - "true", title="Galaxy Workflow", description="Whether this workflow is a Galaxy Workflow." - ) - format_version: str = Field( - "0.1", - alias="format-version", # why this field uses `-` instead of `_`? - title="Galaxy Workflow", - description="Whether this workflow is a Galaxy Workflow.", - ) - name: str = Field(..., title="Name", description="The name of the workflow.") - annotation: Optional[str] = AnnotationField - tags: TagCollection - uuid: Optional[UUID4] = Field( - None, - title="UUID", - description="Universal unique identifier 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."), - ) - 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." - ) - steps: Dict[int, Union[SubworkflowStepToExport, WorkflowToolStepToExport, WorkflowStepToExport]] = Field( - {}, title="Steps", description="A dictionary with information about all the steps of the workflow." - ) - - # Roles ----------------------------------------------------------------- RoleIdField = Annotated[EncodedDatabaseIdField, Field(title="ID", description="Encoded ID of the role")] diff --git a/lib/galaxy/schema/workflows.py b/lib/galaxy/schema/workflows.py index f679fd47818d..261bf615d520 100644 --- a/lib/galaxy/schema/workflows.py +++ b/lib/galaxy/schema/workflows.py @@ -10,8 +10,14 @@ from pydantic import ( Field, field_validator, + UUID4, +) +from typing_extensions import ( + Annotated, + Literal, ) +from galaxy.schema.fields import DecodedDatabaseIdField from galaxy.schema.schema import ( AnnotationField, InputDataCollectionStep, @@ -24,9 +30,179 @@ PreferredObjectStoreIdField, StoredWorkflowSummary, SubworkflowStep, + TagCollection, ToolStep, - WorkflowInput, ) +from galaxy.schema.workflow.comments import WorkflowCommentModel + +WorkflowAnnotationField = Annotated[ + Optional[str], + Field( + title="Annotation", + description="An annotation to provide details or to help understand the purpose and usage of this item.", + ), +] + +WorkflowCreator = Annotated[ + Optional[List[Union[Person, Organization]]], + Field( + None, + title="Creator", + description=("Additional information about the creator (or multiple creators) of this workflow."), + ), +] + + +class WorkflowDictExportStepInput(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.") + + +class InputConnection(Model): + id: int = Field(..., title="ID", description="The order index of the step.") + output_name: str = Field( + ..., + title="Output Name", + description="The output name of the input step that serves as the source for this connection.", + ) + input_subworkflow_step_id: Optional[int] = Field( + None, + title="Input Subworkflow Step ID", + ) + + +class WorkflowStepLayoutPosition(Model): + """Position and dimensions of the workflow step represented by a box on the graph.""" + + bottom: Optional[Union[int, float]] = Field( + None, + title="Bottom", + description="Position of the bottom of the box.", + ) + top: Union[int, float] = Field( + ..., + title="Top", + description="Position of the top of the box.", + ) + left: Union[int, float] = Field( + ..., + title="Left", + description="Left margin or left-most position of the box.", + ) + right: Optional[Union[int, float]] = Field( + None, + title="Right", + description="Right margin or right-most position of the box.", + ) + x: Optional[int] = Field( + None, + title="X", + description="Horizontal coordinate of the top right corner of the box.", + ) + y: Optional[int] = Field( + None, + title="Y", + description="Vertical coordinate of the top right corner of the box.", + ) + height: Optional[Union[int, float]] = Field( + None, + title="Height", + description="Height of the box.", + ) + width: Optional[Union[int, float]] = Field( + None, + title="Width", + description="Width of the box.", + ) + + +class WorkflowInput(Model): + label: Optional[str] = Field( + ..., + title="Label", + description="Label of the input.", + ) + value: Optional[Any] = Field( + ..., + title="Value", + ) + uuid: Optional[UUID4] = Field( + ..., + title="UUID", + description="Universal unique identifier of the input.", + ) + + +class WorkflowOutput(Model): + label: Optional[str] = Field( + None, + title="Label", + description="Label of the output.", + ) + output_name: str = Field( + ..., + title="Output Name", + description="The name of the step output.", + ) + uuid: Optional[UUID4] = Field( + None, + title="UUID", + description="Universal unique identifier of the output.", + ) + + +class ToolShedRepositorySummary(Model): + name: str = Field( + ..., + title="Name", + description="The name of the repository.", + ) + owner: str = Field( + ..., + title="Owner", + description="The owner of the repository.", + ) + changeset_revision: str = Field( + ..., + title="Changeset Revision", + ) + tool_shed: str = Field( + ..., + title="Tool Shed", + description="The Tool Shed base URL.", + ) + + +class PostJobAction(Model): + action_type: str = Field( + ..., + title="Action Type", + description="The type of action to run.", + ) + output_name: str = Field( + ..., + title="Output Name", + description="The name of the output that will be affected by the action.", + ) + action_arguments: Dict[str, Any] = Field( + ..., + title="Action Arguments", + description="Any additional arguments needed by the action.", + ) + short_str: Optional[str] = Field( + None, + title="Short String", + description="A short string representation of the action.", + ) + + +class StepIn(Model): + # TODO - add proper type and description - see _workflow_to_dict_export in manager for more details + # or class WorkflowStepInput + default: Any = Field( + ..., + title="Default", + ) class GetTargetHistoryPayload(Model): @@ -34,19 +210,16 @@ class GetTargetHistoryPayload(Model): 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.", ) @@ -77,7 +250,6 @@ class InvokeWorkflowPayload(GetTargetHistoryPayload): 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, @@ -119,27 +291,22 @@ def inputs_string_to_json(cls, v): 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, @@ -161,7 +328,6 @@ def inputs_string_to_json(cls, v): None, title="Effective Outputs", # lib/galaxy/workflow/run_request.py - see line 455 - description="TODO", ) preferred_intermediate_object_store_id: Optional[str] = Field( None, @@ -187,11 +353,7 @@ class StoredWorkflowDetailed(StoredWorkflowSummary): 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."), - ) + creator: WorkflowCreator steps: Dict[ int, Union[ @@ -228,3 +390,821 @@ class StoredWorkflowDetailed(StoredWorkflowSummary): title="Source Metadata", description="The source metadata of the workflow.", ) + + +class SetWorkflowMenuPayload(Model): + workflow_ids: Union[List[DecodedDatabaseIdField], DecodedDatabaseIdField] = Field( + ..., + title="Workflow IDs", + description="The list of workflow IDs to set the menu entry for.", + ) + + +class SetWorkflowMenuSummary(Model): + message: Optional[Any] = Field( + ..., + title="Message", + description="The message of the operation.", + ) + status: str = Field( + ..., + title="Status", + description="The status of the operation.", + ) + + +class WorkflowDictStepsBase(Model): + when: Optional[str] = Field( + None, + title="When", + description="The when expression for the step.", + ) + post_job_actions: Optional[Union[List[PostJobAction], Dict[str, PostJobAction]]] = Field( + None, + title="Post Job Actions", + description="Set of actions that will be run when the job finishes.", + ) + tool_version: Optional[str] = Field( + None, + title="Tool Version", + description="The version of the tool associated with the step.", + ) + errors: Optional[Union[List[str], str, Dict[str, Any]]] = Field( + None, + title="Errors", + description="An message indicating possible errors in the step.", + ) + tool_id: Optional[str] = Field( # Duplicate of `content_id` or viceversa? + None, + title="Tool ID", + description="The unique name of the tool associated with this step.", + ) + position: Optional[WorkflowStepLayoutPosition] = Field( + None, + title="Position", + description="Layout position of this step in the graph", + ) + outputs: Optional[List[Dict[str, Any]]] = Field( + None, + title="Outputs", + description="The outputs of the step.", + ) + tool_state: Optional[Union[Dict[str, Any], str]] = Field( + None, + title="Tool State", + description="The state of the tool associated with the step", + ) + content_id: Optional[str] = Field( + None, + title="Content ID", + description="The content ID of the step.", + ) + workflow_outputs: Optional[List[WorkflowOutput]] = Field( + None, + title="Workflow Outputs", + description="Workflow outputs associated with this step.", + ) + + +class WorkflowDictStepsExtendedBase(WorkflowDictStepsBase): + type: str = Field( + ..., + title="Type", + description="The type of the module that represents a step in the workflow.", + ) + label: Optional[str] = Field( + None, + title="Label", + description="The label of the step.", + ) + + +class WorkflowDictRunStep(WorkflowDictStepsBase): + inputs: List[Dict[str, Any]] = Field( + ..., + title="Inputs", + description="The inputs of the step.", + ) + replacement_parameters: Optional[List[Union[str, Dict[str, Any]]]] = Field( + None, + title="Replacement Parameters", + description="Informal replacement parameters for the step.", + ) + step_name: str = Field(..., title="Step Name", description="The descriptive name of the module or step.") + step_version: Optional[str] = Field( + None, + title="Step Version", + description="The version of the step's module.", + ) + step_index: int = Field( + ..., + title="Step Index", + description="The order index of the step.", + ) + output_connections: List[Dict[str, Any]] = Field( + ..., + title="Output Connections", + description="The output connections of the step.", + ) + annotation: WorkflowAnnotationField = None + messages: Optional[List[str]] = Field( + None, + title="Messages", + description="Upgrade messages for the step.", + ) + step_type: str = Field( + ..., + title="Step Type", + description="The type of the step.", + ) + step_label: Optional[str] = Field( + None, + title="Step Label", + description="The label of the step.", + ) + + +class WorkflowDictRunToolStep(WorkflowDictRunStep): + model_class: Literal["tool"] = Field( + ..., + title="Model Class", + description="The model class of the tool step.", + # description="The model class of the step, given it is a tool.", + ) + id: str = Field( + ..., + title="ID", + description="The identifier of the tool step.", + ) + name: str = Field( + ..., + title="Name", + description="The name of the tool step.", + ) + version: str = Field( + ..., + title="Version", + description="The version of the tool step.", + ) + description: str = Field( + ..., + title="Description", + description="The description of the tool step.", + ) + labels: List[str] = Field( + ..., + title="Labels", + description="The labels of the tool step.", + ) + edam_operations: List[str] = Field( + ..., + title="EDAM Operations", + description="The EDAM operations of the tool step.", + ) + edam_topics: List[str] = Field( + ..., + title="EDAM Topics", + description="The EDAM topics of the tool step.", + ) + hidden: str = Field( + ..., + title="Hidden", + description="The hidden status of the tool step.", + ) + is_workflow_compatible: bool = Field( + ..., + title="Is Workflow Compatible", + description="Indicates if the tool step is compatible with workflows.", + ) + xrefs: List[str] = Field( + ..., + title="XRefs", + description="The cross-references of the tool step.", + ) + panel_section_id: str = Field( + ..., + title="Panel Section ID", + description="The panel section ID of the tool step.", + ) + panel_section_name: str = Field( + ..., + title="Panel Section Name", + description="The panel section name of the tool step.", + ) + form_style: str = Field( + ..., + title="Form Style", + description="The form style of the tool step.", + ) + help: str = Field( + ..., + title="Help", + description="The help of the tool step.", + ) + citations: bool = Field( + ..., + title="Citations", + description="The citations of the tool step.", + ) + sharable_url: Optional[str] = Field( + None, + title="Sharable URL", + description="The sharable URL of the tool step.", + ) + message: str = Field( + ..., + title="Message", + description="The message of the tool step.", + ) + warnings: Optional[str] = Field( + None, + title="Warnings", + description="The warnings of the tool step.", + ) + versions: List[str] = Field( + ..., + title="Versions", + description="The versions of the tool step.", + ) + requirements: List[str] = Field( + ..., + title="Requirements", + description="The requirements of the tool step.", + ) + tool_errors: Optional[str] = Field( + None, + title="Tool Errors", + description="An message indicating possible errors in the tool step.", + ) + state_inputs: Dict[str, Any] = Field( + ..., + title="State Inputs", + description="The state inputs of the tool step.", + ) + job_id: Optional[str] = Field( + None, + title="Job ID", + description="The ID of the job associated with the tool step.", + ) + job_remap: Optional[str] = Field( + None, + title="Job Remap", + description="The remap of the job associated with the tool step.", + ) + history_id: str = Field( + ..., + title="History ID", + description="The ID of the history associated with the tool step.", + ) + display: bool = Field( + ..., + title="Display", + description="Indicates if the tool step should be displayed.", + ) + action: str = Field( + ..., + title="Action", + description="The action of the tool step.", + ) + license: Optional[str] = Field( + None, + title="License", + description="The license of the tool step.", + ) + creator: Optional[str] = Field( + None, + title="Creator", + description="The creator of the tool step.", + ) + method: str = Field( + ..., + title="Method", + description="The method of the tool step.", + ) + enctype: str = Field( + ..., + title="Enctype", + description="The enctype of the tool step.", + ) + tool_shed_repository: Optional[ToolShedRepositorySummary] = Field( + None, + title="Tool Shed Repository", + description="Information about the tool shed repository associated with the tool.", + ) + link: Optional[str] = Field( + None, + title="Link", + description="The link of the tool step.", + ) + # TODO - see lib/galaxy/tools/__init__.py - class Tool - to_dict for further typing + min_width: Optional[Any] = Field( + None, + title="Min Width", + description="The minimum width of the tool step.", + ) + # TODO - see lib/galaxy/tools/__init__.py - class Tool - to_dict for further typing + target: Optional[Any] = Field( + None, + title="Target", + description="The target of the tool step.", + ) + + +class WorkflowDictPreviewStep(WorkflowDictStepsExtendedBase): + order_index: int = Field( + ..., + title="Order Index", + description="The order index of the step.", + ) + annotation: WorkflowAnnotationField = None + label: str = Field( + ..., + title="Label", + description="The label of the step.", + ) + inputs: List[Dict[str, Any]] = Field( + ..., + title="Inputs", + description="The inputs of the step.", + ) + + +class WorkflowDictEditorStep(WorkflowDictStepsExtendedBase): + id: int = Field( + ..., + title="ID", + description="The identifier of the step. It matches the index order of the step inside the workflow.", + ) + name: Optional[str] = Field( + None, + title="Name", + description="The descriptive name of the module or step.", + ) + inputs: Optional[List[Dict[str, Any]]] = Field( + None, + title="Inputs", + description="The inputs of the step.", + ) + config_form: Optional[Dict[str, Any]] = Field( + None, + title="Config Form", + description="The configuration form for the step.", + ) + annotation: WorkflowAnnotationField + uuid: Optional[UUID4] = Field( + None, + title="UUID", + description="Universal unique identifier of the workflow.", + ) + tooltip: Optional[str] = Field( + None, + title="Tooltip", + description="The tooltip for the step.", + ) + input_connections: Optional[Dict[str, Any]] = Field( + None, + title="Input Connections", + description="The input connections for the step.", + ) + + +class WorkflowDictExportStep(WorkflowDictStepsExtendedBase): + id: int = Field( + ..., + title="ID", + description="The identifier of the step. It matches the index order of the step inside the workflow.", + ) + name: str = Field( + ..., + title="Name", + description="The descriptive name of the module or step.", + ) + uuid: UUID4 = Field( + ..., + title="UUID", + description="Universal unique identifier of the workflow.", + ) + annotation: WorkflowAnnotationField = None + tool_shed_repository: Optional[ToolShedRepositorySummary] = Field( + None, + title="Tool Shed Repository", + description="Information about the tool shed repository associated with the tool.", + ) + tool_representation: Optional[Dict[str, Any]] = Field( + None, + title="Tool Representation", + description="The representation of the tool associated with the step.", + ) + subworkflow: Optional["WorkflowDictExportSummary"] = Field( + None, + title="Sub Workflow", + description="Full information about the subworkflow associated with this step.", + ) + inputs: Optional[List[WorkflowDictExportStepInput]] = Field( + None, + title="Inputs", + description="The inputs of the step.", + ) + in_parameter: Optional[Dict[str, StepIn]] = Field(None, title="In", alias="in") + input_connections: Optional[Dict[str, Union[InputConnection, List[InputConnection]]]] = Field( + None, + title="Input Connections", + description="The input connections of the step.", + ) + + +class WorkflowDictBaseModel(Model): + name: str = Field( + ..., + title="Name", + description="The name of the workflow.", + ) + + +class WorkflowDictExtendedBaseModel(WorkflowDictBaseModel): + version: int = Field( + ..., + title="Version", + description="The version of the workflow represented by an incremental number.", + ) + + +class WorkflowDictPreviewSummary(WorkflowDictExtendedBaseModel): + steps: List[WorkflowDictPreviewStep] = Field( + ..., + title="Steps", + description="Information about all the steps of the workflow.", + ) + + +class WorkflowDictEditorSummary(WorkflowDictExtendedBaseModel): + upgrade_messages: Dict[int, Union[str, Dict[str, Union[str, Dict[str, str]]]]] = Field( + ..., + title="Upgrade Messages", + description="Upgrade messages for each step in the workflow.", + ) + # TODO - can this be modeled further? see manager method _workflow_to_dict_editor + report: Dict[str, Any] = Field( + ..., + title="Report", + description="The reports configuration for the workflow.", + ) + comments: List[WorkflowCommentModel] = Field( + ..., + title="Comments", + description="Comments on the workflow.", + ) + annotation: WorkflowAnnotationField + license: Optional[str] = Field( + ..., + title="License", + description="SPDX Identifier of the license associated with this workflow.", + ) + creator: WorkflowCreator + source_metadata: Optional[Dict[str, Any]] = Field( + ..., + title="Source Metadata", + description="Metadata about the source of the workflow", + ) + steps: Dict[int, WorkflowDictEditorStep] = Field( + ..., + title="Steps", + description="Information about all the steps of the workflow.", + ) + + +class WorkflowDictRunSummary(WorkflowDictExtendedBaseModel): + id: str = Field( + ..., + title="ID", + description="The encoded ID of the stored workflow.", + ) + history_id: Optional[str] = Field( + None, + title="History ID", + description="The encoded ID of the history associated with the workflow.", + ) + step_version_changes: List[Union[str, Dict[str, Any]]] = Field( + ..., + title="Step Version Changes", + description="Version changes for the workflow steps.", + ) + has_upgrade_messages: bool = Field( + ..., + title="Has Upgrade Messages", + description="Whether the workflow has upgrade messages.", + ) + workflow_resource_parameters: Optional[Dict[str, Any]] = Field( + ..., + title="Workflow Resource Parameters", + description="The resource parameters of the workflow.", + ) + steps: List[Union[WorkflowDictRunToolStep, WorkflowDictRunStep]] = Field( + ..., + title="Steps", + description="Information about all the steps of the workflow.", + ) + + +class WorkflowDictExportSummary(WorkflowDictBaseModel): + a_galaxy_workflow: Literal["true"] = Field( + # a_galaxy_workflow: str = Field( + ..., + title="A Galaxy Workflow", + description="Whether this workflow is a Galaxy Workflow.", + ) + version: Optional[int] = Field( + None, + title="Version", + description="The version of the workflow represented by an incremental number.", + ) + format_version: Literal["0.1"] = Field( + # format_version: str = Field( + ..., + alias="format-version", + title="Format Version", + description="The version of the workflow format being used.", + ) + annotation: WorkflowAnnotationField + tags: Union[TagCollection, Literal[""]] = Field( + ..., + title="Tags", + description="The tags associated with the workflow.", + ) + uuid: Optional[UUID4] = Field( + None, + title="UUID", + description="The UUID (Universally Unique Identifier) of the workflow.", + ) + comments: List[Dict[str, Any]] = Field( + ..., + title="Comments", + description="Comments associated with the workflow.", + ) + report: Optional[Dict[str, Any]] = Field( + None, + title="Report", + description="The configuration for generating a report for the workflow.", + ) + creator: WorkflowCreator + license: Optional[str] = Field( + None, + title="License", + description="SPDX Identifier of the license associated with this workflow.", + ) + source_metadata: Optional[Dict[str, Any]] = Field( + None, + title="Source Metadata", + description="Metadata about the source of the workflow.", + ) + steps: Dict[int, WorkflowDictExportStep] = Field( + ..., + title="Steps", + description="Information about all the steps of the workflow.", + ) + + +class WorkflowDictFormat2Summary(Model): + workflow_class: Literal["GalaxyWorkflow"] = Field( + ..., + title="Class", + description="The class of the workflow.", + alias="class", + ) + label: Optional[str] = Field( + None, + title="Label", + description="The label or name of the workflow.", + ) + creator: WorkflowCreator + license: Optional[str] = Field( + None, + title="License", + description="SPDX Identifier of the license associated with this workflow.", + ) + release: Optional[str] = Field( + None, + title="Release", + description="The release information for the workflow.", + ) + tags: Optional[TagCollection] = Field( + None, + title="Tags", + description="The tags associated with the workflow.", + ) + uuid: Optional[UUID4] = Field( + None, + title="UUID", + description="The UUID (Universally Unique Identifier) of the workflow.", + ) + report: Optional[Dict[str, Any]] = Field( + None, + title="Report", + description="The configuration for generating a report for the workflow.", + ) + inputs: Dict[str, Any] = Field( + ..., + title="Inputs", + description="The inputs of the workflow.", + ) + outputs: Dict[str, Any] = Field( + ..., + title="Outputs", + description="The outputs of the workflow.", + ) + # TODO - can be modeled further see manager method workflow_to_dict + steps: Dict[str, Any] = Field( + ..., + title="Steps", + description="Information about all the steps of the workflow.", + ) + doc: WorkflowAnnotationField = None + + +class WorkflowDictFormat2WrappedYamlSummary(Model): + # TODO What type is this? + yaml_content: Any = Field( + ..., + title="YAML Content", + # description="Safe and ordered dump of YAML to stream", + description="The content of the workflow in YAML .", + ) + + +class WorkflowUpdatePayload(Model): + workflow: Optional[Any] = Field( + None, + title="Workflow", + description="The json description of the workflow as would be produced by GET workflows//download or given to `POST workflows`. The workflow contents will be updated to target this.", + ) + name: Optional[str] = Field( + None, + title="Name", + description="Name for the workflow, if not present in payload, name defaults to existing name", + ) + annotation: Optional[str] = Field( + None, + title="Annotation", + description="Annotation for the workflow, if not present in payload, annotation defaults to existing annotation", + ) + menu_entry: Optional[bool] = Field( + None, + title="Menu Entry", + description="Flag indicating if the workflow should appear in the user's menu, if not present, workflow menu entries are not modified", + ) + tags: Optional[List[str]] = Field( + None, + title="Tags", + description="List containing list of tags to add to the workflow (overwriting existing tags), if not present, tags are not modified", + ) + from_tool_form: Optional[bool] = Field( + None, title="From Tool Form", description="True iff encoded state coming in is encoded for the tool form." + ) + published: Optional[bool] = Field( + None, + ) + importable: Optional[bool] = Field( + None, + ) + + +# TODO Think if these are really all optional +class WorkflowCreatePayload(Model): + # TODO - Description comes from previous endpoint + from_history_id: Optional[DecodedDatabaseIdField] = Field( + None, + title="From History ID", + description="The ID of a history from which to extract a workflow.", + ) + job_ids: Optional[List[DecodedDatabaseIdField]] = Field( + None, + title="Job IDs", + description="If from_history_id is set, this is an optional list of job IDs to include when extracting a workflow from history.", + ) + dataset_ids: Optional[List[Union[str, int]]] = Field( + None, + title="Dataset IDs", + description="If from_history_id is set, this is an optional list of HDA 'hid's corresponding to workflow inputs when extracting a workflow from history.", + ) + dataset_collection_ids: Optional[List[Union[str, int]]] = Field( + None, + title="Dataset Collection IDs", + description="If from_history_id is set, this is an optional list of HDCA 'hid's corresponding to workflow inputs when extracting a workflow from history.", + ) + workflow_name: Optional[str] = Field( + None, + title="Workflow Name", + description="If from_history_id is set, this is the name of the workflow to create when extracting a workflow from history.", + ) + # TODO - New descriptions added by me + archive_file: Optional[Any] = Field( + None, + title="Archive File", + description="A file containing a workflow archive to be imported.", + ) + archive_source: Optional[str] = Field( + None, + title="Archive Source", + description="A URL or file path pointing to a workflow archive to be imported.", + ) + from_path: Optional[str] = Field( + None, + title="From Path", + description="A path from which to import a workflow.", + ) + object_id: Optional[str] = Field( + None, + title="Object ID", + description="If from_path is set, this is an optional object ID to include when importing a workflow from a path.", + ) + shared_workflow_id: Optional[str] = Field( + None, + title="Shared Workflow ID", + description="The ID of a shared workflow to import.", + ) + trs_url: Optional[str] = Field( + None, + title="TRS URL", + description="If archive_source is set to 'trs_tool', this is the URL of the Tool Registry Service (TRS) from which to import a workflow.", + ) + trs_server: Optional[str] = Field( + None, + title="TRS Server", + description="If archive_source is set to 'trs_tool', this is the server of the Tool Registry Service (TRS) from which to import a workflow.", + ) + trs_tool_id: Optional[str] = Field( + None, + title="TRS Tool ID", + description="If archive_source is set to 'trs_tool', this is the ID of the tool in the Tool Registry Service (TRS) from which to import a workflow.", + ) + trs_version_id: Optional[str] = Field( + None, + title="TRS Version ID", + description="If archive_source is set to 'trs_tool', this is the version ID of the tool in the Tool Registry Service (TRS) from which to import a workflow.", + ) + workflow: Optional[Dict[str, Any]] = Field( + None, + title="Workflow", + description="A dictionary containing information about a new workflow to import.", + ) + + @field_validator("workflow", "dataset_ids", "dataset_collection_ids", "job_ids", mode="before") + @classmethod + def decode_json(cls, v): + if isinstance(v, str): + return json.loads(v) + return v + + # TODO - these fields are only used, when creating a WorkflowCreateOptions + # Descriptions are taken from the WorkflowCreateOptions class + fill_defaults: Optional[bool] = Field( + None, + description="Fill in default tool state when updating, may change tool_state", + ) + from_tool_form: Optional[bool] = Field( + default=None, + description="If True, assume all tool state coming from generated form instead of potentially simpler json stored in DB/exported", + ) + exact_tools: Optional[bool] = Field( + default=None, description="If False, allow running with less exact tool versions" + ) + update_stored_workflow_attributes: Optional[bool] = Field( + None, + ) + allow_missing_tools: Optional[bool] = Field( + None, + ) + dry_run: Optional[bool] = Field( + None, + ) + import_tools: Optional[bool] = Field( + None, + ) + publish: Optional[bool] = Field( + None, + ) + importable: Optional[bool] = Field( + None, + ) + install_repository_dependencies: Optional[bool] = Field( + None, + ) + install_resolver_dependencies: Optional[bool] = Field( + None, + ) + install_tool_dependencies: Optional[bool] = Field( + None, + ) + new_tool_panel_section_label: Optional[str] = Field( + None, + ) + tool_panel_section_id: Optional[str] = Field( + None, + ) + shed_tool_conf: Optional[str] = Field( + None, + ) + # TODO Type further + tool_panel_section_mapping: Optional[Dict[Any, Any]] = Field( + None, + ) diff --git a/lib/galaxy/webapps/base/controller.py b/lib/galaxy/webapps/base/controller.py index 83c3ae620560..1274a8a00603 100644 --- a/lib/galaxy/webapps/base/controller.py +++ b/lib/galaxy/webapps/base/controller.py @@ -1114,10 +1114,13 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations): slug_builder = SlugBuilder() - def get_stored_workflow(self, trans, id, check_ownership=True, check_accessible=False): + def get_stored_workflow(self, trans, id, check_ownership=True, check_accessible=False, app_by_trans=False): """Get a StoredWorkflow from the database by id, verifying ownership.""" # Load workflow from database - workflow_contents_manager = workflows.WorkflowsManager(self.app) + if app_by_trans: + workflow_contents_manager = workflows.WorkflowsManager(trans.app) + else: + workflow_contents_manager = workflows.WorkflowsManager(self.app) workflow = workflow_contents_manager.get_stored_workflow(trans=trans, workflow_id=id) if not workflow: diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 5de6e9d9b47c..63ddae7b824d 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -2,9 +2,7 @@ API operations for Workflows """ -import json import logging -import os from io import BytesIO from typing import ( Any, @@ -21,7 +19,6 @@ Response, status, ) -from gxformat2.yaml import ordered_dump from pydantic import ( UUID1, UUID4, @@ -29,27 +26,15 @@ from starlette.responses import StreamingResponse from typing_extensions import Annotated -from galaxy import ( - exceptions, - model, - util, -) -from galaxy.files.uris import ( - stream_url_to_str, - validate_uri_access, -) +from galaxy import exceptions from galaxy.managers.context import ( ProvidesHistoryContext, ProvidesUserContext, ) from galaxy.managers.workflows import ( - MissingToolsException, RefactorRequest, RefactorResponse, - WorkflowCreateOptions, - WorkflowUpdateOptions, ) -from galaxy.model.base import transaction from galaxy.model.item_attrs import UsesAnnotations from galaxy.schema.fields import DecodedDatabaseIdField from galaxy.schema.invocation import ( @@ -78,21 +63,25 @@ ) from galaxy.schema.workflows import ( InvokeWorkflowPayload, + SetWorkflowMenuPayload, + SetWorkflowMenuSummary, StoredWorkflowDetailed, + WorkflowCreatePayload, + WorkflowDictEditorSummary, + WorkflowDictExportSummary, + WorkflowDictFormat2Summary, + WorkflowDictFormat2WrappedYamlSummary, + WorkflowDictPreviewSummary, + WorkflowDictRunSummary, + WorkflowUpdatePayload, ) from galaxy.structured_app import StructuredApp -from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager from galaxy.tools import recommendations from galaxy.tools.parameters import populate_state from galaxy.tools.parameters.workflow_utils import workflow_building_modes -from galaxy.web import ( - expose_api, - expose_api_raw_anonymous_and_sessionless, - format_return_as_json, -) +from galaxy.web import expose_api from galaxy.webapps.base.controller import ( SharableMixin, - url_for, UsesStoredWorkflowMixin, ) from galaxy.webapps.base.webapp import GalaxyWebTransaction @@ -119,7 +108,6 @@ WorkflowIndexPayload, WorkflowsService, ) -from galaxy.workflow.extract import extract_workflow from galaxy.workflow.modules import module_factory log = logging.getLogger(__name__) @@ -144,232 +132,6 @@ def __init__(self, app: StructuredApp): self.workflow_contents_manager = app.workflow_contents_manager self.tool_recommendations = recommendations.ToolRecommendations() - @expose_api - def set_workflow_menu(self, trans: GalaxyWebTransaction, payload=None, **kwd): - """ - Save workflow menu to be shown in the tool panel - PUT /api/workflows/menu - """ - payload = payload or {} - user = trans.user - workflow_ids = payload.get("workflow_ids") - if workflow_ids is None: - workflow_ids = [] - elif not isinstance(workflow_ids, list): - workflow_ids = [workflow_ids] - workflow_ids_decoded = [] - # Decode the encoded workflow ids - for ids in workflow_ids: - workflow_ids_decoded.append(trans.security.decode_id(ids)) - session = trans.sa_session - # This explicit remove seems like a hack, need to figure out - # how to make the association do it automatically. - for m in user.stored_workflow_menu_entries: - session.delete(m) - user.stored_workflow_menu_entries = [] - # To ensure id list is unique - seen_workflow_ids = set() - for wf_id in workflow_ids_decoded: - if wf_id in seen_workflow_ids: - continue - else: - seen_workflow_ids.add(wf_id) - m = model.StoredWorkflowMenuEntry() - m.stored_workflow = session.get(model.StoredWorkflow, wf_id) - - user.stored_workflow_menu_entries.append(m) - with transaction(session): - session.commit() - message = "Menu updated." - trans.set_message(message) - return {"message": message, "status": "done"} - - @expose_api - def create(self, trans: GalaxyWebTransaction, payload=None, **kwd): - """ - POST /api/workflows - - Create workflows in various ways. - - :param from_history_id: Id of history to extract a workflow from. - :type from_history_id: str - - :param job_ids: If from_history_id is set - optional list of jobs to include when extracting a workflow from history - :type job_ids: str - - :param dataset_ids: If from_history_id is set - optional list of HDA "hid"s corresponding to workflow inputs when extracting a workflow from history - :type dataset_ids: str - - :param dataset_collection_ids: If from_history_id is set - optional list of HDCA "hid"s corresponding to workflow inputs when extracting a workflow from history - :type dataset_collection_ids: str - - :param workflow_name: If from_history_id is set - name of the workflow to create when extracting a workflow from history - :type workflow_name: str - - """ - ways_to_create = { - "archive_file", - "archive_source", - "from_history_id", - "from_path", - "shared_workflow_id", - "workflow", - } - - if trans.user_is_bootstrap_admin: - raise exceptions.RealUserRequiredException("Only real users can create or run workflows.") - - if payload is None or len(ways_to_create.intersection(payload)) == 0: - message = f"One parameter among - {', '.join(ways_to_create)} - must be specified" - raise exceptions.RequestParameterMissingException(message) - - if len(ways_to_create.intersection(payload)) > 1: - message = f"Only one parameter among - {', '.join(ways_to_create)} - must be specified" - raise exceptions.RequestParameterInvalidException(message) - - if "archive_source" in payload or "archive_file" in payload: - archive_source = payload.get("archive_source") - archive_file = payload.get("archive_file") - archive_data = None - if archive_source: - validate_uri_access(archive_source, trans.user_is_admin, trans.app.config.fetch_url_allowlist_ips) - if archive_source.startswith("file://"): - workflow_src = {"src": "from_path", "path": archive_source[len("file://") :]} - payload["workflow"] = workflow_src - return self.__api_import_new_workflow(trans, payload, **kwd) - elif archive_source == "trs_tool": - server = None - trs_tool_id = None - trs_version_id = None - import_source = None - if "trs_url" in payload: - parts = self.app.trs_proxy.match_url(payload["trs_url"]) - if parts: - server = self.app.trs_proxy.server_from_url(parts["trs_base_url"]) - trs_tool_id = parts["tool_id"] - trs_version_id = parts["version_id"] - payload["trs_tool_id"] = trs_tool_id - payload["trs_version_id"] = trs_version_id - else: - raise exceptions.MessageException("Invalid TRS URL.") - else: - trs_server = payload.get("trs_server") - server = self.app.trs_proxy.get_server(trs_server) - trs_tool_id = payload.get("trs_tool_id") - trs_version_id = payload.get("trs_version_id") - - archive_data = server.get_version_descriptor(trs_tool_id, trs_version_id) - else: - try: - archive_data = stream_url_to_str( - archive_source, trans.app.file_sources, prefix="gx_workflow_download" - ) - import_source = "URL" - except Exception: - raise exceptions.MessageException(f"Failed to open URL '{archive_source}'.") - elif hasattr(archive_file, "file"): - uploaded_file = archive_file.file - uploaded_file_name = uploaded_file.name - if os.path.getsize(os.path.abspath(uploaded_file_name)) > 0: - archive_data = util.unicodify(uploaded_file.read()) - import_source = "uploaded file" - else: - raise exceptions.MessageException("You attempted to upload an empty file.") - else: - raise exceptions.MessageException("Please provide a URL or file.") - return self.__api_import_from_archive(trans, archive_data, import_source, payload=payload) - - if "from_history_id" in payload: - from_history_id = payload.get("from_history_id") - from_history_id = self.decode_id(from_history_id) - history = self.history_manager.get_accessible(from_history_id, trans.user, current_history=trans.history) - - job_ids = [self.decode_id(_) for _ in payload.get("job_ids", [])] - dataset_ids = payload.get("dataset_ids", []) - dataset_collection_ids = payload.get("dataset_collection_ids", []) - workflow_name = payload["workflow_name"] - stored_workflow = extract_workflow( - trans=trans, - user=trans.user, - history=history, - job_ids=job_ids, - dataset_ids=dataset_ids, - dataset_collection_ids=dataset_collection_ids, - workflow_name=workflow_name, - ) - item = stored_workflow.to_dict(value_mapper={"id": trans.security.encode_id}) - item["url"] = url_for("workflow", id=item["id"]) - return item - - if "from_path" in payload: - from_path = payload.get("from_path") - object_id = payload.get("object_id") - workflow_src = {"src": "from_path", "path": from_path} - if object_id is not None: - workflow_src["object_id"] = object_id - payload["workflow"] = workflow_src - return self.__api_import_new_workflow(trans, payload, **kwd) - - if "shared_workflow_id" in payload: - workflow_id = payload["shared_workflow_id"] - return self.__api_import_shared_workflow(trans, workflow_id, payload) - - if "workflow" in payload: - return self.__api_import_new_workflow(trans, payload, **kwd) - - # This was already raised above, but just in case... - raise exceptions.RequestParameterMissingException("No method for workflow creation supplied.") - - @expose_api_raw_anonymous_and_sessionless - def workflow_dict(self, trans: GalaxyWebTransaction, workflow_id, **kwd): - """ - GET /api/workflows/{encoded_workflow_id}/download - - Returns a selected workflow. - - :type style: str - :param style: Style of export. The default is 'export', which is the meant to be used - with workflow import endpoints. Other formats such as 'instance', 'editor', - 'run' are more tied to the GUI and should not be considered stable APIs. - The default format for 'export' is specified by the - admin with the `default_workflow_export_format` config - option. Style can be specified as either 'ga' or 'format2' directly - to be explicit about which format to download. - - :param instance: true if fetch by Workflow ID instead of StoredWorkflow id, false - by default. - :type instance: boolean - """ - stored_workflow = self.__get_stored_accessible_workflow(trans, workflow_id, **kwd) - - style = kwd.get("style", "export") - download_format = kwd.get("format") - version = kwd.get("version") - history = None - if history_id := kwd.get("history_id"): - history = self.history_manager.get_accessible( - self.decode_id(history_id), trans.user, current_history=trans.history - ) - ret_dict = self.workflow_contents_manager.workflow_to_dict( - trans, stored_workflow, style=style, version=version, history=history - ) - if download_format == "json-download": - sname = stored_workflow.name - sname = "".join(c in util.FILENAME_VALID_CHARS and c or "_" for c in sname)[0:150] - if ret_dict.get("format-version", None) == "0.1": - extension = "ga" - else: - extension = "gxwf.json" - trans.response.headers["Content-Disposition"] = ( - f'attachment; filename="Galaxy-Workflow-{sname}.{extension}"' - ) - trans.response.set_content_type("application/galaxy-archive") - - if style == "format2" and download_format != "json-download": - return ordered_dump(ret_dict) - else: - return format_return_as_json(ret_dict, pretty=True) - @expose_api def import_new_workflow_deprecated(self, trans: GalaxyWebTransaction, payload, **kwd): """ @@ -382,146 +144,7 @@ def import_new_workflow_deprecated(self, trans: GalaxyWebTransaction, payload, * Deprecated in favor to POST /api/workflows with encoded 'workflow' in payload the same way. """ - return self.__api_import_new_workflow(trans, payload, **kwd) - - @expose_api - def update(self, trans: GalaxyWebTransaction, id, payload, **kwds): - """ - PUT /api/workflows/{id} - - Update 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 any or all the - - :workflow: - - the json description of the workflow as would be - produced by GET workflows//download or - given to `POST workflows` - - The workflow contents will be updated to target this. - - :name: - - optional string name for the workflow, if not present in payload, - name defaults to existing name - - :annotation: - - optional string annotation for the workflow, if not present in payload, - annotation defaults to existing annotation - - :menu_entry: - - optional boolean marking if the workflow should appear in the user\'s menu, - if not present, workflow menu entries are not modified - - :tags: - - optional list containing list of tags to add to the workflow (overwriting - existing tags), if not present, tags are not modified - - :from_tool_form: - - True iff encoded state coming in is encoded for the tool form. - - - :rtype: dict - :returns: serialized version of the workflow - """ - stored_workflow = self.__get_stored_workflow(trans, id, **kwds) - workflow_dict = payload.get("workflow", {}) - workflow_dict.update({k: v for k, v in payload.items() if k not in workflow_dict}) - if workflow_dict: - require_flush = False - raw_workflow_description = self.__normalize_workflow(trans, workflow_dict) - workflow_dict = raw_workflow_description.as_dict - new_workflow_name = workflow_dict.get("name") - old_workflow = stored_workflow.latest_workflow - name_updated = new_workflow_name and new_workflow_name != stored_workflow.name - steps_updated = "steps" in workflow_dict - if name_updated and not steps_updated: - sanitized_name = new_workflow_name or old_workflow.name - if not sanitized_name: - raise exceptions.MessageException("Workflow must have a valid name.") - workflow = old_workflow.copy(user=trans.user) - workflow.stored_workflow = stored_workflow - workflow.name = sanitized_name - stored_workflow.name = sanitized_name - stored_workflow.latest_workflow = workflow - trans.sa_session.add(workflow, stored_workflow) - require_flush = True - - if "hidden" in workflow_dict and stored_workflow.hidden != workflow_dict["hidden"]: - stored_workflow.hidden = workflow_dict["hidden"] - require_flush = True - - if "published" in workflow_dict and stored_workflow.published != workflow_dict["published"]: - stored_workflow.published = workflow_dict["published"] - require_flush = True - - if "importable" in workflow_dict and stored_workflow.importable != workflow_dict["importable"]: - stored_workflow.importable = workflow_dict["importable"] - require_flush = True - - if "annotation" in workflow_dict and not steps_updated: - newAnnotation = workflow_dict["annotation"] - self.add_item_annotation(trans.sa_session, trans.user, stored_workflow, newAnnotation) - require_flush = True - - if "menu_entry" in workflow_dict or "show_in_tool_panel" in workflow_dict: - show_in_panel = workflow_dict.get("menu_entry") or workflow_dict.get("show_in_tool_panel") - stored_workflow_menu_entries = trans.user.stored_workflow_menu_entries - decoded_id = trans.security.decode_id(id) - if show_in_panel: - workflow_ids = [wf.stored_workflow_id for wf in stored_workflow_menu_entries] - if decoded_id not in workflow_ids: - menu_entry = model.StoredWorkflowMenuEntry() - menu_entry.stored_workflow = stored_workflow - stored_workflow_menu_entries.append(menu_entry) - trans.sa_session.add(menu_entry) - require_flush = True - else: - # remove if in list - entries = {x.stored_workflow_id: x for x in stored_workflow_menu_entries} - if decoded_id in entries: - stored_workflow_menu_entries.remove(entries[decoded_id]) - require_flush = True - # set tags - if "tags" in workflow_dict: - trans.tag_handler.set_tags_from_list( - user=trans.user, - item=stored_workflow, - new_tags_list=workflow_dict["tags"], - ) - - if require_flush: - with transaction(trans.sa_session): - trans.sa_session.commit() - - if "steps" in workflow_dict or "comments" in workflow_dict: - try: - workflow_update_options = WorkflowUpdateOptions(**payload) - workflow, errors = self.workflow_contents_manager.update_workflow_from_raw_description( - trans, - stored_workflow, - raw_workflow_description, - workflow_update_options, - ) - except MissingToolsException: - raise exceptions.MessageException( - "This workflow contains missing tools. It cannot be saved until they have been removed from the workflow or installed." - ) - - else: - message = "Updating workflow requires dictionary containing 'workflow' attribute with new JSON description." - raise exceptions.RequestParameterInvalidException(message) - return self.workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style="instance") + return self.service._api_import_new_workflow(trans, payload, **kwd) @expose_api def build_module(self, trans: GalaxyWebTransaction, payload=None): @@ -577,63 +200,6 @@ def get_tool_predictions(self, trans: ProvidesUserContext, payload, **kwd): # # -- Helper methods -- # - def __api_import_from_archive(self, trans: GalaxyWebTransaction, archive_data, source=None, payload=None): - payload = payload or {} - try: - data = json.loads(archive_data) - except Exception: - if "GalaxyWorkflow" in archive_data: - data = {"yaml_content": archive_data} - else: - raise exceptions.MessageException("The data content does not appear to be a valid workflow.") - if not data: - raise exceptions.MessageException("The data content is missing.") - raw_workflow_description = self.__normalize_workflow(trans, data) - workflow_create_options = WorkflowCreateOptions(**payload) - workflow, missing_tool_tups = self._workflow_from_dict( - trans, raw_workflow_description, workflow_create_options, source=source - ) - workflow_id = workflow.id - workflow = workflow.latest_workflow - - response = { - "message": f"Workflow '{workflow.name}' imported successfully.", - "status": "success", - "id": trans.security.encode_id(workflow_id), - } - if workflow.has_errors: - response["message"] = "Imported, but some steps in this workflow have validation errors." - response["status"] = "error" - elif len(workflow.steps) == 0: - response["message"] = "Imported, but this workflow has no steps." - response["status"] = "error" - elif workflow.has_cycles: - response["message"] = "Imported, but this workflow contains cycles." - response["status"] = "error" - return response - - def __api_import_new_workflow(self, trans: GalaxyWebTransaction, payload, **kwd): - data = payload["workflow"] - raw_workflow_description = self.__normalize_workflow(trans, data) - workflow_create_options = WorkflowCreateOptions(**payload) - workflow, missing_tool_tups = self._workflow_from_dict( - trans, - raw_workflow_description, - workflow_create_options, - ) - # galaxy workflow newly created id - workflow_id = workflow.id - # api encoded, id - encoded_id = trans.security.encode_id(workflow_id) - item = workflow.to_dict(value_mapper={"id": trans.security.encode_id}) - item["annotations"] = [x.annotation for x in workflow.annotations] - item["url"] = url_for("workflow", id=encoded_id) - item["owner"] = workflow.user.username - item["number_of_steps"] = len(workflow.latest_workflow.steps) - return item - - def __normalize_workflow(self, trans: GalaxyWebTransaction, as_dict): - return self.workflow_contents_manager.normalize_workflow_format(trans, as_dict) @expose_api def import_shared_workflow_deprecated(self, trans: GalaxyWebTransaction, payload, **kwd): @@ -650,97 +216,7 @@ def import_shared_workflow_deprecated(self, trans: GalaxyWebTransaction, payload workflow_id = payload.get("workflow_id", None) if workflow_id is None: raise exceptions.ObjectAttributeMissingException("Missing required parameter 'workflow_id'.") - self.__api_import_shared_workflow(trans, workflow_id, payload) - - def __api_import_shared_workflow(self, trans: GalaxyWebTransaction, workflow_id, payload, **kwd): - try: - stored_workflow = self.get_stored_workflow(trans, workflow_id, check_ownership=False) - except Exception: - raise exceptions.ObjectNotFound("Malformed workflow id specified.") - if stored_workflow.importable is False: - raise exceptions.ItemAccessibilityException( - "The owner of this workflow has disabled imports via this link." - ) - elif stored_workflow.deleted: - raise exceptions.ItemDeletionException("You can't import this workflow because it has been deleted.") - imported_workflow = self._import_shared_workflow(trans, stored_workflow) - item = imported_workflow.to_dict(value_mapper={"id": trans.security.encode_id}) - encoded_id = trans.security.encode_id(imported_workflow.id) - item["url"] = url_for("workflow", id=encoded_id) - return item - - def _workflow_from_dict(self, trans, data, workflow_create_options, source=None): - """Creates a workflow from a dict. - - Created workflow is stored in the database and returned. - """ - publish = workflow_create_options.publish - importable = workflow_create_options.is_importable - if publish and not importable: - raise exceptions.RequestParameterInvalidException("Published workflow must be importable.") - - workflow_contents_manager = self.app.workflow_contents_manager - raw_workflow_description = workflow_contents_manager.ensure_raw_description(data) - created_workflow = workflow_contents_manager.build_workflow_from_raw_description( - trans, - raw_workflow_description, - workflow_create_options, - source=source, - ) - if importable: - self._make_item_accessible(trans.sa_session, created_workflow.stored_workflow) - with transaction(trans.sa_session): - trans.sa_session.commit() - - self._import_tools_if_needed(trans, workflow_create_options, raw_workflow_description) - return created_workflow.stored_workflow, created_workflow.missing_tools - - def _import_tools_if_needed(self, trans, workflow_create_options, raw_workflow_description): - if not workflow_create_options.import_tools: - return - - if not trans.user_is_admin: - raise exceptions.AdminRequiredException() - - data = raw_workflow_description.as_dict - - tools = {} - for key in data["steps"]: - item = data["steps"][key] - if item is not None: - if "tool_shed_repository" in item: - tool_shed_repository = item["tool_shed_repository"] - if ( - "owner" in tool_shed_repository - and "changeset_revision" in tool_shed_repository - and "name" in tool_shed_repository - and "tool_shed" in tool_shed_repository - ): - toolstr = ( - tool_shed_repository["owner"] - + tool_shed_repository["changeset_revision"] - + tool_shed_repository["name"] - + tool_shed_repository["tool_shed"] - ) - tools[toolstr] = tool_shed_repository - - irm = InstallRepositoryManager(self.app) - install_options = workflow_create_options.install_options - for k in tools: - item = tools[k] - tool_shed_url = f"https://{item['tool_shed']}/" - name = item["name"] - owner = item["owner"] - changeset_revision = item["changeset_revision"] - irm.install(tool_shed_url, name, owner, changeset_revision, install_options) - - def __get_stored_accessible_workflow(self, trans, workflow_id, **kwd): - instance = util.string_as_bool(kwd.get("instance", "false")) - return self.workflow_manager.get_stored_accessible_workflow(trans, workflow_id, by_stored_id=not instance) - - def __get_stored_workflow(self, trans, workflow_id, **kwd): - instance = util.string_as_bool(kwd.get("instance", "false")) - return self.workflow_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + self.service._api_import_shared_workflow(trans, workflow_id, payload) StoredWorkflowIDPathParam = Annotated[ @@ -872,6 +348,30 @@ 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.", ) +StyleQueryParam = Annotated[ + Optional[str], + Query( + title="Style of export", + description="The default is 'export', which is meant to be used with workflow import endpoints. Other formats such as 'instance', 'editor', 'run' are tied to the GUI and should not be considered stable APIs. The default format for 'export' is specified by the admin with the `default_workflow_export_format` config option. Style can be specified as either 'ga' or 'format2' directly to be explicit about which format to download.", + ), +] + +FormatQueryParam = Annotated[ + Optional[str], + Query( + title="Format", + description="The format to download the workflow in.", + ), +] + +WorkflowsHistoryIDQueryParam = Annotated[ + Optional[DecodedDatabaseIdField], + Query( + title="History ID", + description="The history id to import a workflow from.", + ), +] + InvokeWorkflowBody = Annotated[ InvokeWorkflowPayload, Body( @@ -890,6 +390,24 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): ), ] +SetWorkflowMenuBody = Annotated[ + Optional[SetWorkflowMenuPayload], + Body( + title="Set workflow menu", + description="The values to set a workflow menu.", + ), +] + +DownloadWorkflowSummary = Union[ + WorkflowDictEditorSummary, + StoredWorkflowDetailed, + WorkflowDictRunSummary, + WorkflowDictPreviewSummary, + WorkflowDictFormat2Summary, + WorkflowDictExportSummary, + WorkflowDictFormat2WrappedYamlSummary, +] + @router.cbv class FastAPIWorkflows: @@ -946,6 +464,29 @@ def sharing( """Return the sharing status of the item.""" return self.service.shareable_service.sharing(trans, workflow_id) + @router.get( + "/api/workflows/{workflow_id}/download", + summary="Returns a selected workflow.", + response_model_exclude_unset=True, + ) + # Preserve the following download route for now for dependent applications -- deprecate at some point + @router.get( + "/api/workflows/download/{workflow_id}", + summary="Returns a selected workflow.", + response_model_exclude_unset=True, + ) + def workflow_dict( + self, + workflow_id: StoredWorkflowIDPathParam, + history_id: WorkflowsHistoryIDQueryParam = None, + style: StyleQueryParam = "export", + format: FormatQueryParam = None, + version: VersionQueryParam = None, + instance: InstanceQueryParam = False, + trans: ProvidesUserContext = DependsOnTrans, + ) -> DownloadWorkflowSummary: + return self.service.download_workflow(trans, workflow_id, history_id, style, format, version, instance) + @router.put( "/api/workflows/{workflow_id}/enable_link_access", summary="Makes this item accessible by a URL link.", @@ -1007,6 +548,17 @@ def unpublish( """Removes this item from the published list and return the current sharing status.""" return self.service.shareable_service.unpublish(trans, workflow_id) + @router.put( + "/api/workflows/menu", + summary="Save workflow menu to be shown in the tool panel", + ) + def set_workflow_menu( + self, + payload: SetWorkflowMenuBody = None, + trans: ProvidesHistoryContext = DependsOnTrans, + ) -> SetWorkflowMenuSummary: + return self.service.set_workflow_menu(payload, trans) + @router.put( "/api/workflows/{workflow_id}/share_with_users", summary="Share this item with specific users.", @@ -1035,6 +587,20 @@ def set_slug( self.service.shareable_service.set_slug(trans, workflow_id, payload) return Response(status_code=status.HTTP_204_NO_CONTENT) + @router.put( + "/api/workflows/{workflow_id}", + summary="Update the workflow stored with the ``id``", + name="update_workflow", + ) + def update( + self, + workflow_id: StoredWorkflowIDPathParam, + payload: WorkflowUpdatePayload, + instance: InstanceQueryParam = False, + trans: ProvidesHistoryContext = DependsOnTrans, + ) -> StoredWorkflowDetailed: + return self.service.update_workflow(trans, payload, instance, workflow_id) + @router.delete( "/api/workflows/{workflow_id}", summary="Add the deleted flag to a workflow.", @@ -1047,6 +613,18 @@ def delete_workflow( self.service.delete(trans, workflow_id) return Response(status_code=status.HTTP_204_NO_CONTENT) + @router.post( + "/api/workflows", + summary="Create workflows in various ways", + name="create_workflow", + ) + def create( + self, + payload: WorkflowCreatePayload, + trans: ProvidesUserContext = DependsOnTrans, + ): + return self.service.create(trans, payload) + @router.post( "/api/workflows/{workflow_id}/undelete", summary="Remove the deleted flag from a workflow.", @@ -1175,7 +753,7 @@ def show_workflow( ), ] -HistoryIdQueryParam = Annotated[ +InvocationsHistoryIdQueryParam = Annotated[ Optional[DecodedDatabaseIdField], Query( title="History ID", @@ -1284,7 +862,7 @@ def index_invocations( self, response: Response, workflow_id: WorkflowIdQueryParam = None, - history_id: HistoryIdQueryParam = None, + history_id: InvocationsHistoryIdQueryParam = None, job_id: JobIdQueryParam = None, user_id: UserIdQueryParam = None, sort_by: InvocationsSortByQueryParam = None, @@ -1338,7 +916,7 @@ def index_workflow_invocations( self, response: Response, workflow_id: StoredWorkflowIDPathParam, - history_id: HistoryIdQueryParam = None, + history_id: InvocationsHistoryIdQueryParam = None, job_id: JobIdQueryParam = None, user_id: UserIdQueryParam = None, sort_by: InvocationsSortByQueryParam = None, diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 1e40c414f740..cb2237decb7d 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -592,9 +592,6 @@ def populate_api_routes(webapp, app): webapp.mapper.resource("visualization", "visualizations", path_prefix="/api") webapp.mapper.resource("plugins", "plugins", path_prefix="/api") webapp.mapper.connect("/api/workflows/build_module", action="build_module", controller="workflows") - webapp.mapper.connect( - "/api/workflows/menu", action="set_workflow_menu", controller="workflows", conditions=dict(method=["PUT"]) - ) webapp.mapper.resource("workflow", "workflows", path_prefix="/api") # ---- visualizations registry ---- generic template renderer @@ -625,21 +622,6 @@ def populate_api_routes(webapp, app): action="import_new_workflow_deprecated", conditions=dict(method=["POST"]), ) - webapp.mapper.connect( - "workflow_dict", - "/api/workflows/{workflow_id}/download", - controller="workflows", - action="workflow_dict", - conditions=dict(method=["GET"]), - ) - # Preserve the following download route for now for dependent applications -- deprecate at some point - webapp.mapper.connect( - "workflow_dict", - "/api/workflows/download/{workflow_id}", - controller="workflows", - action="workflow_dict", - conditions=dict(method=["GET"]), - ) # Deprecated in favor of POST /api/workflows with shared_workflow_id in payload. webapp.mapper.connect( "import_shared_workflow_deprecated", @@ -688,12 +670,6 @@ def populate_api_routes(webapp, app): # action="import_tool_version", # 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", diff --git a/lib/galaxy/webapps/galaxy/services/workflows.py b/lib/galaxy/webapps/galaxy/services/workflows.py index 72414b169abf..6a2bee30363e 100644 --- a/lib/galaxy/webapps/galaxy/services/workflows.py +++ b/lib/galaxy/webapps/galaxy/services/workflows.py @@ -1,4 +1,6 @@ +import json import logging +import os from typing import ( Any, Dict, @@ -8,19 +10,37 @@ Union, ) +from fastapi.responses import PlainTextResponse +from gxformat2._yaml import ordered_dump +from markupsafe import escape + from galaxy import ( exceptions, + model, + util, web, ) -from galaxy.managers.context import ProvidesUserContext +from galaxy.files.uris import ( + stream_url_to_str, + validate_uri_access, +) +from galaxy.managers.context import ( + ProvidesHistoryContext, + ProvidesUserContext, +) +from galaxy.managers.histories import HistoryManager from galaxy.managers.workflows import ( + MissingToolsException, RefactorResponse, WorkflowContentsManager, + WorkflowCreateOptions, WorkflowSerializer, WorkflowsManager, + WorkflowUpdateOptions, ) from galaxy.model import StoredWorkflow from galaxy.model.base import transaction +from galaxy.model.item_attrs import UsesAnnotations from galaxy.schema.invocation import WorkflowInvocationResponse from galaxy.schema.schema import ( InvocationsStateCounts, @@ -28,12 +48,29 @@ ) from galaxy.schema.workflows import ( InvokeWorkflowPayload, + SetWorkflowMenuPayload, + SetWorkflowMenuSummary, StoredWorkflowDetailed, + WorkflowDictEditorSummary, + WorkflowDictExportSummary, + WorkflowDictFormat2Summary, + WorkflowDictFormat2WrappedYamlSummary, + WorkflowDictPreviewSummary, + WorkflowDictRunSummary, + WorkflowUpdatePayload, ) +from galaxy.tool_shed.galaxy_install.install_manager import InstallRepositoryManager +from galaxy.util.sanitize_html import sanitize_html from galaxy.util.tool_shed.tool_shed_registry import Registry +from galaxy.webapps.base.controller import ( + SharableMixin, + url_for, + UsesStoredWorkflowMixin, +) from galaxy.webapps.galaxy.services.base import ServiceBase from galaxy.webapps.galaxy.services.notifications import NotificationService from galaxy.webapps.galaxy.services.sharable import ShareableService +from galaxy.workflow.extract import extract_workflow from galaxy.workflow.run import queue_invoke from galaxy.workflow.run_request import build_workflow_run_configs @@ -52,12 +89,62 @@ def __init__( serializer: WorkflowSerializer, tool_shed_registry: Registry, notification_service: NotificationService, + history_manager: HistoryManager, + uses_annotations: UsesAnnotations, + uses_stored_workflow_mix_in: UsesStoredWorkflowMixin, + sharable_mixin: SharableMixin, ): self._workflows_manager = workflows_manager self._workflow_contents_manager = workflow_contents_manager self._serializer = serializer self.shareable_service = ShareableService(workflows_manager, serializer, notification_service) self._tool_shed_registry = tool_shed_registry + self._history_manager = history_manager + self._uses_annotations = uses_annotations + self._uses_stored_workflow_mix_in = uses_stored_workflow_mix_in + self._sharable_mixin = sharable_mixin + + def download_workflow(self, trans, workflow_id, history_id, style, format, version, instance): + stored_workflow = self._workflows_manager.get_stored_accessible_workflow( + trans, workflow_id, by_stored_id=not instance + ) + history = None + if history_id: + history = self._history_manager.get_accessible(history_id, trans.user, current_history=trans.history) + ret_dict = self._workflow_contents_manager.workflow_to_dict( + trans, stored_workflow, style=style, version=version, history=history + ) + if format == "json-download": + sname = stored_workflow.name + sname = "".join(c in util.FILENAME_VALID_CHARS and c or "_" for c in sname)[0:150] + if ret_dict.get("format-version", None) == "0.1": + extension = "ga" + else: + extension = "gxwf.json" + trans.response.headers["Content-Disposition"] = ( + f'attachment; filename="Galaxy-Workflow-{sname}.{extension}"' + ) + trans.response.set_content_type("application/galaxy-archive") + if style == "export": + style = style = self._workflow_contents_manager.app.config.default_workflow_export_format + if style == "format2" and format != "json-download": + return PlainTextResponse(ordered_dump(ret_dict)) + elif style == "editor": + return WorkflowDictEditorSummary(**ret_dict) + elif style == ("legacy" or "instance"): + return StoredWorkflowDetailed(**ret_dict) + elif style == "run": + return WorkflowDictRunSummary(**ret_dict) + elif style == "preview": + return WorkflowDictPreviewSummary(**ret_dict) + elif style == "format2": + return WorkflowDictFormat2Summary(**ret_dict) + elif style == "format2_wrapped_yaml": + return WorkflowDictFormat2WrappedYamlSummary(**ret_dict) + elif style == "ga": + return WorkflowDictExportSummary(**ret_dict) + else: + raise exceptions.RequestParameterInvalidException(f"Unknown workflow style {style}") def index( self, @@ -221,6 +308,42 @@ def refactor( 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 set_workflow_menu( + self, + payload: Optional[SetWorkflowMenuPayload], + trans: ProvidesHistoryContext, + ) -> SetWorkflowMenuSummary: + user = trans.user + if payload: + workflow_ids = payload.workflow_ids + if not isinstance(workflow_ids, list): + workflow_ids = [workflow_ids] + else: + workflow_ids = [] + session = trans.sa_session + # This explicit remove seems like a hack, need to figure out + # how to make the association do it automatically. + for m in user.stored_workflow_menu_entries: + session.delete(m) + user.stored_workflow_menu_entries = [] + # To ensure id list is unique + seen_workflow_ids = set() + for wf_id in workflow_ids: + if wf_id in seen_workflow_ids: + continue + else: + seen_workflow_ids.add(wf_id) + m = model.StoredWorkflowMenuEntry() + m.stored_workflow = session.get(model.StoredWorkflow, wf_id) + + user.stored_workflow_menu_entries.append(m) + with transaction(session): + session.commit() + message = "Menu updated." + # TODO - It seems like this populates a mako template, is it necessary? + # trans.set_message(message) + return SetWorkflowMenuSummary(message=message, status="done") + 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: @@ -257,3 +380,406 @@ def __get_full_shed_url(self, url): if url in shed_url: return shed_url return None + + def update_workflow( + self, + trans: ProvidesHistoryContext, + payload: WorkflowUpdatePayload, + instance: Optional[bool], + workflow_id: int, + ): + stored_workflow = self._workflows_manager.get_stored_workflow(trans, workflow_id, by_stored_id=not instance) + payload_dict = payload.model_dump(exclude_unset=True) + workflow_dict = payload_dict.get("workflow", {}) + workflow_dict.update({k: v for k, v in payload_dict.items() if k not in workflow_dict}) + if workflow_dict: + require_flush = False + raw_workflow_description = self._workflow_contents_manager.normalize_workflow_format(trans, workflow_dict) + workflow_dict = raw_workflow_description.as_dict + new_workflow_name = workflow_dict.get("name") + old_workflow = stored_workflow.latest_workflow + name_updated = new_workflow_name and new_workflow_name != stored_workflow.name + steps_updated = "steps" in workflow_dict + if name_updated and not steps_updated: + sanitized_name = sanitize_html(new_workflow_name or old_workflow.name) + if not sanitized_name: + raise exceptions.MessageException("Workflow must have a valid name.") + workflow = old_workflow.copy(user=trans.user) + workflow.stored_workflow = stored_workflow + workflow.name = sanitized_name + stored_workflow.name = sanitized_name + stored_workflow.latest_workflow = workflow + + # TODO MyPy complains about this line, but it seems to be working + # trans.sa_session.add(workflow, stored_workflow) + # TODO: This also appears to work. Is it better? + trans.sa_session.add(workflow) + + require_flush = True + + if "hidden" in workflow_dict and stored_workflow.hidden != workflow_dict["hidden"]: + stored_workflow.hidden = workflow_dict["hidden"] + require_flush = True + + if "published" in workflow_dict and stored_workflow.published != workflow_dict["published"]: + stored_workflow.published = workflow_dict["published"] + require_flush = True + + if "importable" in workflow_dict and stored_workflow.importable != workflow_dict["importable"]: + stored_workflow.importable = workflow_dict["importable"] + require_flush = True + + if "annotation" in workflow_dict and not steps_updated: + newAnnotation = sanitize_html(workflow_dict["annotation"]) + self._uses_annotations.add_item_annotation(trans.sa_session, trans.user, stored_workflow, newAnnotation) + require_flush = True + + if "menu_entry" in workflow_dict or "show_in_tool_panel" in workflow_dict: + show_in_panel = workflow_dict.get("menu_entry") or workflow_dict.get("show_in_tool_panel") + stored_workflow_menu_entries = trans.user.stored_workflow_menu_entries + if show_in_panel: + workflow_ids = [wf.stored_workflow_id for wf in stored_workflow_menu_entries] + if workflow_id not in workflow_ids: + menu_entry = model.StoredWorkflowMenuEntry() + menu_entry.stored_workflow = stored_workflow + stored_workflow_menu_entries.append(menu_entry) + trans.sa_session.add(menu_entry) + require_flush = True + else: + # remove if in list + entries = {x.stored_workflow_id: x for x in stored_workflow_menu_entries} + if workflow_id in entries: + stored_workflow_menu_entries.remove(entries[workflow_id]) + require_flush = True + # set tags + if "tags" in workflow_dict: + trans.tag_handler.set_tags_from_list( + user=trans.user, + item=stored_workflow, + new_tags_list=workflow_dict["tags"], + ) + + if require_flush: + with transaction(trans.sa_session): + trans.sa_session.commit() + + if "steps" in workflow_dict or "comments" in workflow_dict: + try: + workflow_update_options = WorkflowUpdateOptions(**payload_dict) + workflow, errors = self._workflow_contents_manager.update_workflow_from_raw_description( + trans, + stored_workflow, + raw_workflow_description, + workflow_update_options, + ) + except MissingToolsException: + raise exceptions.MessageException( + "This workflow contains missing tools. It cannot be saved until they have been removed from the workflow or installed." + ) + + else: + message = "Updating workflow requires dictionary containing 'workflow' attribute with new JSON description." + raise exceptions.RequestParameterInvalidException(message) + return StoredWorkflowDetailed( + **self._workflow_contents_manager.workflow_to_dict(trans, stored_workflow, style="instance") + ) + + def create( + self, + trans, + payload=None, + ): + payload = payload.model_dump(exclude_unset=True) + ways_to_create = { + "archive_file", + "archive_source", + "from_history_id", + "from_path", + "shared_workflow_id", + "workflow", + } + + if trans.user_is_bootstrap_admin: + raise exceptions.RealUserRequiredException("Only real users can create or run workflows.") + + if payload is None or len(ways_to_create.intersection(payload)) == 0: + # Workflow is not propelry received + message = f"One parameter among - {', '.join(ways_to_create)} - must be specified" + raise exceptions.RequestParameterMissingException(message) + + if len(ways_to_create.intersection(payload)) > 1: + message = f"Only one parameter among - {', '.join(ways_to_create)} - must be specified" + raise exceptions.RequestParameterInvalidException(message) + + if "archive_source" in payload or "archive_file" in payload: + archive_source = payload.get("archive_source") + archive_file = payload.get("archive_file") + archive_data = None + if archive_source: + validate_uri_access(archive_source, trans.user_is_admin, trans.app.config.fetch_url_allowlist_ips) + if archive_source.startswith("file://"): + workflow_src = {"src": "from_path", "path": archive_source[len("file://") :]} + payload["workflow"] = workflow_src + return self._api_import_new_workflow(trans, payload) + elif archive_source == "trs_tool": + server = None + trs_tool_id = None + trs_version_id = None + import_source = None + if "trs_url" in payload: + # parts = self.app.trs_proxy.match_url(payload["trs_url"]) + parts = trans.app.trs_proxy.match_url(payload["trs_url"]) + if parts: + # server = self.app.trs_proxy.server_from_url(parts["trs_base_url"]) + server = trans.app.trs_proxy.server_from_url(parts["trs_base_url"]) + trs_tool_id = parts["tool_id"] + trs_version_id = parts["version_id"] + payload["trs_tool_id"] = trs_tool_id + payload["trs_version_id"] = trs_version_id + else: + raise exceptions.MessageException("Invalid TRS URL.") + else: + trs_server = payload.get("trs_server") + # server = self.app.trs_proxy.get_server(trs_server) + server = trans.app.trs_proxy.get_server(trs_server) + trs_tool_id = payload.get("trs_tool_id") + trs_version_id = payload.get("trs_version_id") + + archive_data = server.get_version_descriptor(trs_tool_id, trs_version_id) + else: + try: + archive_data = stream_url_to_str( + archive_source, trans.app.file_sources, prefix="gx_workflow_download" + ) + import_source = "URL" + except Exception: + raise exceptions.MessageException(f"Failed to open URL '{escape(archive_source)}'.") + elif hasattr(archive_file, "file"): + uploaded_file = archive_file.file + uploaded_file_name = uploaded_file.name + if os.path.getsize(os.path.abspath(uploaded_file_name)) > 0: + archive_data = util.unicodify(uploaded_file.read()) + import_source = "uploaded file" + else: + raise exceptions.MessageException("You attempted to upload an empty file.") + else: + raise exceptions.MessageException("Please provide a URL or file.") + return self.__api_import_from_archive(trans, archive_data, import_source, payload=payload) + + if "from_history_id" in payload: + from_history_id = payload.get("from_history_id") + # from_history_id = self.decode_id(from_history_id) + history = self._history_manager.get_accessible(from_history_id, trans.user, current_history=trans.history) + + # job_ids = [self.decode_id(_) for _ in payload.get("job_ids", [])] + job_ids = payload.get("job_ids", []) + dataset_ids = payload.get("dataset_ids", []) + dataset_collection_ids = payload.get("dataset_collection_ids", []) + workflow_name = payload["workflow_name"] + stored_workflow = extract_workflow( + trans=trans, + user=trans.user, + history=history, + job_ids=job_ids, + dataset_ids=dataset_ids, + dataset_collection_ids=dataset_collection_ids, + workflow_name=workflow_name, + ) + item = stored_workflow.to_dict(value_mapper={"id": trans.security.encode_id}) + item["url"] = url_for("workflow", id=item["id"]) + return item + + if "from_path" in payload: + from_path = payload.get("from_path") + object_id = payload.get("object_id") + workflow_src = {"src": "from_path", "path": from_path} + if object_id is not None: + workflow_src["object_id"] = object_id + payload["workflow"] = workflow_src + return self._api_import_new_workflow(trans, payload) + + if "shared_workflow_id" in payload: + workflow_id = payload["shared_workflow_id"] + return self._api_import_shared_workflow(trans, workflow_id, payload) + + if "workflow" in payload: + return self._api_import_new_workflow(trans, payload) + + # This was already raised above, but just in case... + raise exceptions.RequestParameterMissingException("No method for workflow creation supplied.") + + def _api_import_new_workflow( + self, + trans, + payload, + **kwd, + ): + data = payload["workflow"] + raw_workflow_description = self.__normalize_workflow(trans, data) + workflow_create_options = WorkflowCreateOptions(**payload) + workflow, missing_tool_tups = self.__workflow_from_dict( + trans, + raw_workflow_description, + workflow_create_options, + ) + # galaxy workflow newly created id + workflow_id = workflow.id + # api encoded, id + encoded_id = trans.security.encode_id(workflow_id) + item = workflow.to_dict(value_mapper={"id": trans.security.encode_id}) + item["annotations"] = [x.annotation for x in workflow.annotations] + item["url"] = url_for("workflow", id=encoded_id) + item["owner"] = workflow.user.username + item["number_of_steps"] = len(workflow.latest_workflow.steps) + return item + + def _api_import_shared_workflow( + self, + trans, + workflow_id, + payload, + **kwd, + ): + try: + stored_workflow = self._uses_stored_workflow_mix_in.get_stored_workflow( + trans, workflow_id, check_ownership=False, app_by_trans=True + ) + except Exception: + raise exceptions.ObjectNotFound("Malformed workflow id specified.") + if stored_workflow.importable is False: + raise exceptions.ItemAccessibilityException( + "The owner of this workflow has disabled imports via this link." + ) + elif stored_workflow.deleted: + raise exceptions.ItemDeletionException("You can't import this workflow because it has been deleted.") + imported_workflow = self._uses_stored_workflow_mix_in._import_shared_workflow(trans, stored_workflow) + item = imported_workflow.to_dict(value_mapper={"id": trans.security.encode_id}) + encoded_id = trans.security.encode_id(imported_workflow.id) + item["url"] = url_for("workflow", id=encoded_id) + return item + + def __api_import_from_archive( + self, + trans, + archive_data, + source=None, + payload=None, + ): + payload = payload or {} + try: + data = json.loads(archive_data) + except Exception: + if "GalaxyWorkflow" in archive_data: + data = {"yaml_content": archive_data} + else: + raise exceptions.MessageException("The data content does not appear to be a valid workflow.") + if not data: + raise exceptions.MessageException("The data content is missing.") + raw_workflow_description = self.__normalize_workflow(trans, data) + workflow_create_options = WorkflowCreateOptions(**payload) + workflow, missing_tool_tups = self.__workflow_from_dict( + trans, raw_workflow_description, workflow_create_options, source=source + ) + workflow_id = workflow.id + workflow = workflow.latest_workflow + + response = { + "message": f"Workflow '{escape(workflow.name)}' imported successfully.", + "status": "success", + "id": trans.security.encode_id(workflow_id), + } + if workflow.has_errors: + response["message"] = "Imported, but some steps in this workflow have validation errors." + response["status"] = "error" + elif len(workflow.steps) == 0: + response["message"] = "Imported, but this workflow has no steps." + response["status"] = "error" + elif workflow.has_cycles: + response["message"] = "Imported, but this workflow contains cycles." + response["status"] = "error" + return response + + def __workflow_from_dict( + self, + trans, + data, + workflow_create_options, + source=None, + ): + """Creates a workflow from a dict. + + Created workflow is stored in the database and returned. + """ + publish = workflow_create_options.publish + importable = workflow_create_options.is_importable + if publish and not importable: + raise exceptions.RequestParameterInvalidException("Published workflow must be importable.") + + # workflow_contents_manager = self.app.workflow_contents_manager + workflow_contents_manager = trans.app.workflow_contents_manager + raw_workflow_description = workflow_contents_manager.ensure_raw_description(data) + created_workflow = workflow_contents_manager.build_workflow_from_raw_description( + trans, + raw_workflow_description, + workflow_create_options, + source=source, + ) + if importable: + self._sharable_mixin._make_item_accessible(trans.sa_session, created_workflow.stored_workflow) + with transaction(trans.sa_session): + trans.sa_session.commit() + + self.__import_tools_if_needed(trans, workflow_create_options, raw_workflow_description) + return created_workflow.stored_workflow, created_workflow.missing_tools + + def __import_tools_if_needed( + self, + trans, + workflow_create_options, + raw_workflow_description, + ): + if not workflow_create_options.import_tools: + return + + if not trans.user_is_admin: + raise exceptions.AdminRequiredException() + + data = raw_workflow_description.as_dict + + tools = {} + for key in data["steps"]: + item = data["steps"][key] + if item is not None: + if "tool_shed_repository" in item: + tool_shed_repository = item["tool_shed_repository"] + if ( + "owner" in tool_shed_repository + and "changeset_revision" in tool_shed_repository + and "name" in tool_shed_repository + and "tool_shed" in tool_shed_repository + ): + toolstr = ( + tool_shed_repository["owner"] + + tool_shed_repository["changeset_revision"] + + tool_shed_repository["name"] + + tool_shed_repository["tool_shed"] + ) + tools[toolstr] = tool_shed_repository + + # irm = InstallRepositoryManager(self.app) + irm = InstallRepositoryManager(trans.app) + install_options = workflow_create_options.install_options + for k in tools: + item = tools[k] + tool_shed_url = f"https://{item['tool_shed']}/" + name = item["name"] + owner = item["owner"] + changeset_revision = item["changeset_revision"] + irm.install(tool_shed_url, name, owner, changeset_revision, install_options) + + def __normalize_workflow( + self, + trans, + as_dict, + ): + return self._workflow_contents_manager.normalize_workflow_format(trans, as_dict) diff --git a/lib/galaxy_test/api/test_tags.py b/lib/galaxy_test/api/test_tags.py index 776f95db147d..21c1ecf77d7c 100644 --- a/lib/galaxy_test/api/test_tags.py +++ b/lib/galaxy_test/api/test_tags.py @@ -140,7 +140,7 @@ def create_item(self) -> str: data = dict( workflow=json.dumps(workflow), ) - upload_response = self._post("workflows", data=data) + upload_response = self._post("workflows", data=data, json=True) self._assert_status_code_is(upload_response, 200) return upload_response.json()["id"] diff --git a/lib/galaxy_test/api/test_workflow_extraction.py b/lib/galaxy_test/api/test_workflow_extraction.py index ada641171af6..839fb7756952 100644 --- a/lib/galaxy_test/api/test_workflow_extraction.py +++ b/lib/galaxy_test/api/test_workflow_extraction.py @@ -602,7 +602,7 @@ def _extract_and_download_workflow(self, history_id: str, **extract_payload): if isinstance(value, list): extract_payload[key] = dumps(value) - create_workflow_response = self._post("workflows", data=extract_payload) + create_workflow_response = self._post("workflows", data=extract_payload, json=True) self._assert_status_code_is(create_workflow_response, 200) new_workflow_id = create_workflow_response.json()["id"] diff --git a/lib/galaxy_test/api/test_workflows.py b/lib/galaxy_test/api/test_workflows.py index 0b3b38de88fd..23e8579c24d4 100644 --- a/lib/galaxy_test/api/test_workflows.py +++ b/lib/galaxy_test/api/test_workflows.py @@ -789,9 +789,11 @@ def __test_upload( data["import_tools"] = import_tools if use_deprecated_route: route = "workflows/upload" + upload_response = self._post(route, data=data) else: route = "workflows" - upload_response = self._post(route, data=data) + upload_response = self._post(route, data=data, json=True) + # upload_response = self._post(route, data=data) if assert_ok: self._assert_status_code_is(upload_response, 200) self._assert_user_has_workflow_with_name(name) @@ -870,6 +872,15 @@ def test_update_tags(self): update_response = self._update_workflow(workflow_id, update_payload).json() assert update_response["tags"] == [] + def test_set_workflow_menu(self): + original_name = "test update name" + workflow_object = self.workflow_populator.load_workflow(name=original_name) + upload_response = self.__test_upload(workflow=workflow_object, name=original_name) + workflow = upload_response.json() + workflow_id = workflow["id"] + response = self._put("/api/workflows/menu", {"workflow_ids": workflow_id}, json=True) + self._assert_status_code_is(response, 200) + def test_update_name(self): original_name = "test update name" workflow_object = self.workflow_populator.load_workflow(name=original_name) @@ -1140,7 +1151,7 @@ def test_not_importable_prevents_import(self): def test_url_import(self): url = "https://raw.githubusercontent.com/galaxyproject/galaxy/release_19.09/test/base/data/test_workflow_1.ga" - workflow_id = self._post("workflows", data={"archive_source": url}).json()["id"] + workflow_id = self._post("workflows", data={"archive_source": url}, json=True).json()["id"] workflow = self._download_workflow(workflow_id) assert "TestWorkflow1" in workflow["name"] assert ( @@ -1149,7 +1160,7 @@ def test_url_import(self): def test_base64_import(self): base64_url = "base64://" + base64.b64encode(workflow_str.encode("utf-8")).decode("utf-8") - response = self._post("workflows", data={"archive_source": base64_url}) + response = self._post("workflows", data={"archive_source": base64_url}, json=True) response.raise_for_status() workflow_id = response.json()["id"] workflow = self._download_workflow(workflow_id) @@ -1162,7 +1173,7 @@ def test_trs_import(self): "trs_tool_id": "#workflow/github.com/jmchilton/galaxy-workflow-dockstore-example-1/mycoolworkflow", "trs_version_id": "master", } - workflow_id = self._post("workflows", data=trs_payload).json()["id"] + workflow_id = self._post("workflows", data=trs_payload, json=True).json()["id"] original_workflow = self._download_workflow(workflow_id) assert "Test Workflow" in original_workflow["name"] assert original_workflow.get("source_metadata").get("trs_tool_id") == trs_payload["trs_tool_id"] @@ -1189,7 +1200,7 @@ def test_trs_import_from_dockstore_trs_url(self): "%23workflow%2Fgithub.com%2Fjmchilton%2Fgalaxy-workflow-dockstore-example-1%2Fmycoolworkflow/" "versions/master", } - workflow_id = self._post("workflows", data=trs_payload).json()["id"] + workflow_id = self._post("workflows", data=trs_payload, json=True).json()["id"] original_workflow = self._download_workflow(workflow_id) assert "Test Workflow" in original_workflow["name"] assert ( @@ -1222,7 +1233,7 @@ def test_trs_import_from_workflowhub_trs_url(self): "archive_source": "trs_tool", "trs_url": "https://workflowhub.eu/ga4gh/trs/v2/tools/109/versions/5", } - workflow_id = self._post("workflows", data=trs_payload).json()["id"] + workflow_id = self._post("workflows", data=trs_payload, json=True).json()["id"] original_workflow = self._download_workflow(workflow_id) assert "COVID-19: variation analysis reporting" in original_workflow["name"] assert original_workflow.get("source_metadata").get("trs_tool_id") == "109" @@ -5415,13 +5426,13 @@ def _step_map(self, workflow): return step_map def test_empty_create(self): - response = self._post("workflows") + response = self._post("workflows", json=True) self._assert_status_code_is(response, 400) self._assert_error_code_is(response, error_codes.error_codes_by_name["USER_REQUEST_MISSING_PARAMETER"]) def test_invalid_create_multiple_types(self): data = {"shared_workflow_id": "1234567890abcdef", "from_history_id": "1234567890abcdef"} - response = self._post("workflows", data) + response = self._post("workflows", data=data, json=True) self._assert_status_code_is(response, 400) self._assert_error_code_is(response, error_codes.error_codes_by_name["USER_REQUEST_INVALID_PARAMETER"]) @@ -7381,7 +7392,7 @@ def test_workflow_from_path_requires_admin(self): path_as_uri = f"file://{workflow_path}" import_data = dict(archive_source=path_as_uri) - import_response = self._post("workflows", data=import_data) + import_response = self._post("workflows", data=import_data, json=True) self._assert_status_code_is(import_response, 403) self._assert_error_code_is(import_response, error_codes.error_codes_by_name["ADMIN_REQUIRED"]) finally: @@ -7522,12 +7533,13 @@ def __import_workflow(self, workflow_id, deprecated_route=False): import_data = dict( workflow_id=workflow_id, ) + return self._post(route, import_data) else: route = "workflows" import_data = dict( shared_workflow_id=workflow_id, ) - return self._post(route, import_data) + return self._post(route, import_data, json=True) def _show_workflow(self, workflow_id): show_response = self._get(f"workflows/{workflow_id}") diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 1104e94b1dbf..dc2bc1008d23 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -1735,7 +1735,7 @@ def import_workflow_from_path_raw(self, from_path: str, object_id: Optional[str] from_path=from_path, object_id=object_id, ) - import_response = self._post("workflows", data=data) + import_response = self._post("workflows", data=data, json=True) return import_response def import_workflow_from_path(self, from_path: str, object_id: Optional[str] = None) -> str: @@ -2283,7 +2283,7 @@ def import_workflow(self, workflow, **kwds) -> Dict[str, Any]: "workflow": workflow_str, } data.update(**kwds) - upload_response = self._post("workflows", data=data) + upload_response = self._post("workflows", data=data, json=True) assert upload_response.status_code == 200, upload_response.content return upload_response.json() diff --git a/lib/galaxy_test/selenium/framework.py b/lib/galaxy_test/selenium/framework.py index 310c88c88053..8d242fe2004b 100644 --- a/lib/galaxy_test/selenium/framework.py +++ b/lib/galaxy_test/selenium/framework.py @@ -819,7 +819,7 @@ def import_workflow(self, workflow: dict, **kwds) -> dict: "workflow": workflow_str, } data.update(**kwds) - upload_response = self._post("workflows", data=data) + upload_response = self._post("workflows", data=data, json=True) upload_response.raise_for_status() return upload_response.json()