diff --git a/client/src/components/Workflow/Editor/Node.vue b/client/src/components/Workflow/Editor/Node.vue index e152292f2db3..c73a7b5b9b21 100644 --- a/client/src/components/Workflow/Editor/Node.vue +++ b/client/src/components/Workflow/Editor/Node.vue @@ -69,6 +69,7 @@ {{ step.id + 1 }}: @@ -83,7 +84,13 @@ @click="makeActive"> {{ errors }} + + + + @@ -139,10 +152,12 @@ import type { Step } from "@/stores/workflowStepStore"; import type { OutputTerminals } from "./modules/terminals"; +import GenericItem from "@/components/History/Content/GenericItem.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; import DraggableWrapper from "@/components/Workflow/Editor/DraggablePan.vue"; import NodeInput from "@/components/Workflow/Editor/NodeInput.vue"; import NodeOutput from "@/components/Workflow/Editor/NodeOutput.vue"; +// import NodeInvocationOutput from "@/components/Workflow/Editor/NodeInvocationOutput.vue"; import Recommendations from "@/components/Workflow/Editor/Recommendations.vue"; Vue.use(BootstrapVue); @@ -165,6 +180,7 @@ const props = defineProps({ scale: { type: Number, default: 1 }, highlight: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, + invocation: { type: Boolean, default: false }, }); const emit = defineEmits([ diff --git a/client/src/components/Workflow/Editor/NodeInvocationOutput.vue b/client/src/components/Workflow/Editor/NodeInvocationOutput.vue new file mode 100644 index 000000000000..3ade4881f3aa --- /dev/null +++ b/client/src/components/Workflow/Editor/NodeInvocationOutput.vue @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + {{ label }} + + ({{ extensions.join(", ") }}) + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/Workflow/Editor/NodeOutput.vue b/client/src/components/Workflow/Editor/NodeOutput.vue index 3d21e05105ed..88d55befd107 100644 --- a/client/src/components/Workflow/Editor/NodeOutput.vue +++ b/client/src/components/Workflow/Editor/NodeOutput.vue @@ -3,6 +3,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faSquare } from "@fortawesome/free-regular-svg-icons"; import { faCheckSquare, + faChevronCircleDown, faChevronCircleRight, faEye, faEyeSlash, @@ -35,7 +36,7 @@ import ConnectionMenu from "@/components/Workflow/Editor/ConnectionMenu.vue"; type ElementBounding = UnwrapRef; -library.add(faSquare, faCheckSquare, faChevronCircleRight, faEye, faEyeSlash, faMinus, faPlus); +library.add(faSquare, faCheckSquare, faChevronCircleDown, faChevronCircleRight, faEye, faEyeSlash, faMinus, faPlus); const props = defineProps<{ output: OutputTerminalSource; @@ -50,6 +51,7 @@ const props = defineProps<{ datatypesMapper: DatatypesMapperModel; parentNode: HTMLElement | null; readonly: boolean; + invocation: boolean; }>(); const emit = defineEmits(["pan-by", "stopDragging", "onDragConnector"]); @@ -379,8 +381,8 @@ const removeTagsAction = computed(() => { :id="id" ref="terminalComponent" v-b-tooltip.hover="outputDetails" - class="output-terminal prevent-zoom" - :class="{ 'mapped-over': isMultiple }" + class="prevent-zoom" + :class="{ 'mapped-over': isMultiple, 'output-terminal': !invocation, 'invocation-terminal': invocation }" :output-name="output.name" :root-offset="rootOffset" :prevent-default="false" @@ -398,7 +400,8 @@ const removeTagsAction = computed(() => { :aria-label="`Connect output ${output.name} to input. Press space to see a list of available inputs`" @click="toggleChildComponent"> - + + { } } +.invocation-terminal { + @include node-terminal-style(bottom); + + &:hover { + color: $brand-success; + } + + button:focus + .terminal-icon { + color: $brand-success; + } +} + .connection-menu-button { border: none; position: absolute; diff --git a/client/src/components/Workflow/Editor/WorkflowGraph.vue b/client/src/components/Workflow/Editor/WorkflowGraph.vue index 467ad460dd29..a3f7dfe5b337 100644 --- a/client/src/components/Workflow/Editor/WorkflowGraph.vue +++ b/client/src/components/Workflow/Editor/WorkflowGraph.vue @@ -31,6 +31,7 @@ :scroll="scroll" :scale="scale" :readonly="readonly" + :invocation="invocation" @pan-by="panBy" @stopDragging="onStopDragging" @onDragConnector="onDragConnector" @@ -93,6 +94,7 @@ const props = defineProps({ initialPosition: { type: Object as PropType<{ x: number; y: number }>, default: () => ({ x: 50, y: 20 }) }, showMinimap: { type: Boolean, default: true }, showZoomControls: { type: Boolean, default: true }, + invocation: { type: Boolean, default: false }, }); const { stateStore, stepStore } = useWorkflowStores(); diff --git a/client/src/components/Workflow/Editor/modules/model.ts b/client/src/components/Workflow/Editor/modules/model.ts index e8437f439032..b5e6cc2aee89 100644 --- a/client/src/components/Workflow/Editor/modules/model.ts +++ b/client/src/components/Workflow/Editor/modules/model.ts @@ -13,6 +13,11 @@ interface Workflow { tags: string[]; } +interface Invocation { + steps: Steps; + comments: WorkflowComment[]; +} + /** * Loads a workflow into the editor * @@ -23,7 +28,7 @@ interface Workflow { */ export async function fromSimple( id: string, - data: Workflow, + data: Workflow | Invocation, appendData = false, defaultPosition = { top: 0, left: 0 } ) { diff --git a/client/src/components/Workflow/Invocation/InvocationGraph.vue b/client/src/components/Workflow/Invocation/InvocationGraph.vue new file mode 100644 index 000000000000..56d9900498d9 --- /dev/null +++ b/client/src/components/Workflow/Invocation/InvocationGraph.vue @@ -0,0 +1,349 @@ + + + + + + + + + + {{ errorMessage }} + + Unknown Error + + + + + + + + + + + + diff --git a/client/src/components/Workflow/icons.js b/client/src/components/Workflow/icons.js index fda447092b49..8014bf72d573 100644 --- a/client/src/components/Workflow/icons.js +++ b/client/src/components/Workflow/icons.js @@ -11,4 +11,5 @@ export default { subworkflow: "fa-sitemap fa-rotate-270", parameter_input: "fa-pencil-alt", // fa-pencil for older FontAwesome, fa-pencil-alt for newer pause: "fa-pause", + history_item: "fa-folder", }; diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue index d53391b83d21..41d72916aa37 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue @@ -3,6 +3,7 @@ import { useWorkflowInstance } from "@/composables/useWorkflowInstance"; import ParameterStep from "./ParameterStep.vue"; import WorkflowInvocationStep from "./WorkflowInvocationStep.vue"; +import InvocationGraph from "@/components/Workflow/Invocation/InvocationGraph.vue"; import GenericHistoryItem from "components/History/Content/GenericItem.vue"; const props = defineProps({ @@ -66,6 +67,11 @@ function dataInputStepLabel(key: number, input: HasSrc) { :workflow="workflow" :workflow-step="step" /> + + + diff --git a/client/src/stores/workflowStepStore.ts b/client/src/stores/workflowStepStore.ts index c696f3141ad9..f73ea25ffa67 100644 --- a/client/src/stores/workflowStepStore.ts +++ b/client/src/stores/workflowStepStore.ts @@ -102,7 +102,7 @@ export interface NewStep { tool_state: Record; tool_version?: string; tooltip?: string; - type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause"; + type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause" | "history_item"; uuid?: string; when?: string | null; workflow_outputs?: WorkflowOutput[]; @@ -398,6 +398,7 @@ function stepToConnections(step: Step): Connection[] { }); }); } + console.log("awan Connections for step", step.id, connections); return connections; } diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 2bd7c95e6add..bc08ddf4e92b 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -862,7 +862,7 @@ def _workflow_from_raw_description( return workflow, missing_tool_tups - def workflow_to_dict(self, trans, stored, style="export", version=None, history=None): + def workflow_to_dict(self, trans, stored, style="export", version=None, history=None, invocation_id=None): """Export the workflow contents to a dictionary ready for JSON-ification and to be sent out via API for instance. There are three styles of export allowed 'export', 'instance', and 'editor'. The Galaxy team will do its best to preserve the backward compatibility of the @@ -883,7 +883,7 @@ def to_format_2(wf_dict, **kwds): if style == "export": style = self.app.config.default_workflow_export_format if style == "editor": - wf_dict = self._workflow_to_dict_editor(trans, stored, workflow) + wf_dict = self._workflow_to_dict_editor(trans, stored, workflow, invocation_id=invocation_id) elif style == "legacy": wf_dict = self._workflow_to_dict_instance(trans, stored, workflow=workflow, legacy=True) elif style == "instance": @@ -1170,7 +1170,7 @@ def _workflow_resource_parameters(self, trans, stored, workflow): """Get workflow scheduling resource parameters for this user and workflow or None if not configured.""" return self._resource_mapper_function(trans=trans, stored_workflow=stored, workflow=workflow) - def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True, is_subworkflow=False): + def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True, is_subworkflow=False, invocation_id=None): # Pack workflow data into a dictionary and return data = {} data["name"] = workflow.name @@ -1183,6 +1183,10 @@ def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True, is_sub data["annotation"] = self.get_item_annotation_str(trans.sa_session, trans.user, stored) or "" data["comments"] = [comment.to_dict() for comment in workflow.comments] + invocation = None + if invocation_id: + invocation = self.app.workflow_manager.get_invocation(trans, trans.security.decode_id(invocation_id)) + output_label_index = set() input_step_types = set(workflow.input_step_types) # For each step, rebuild the form and encode the state diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 8cf8d5f629bc..e5e4f00f1a7c 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -552,7 +552,7 @@ def editor(self, trans, id=None, workflow_id=None, version=None, **kwargs): return editor_config @web.json - def load_workflow(self, trans, id, version=None, **kwargs): + def load_workflow(self, trans, id, version=None, invocation_id=None, **kwargs): """ Get the latest Workflow for the StoredWorkflow identified by `id` and encode it as a json string that can be read by the workflow editor @@ -561,8 +561,7 @@ def load_workflow(self, trans, id, version=None, **kwargs): trans.workflow_building_mode = workflow_building_modes.ENABLED stored = self.get_stored_workflow(trans, id, check_ownership=False, check_accessible=True) workflow_contents_manager = self.app.workflow_contents_manager - return workflow_contents_manager.workflow_to_dict(trans, stored, style="editor", version=version) - + return workflow_contents_manager.workflow_to_dict(trans, stored, style="editor", version=version, invocation_id=invocation_id) @web.json_pretty def for_direct_import(self, trans, id, **kwargs): """