From f0c83764493e2b4768dbb50ec40b3f8c57529485 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:02:08 +0100 Subject: [PATCH 01/20] remove double label in form default --- client/src/components/Workflow/Editor/Forms/FormDefault.vue | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.vue b/client/src/components/Workflow/Editor/Forms/FormDefault.vue index 8ac56fd941da..3a0f82c0a9d6 100644 --- a/client/src/components/Workflow/Editor/Forms/FormDefault.vue +++ b/client/src/components/Workflow/Editor/Forms/FormDefault.vue @@ -97,9 +97,6 @@ const { stepId, contentId, annotation, label, name, type, configForm } = useStep const { stepStore } = useWorkflowStores(); const uniqueErrorLabel = useUniqueLabelError(stepStore, label.value); const stepTitle = computed(() => { - if (label.value) { - return label.value; - } if (isSubworkflow.value) { return name.value; } else { From cf9b3027a73d912cdede62d12f2233e0ff5c4a92 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:46:17 +0100 Subject: [PATCH 02/20] remove workflows and inputs from editor config --- client/src/components/Panels/ToolBox.vue | 1 - client/src/components/Panels/ToolPanel.vue | 2 - .../src/components/Workflow/Editor/Index.vue | 5 -- .../entry/analysis/modules/WorkflowEditor.vue | 1 - .../webapps/galaxy/controllers/workflow.py | 49 ------------------- lib/galaxy/workflow/modules.py | 17 ------- 6 files changed, 75 deletions(-) diff --git a/client/src/components/Panels/ToolBox.vue b/client/src/components/Panels/ToolBox.vue index 50e5aeb10ac7..7bc882468056 100644 --- a/client/src/components/Panels/ToolBox.vue +++ b/client/src/components/Panels/ToolBox.vue @@ -33,7 +33,6 @@ const props = defineProps({ panelView: { type: String, required: true }, showAdvanced: { type: Boolean, default: false, required: true }, panelQuery: { type: String, required: true }, - editorWorkflows: { type: Array, default: null }, dataManagers: { type: Array, default: null }, moduleSections: { type: Array as PropType>, default: null }, }); diff --git a/client/src/components/Panels/ToolPanel.vue b/client/src/components/Panels/ToolPanel.vue index fc9b9db2aa60..55bc38f28694 100644 --- a/client/src/components/Panels/ToolPanel.vue +++ b/client/src/components/Panels/ToolPanel.vue @@ -17,7 +17,6 @@ import Heading from "@/components/Common/Heading.vue"; const props = defineProps({ workflow: { type: Boolean, default: false }, - editorWorkflows: { type: Array, default: null }, dataManagers: { type: Array, default: null }, moduleSections: { type: Array, default: null }, }); @@ -198,7 +197,6 @@ watch( :panel-query.sync="query" :panel-view="currentPanelView" :show-advanced.sync="showAdvanced" - :editor-workflows="editorWorkflows" :data-managers="dataManagers" :module-sections="moduleSections" @updatePanelView="updatePanelView" diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 45225d211533..5994cdaea67d 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -47,7 +47,6 @@ workflow :module-sections="moduleSections" :data-managers="dataManagers" - :editor-workflows="workflows" @onInsertTool="onInsertTool" @onInsertModule="onInsertModule" @onInsertWorkflow="onInsertWorkflow" @@ -265,10 +264,6 @@ export default { type: Array, required: true, }, - workflows: { - type: Array, - required: true, - }, }, setup(props, { emit }) { const { datatypes, datatypesMapper, datatypesMapperLoading } = useDatatypesMapper(); diff --git a/client/src/entry/analysis/modules/WorkflowEditor.vue b/client/src/entry/analysis/modules/WorkflowEditor.vue index fb8ed6eea9d5..67634f4a3842 100644 --- a/client/src/entry/analysis/modules/WorkflowEditor.vue +++ b/client/src/entry/analysis/modules/WorkflowEditor.vue @@ -7,7 +7,6 @@ :initial-version="editorConfig.initialVersion" :module-sections="editorConfig.moduleSections" :workflow-tags="editorConfig.tags" - :workflows="editorConfig.workflows" @update:confirmation="$emit('update:confirmation', $event)" /> + + + + diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 5994cdaea67d..7f03c1e3773e 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -51,6 +51,10 @@ @onInsertModule="onInsertModule" @onInsertWorkflow="onInsertWorkflow" @onInsertWorkflowSteps="onInsertWorkflowSteps" /> + { - const updatedStep = { - ...stepData, - tool_state: response.tool_state, - inputs: response.inputs, - outputs: response.outputs, - config_form: response.config_form, - }; + const response = await getModule( + { name, type, content_id: contentId, tool_state: state }, + stepData.id, + this.stateStore.setLoadingState + ); - this.stepStore.updateStep(updatedStep); - action.updateStepData = updatedStep; + const updatedStep = { + ...stepData, + tool_state: response.tool_state, + inputs: response.inputs, + outputs: response.outputs, + config_form: response.config_form, + }; - this.stateStore.activeNodeId = stepData.id; - } - ); + this.stepStore.updateStep(updatedStep); + action.updateStepData = updatedStep; + + this.stateStore.activeNodeId = stepData.id; }, async _loadEditorData(data) { if (data.name !== undefined) { diff --git a/client/src/components/Workflow/Editor/modules/activities.ts b/client/src/components/Workflow/Editor/modules/activities.ts index aed37909dea2..5a5534a4af0e 100644 --- a/client/src/components/Workflow/Editor/modules/activities.ts +++ b/client/src/components/Workflow/Editor/modules/activities.ts @@ -13,6 +13,7 @@ import { faWrench, } from "@fortawesome/free-solid-svg-icons"; import { watchImmediate } from "@vueuse/core"; +import { faDiagramNext } from "font-awesome-6"; import { computed, type Ref } from "vue"; import { type Activity, useActivityStore } from "@/stores/activityStore"; @@ -27,6 +28,15 @@ export const workflowEditorActivities = [ icon: faPencilAlt, visible: true, }, + { + title: "Inputs", + id: "workflow-editor-inputs", + tooltip: "Add input steps to your workflow", + description: "Add input steps to your workflow.", + icon: faDiagramNext, + panel: true, + visible: true, + }, { title: "Tools", id: "workflow-editor-tools", diff --git a/client/src/components/Workflow/Editor/modules/inputs.ts b/client/src/components/Workflow/Editor/modules/inputs.ts new file mode 100644 index 000000000000..1e299c4a5bd4 --- /dev/null +++ b/client/src/components/Workflow/Editor/modules/inputs.ts @@ -0,0 +1,84 @@ +import { faFile, faFolder } from "@fortawesome/free-regular-svg-icons"; +import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"; +import { type IconDefinition } from "font-awesome-6"; + +export interface WorkflowInput { + id: string; + title: string; + description: string; + stateOverwrites?: { + parameter_type?: "text" | "integer" | "boolean" | "color" | "float" | "directory_uri"; + }; + icon: IconDefinition; +} + +export function getWorkflowInputs(): WorkflowInput[] { + return [ + { + id: "data_input", + title: "Input Dataset", + description: "Single dataset input", + icon: faFile, + }, + { + id: "data_collection_input", + title: "Input Dataset Collection", + description: "Input for a collection of datasets", + icon: faFolder, + }, + { + id: "parameter_input", + title: "Text Input", + description: "Text parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "text", + }, + }, + { + id: "parameter_input", + title: "Integer Input", + description: "Whole number parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "integer", + }, + }, + { + id: "parameter_input", + title: "Float Input", + description: "Imprecise decimal number parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "float", + }, + }, + { + id: "parameter_input", + title: "Boolean Input", + description: "True / False parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "boolean", + }, + }, + { + id: "parameter_input", + title: "Color Input", + description: "Color parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "color", + }, + }, + { + id: "parameter_input", + title: "Directory Input", + description: "Directory parameter used for workflow logic", + icon: faPencilAlt, + stateOverwrites: { + parameter_type: "directory_uri", + }, + }, + ]; +} diff --git a/client/yarn.lock b/client/yarn.lock index 63fb0ca4c55e..cd6c4bf5f55a 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1456,6 +1456,11 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073" integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ== +"@fortawesome/fontawesome-common-types@6.7.1": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.1.tgz#6201640f39fdcf8e41cd9d1a92b2da3a96817fa4" + integrity sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ== + "@fortawesome/fontawesome-common-types@^0.2.36": version "0.2.36" resolved "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz" @@ -6335,6 +6340,13 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +"font-awesome-6@npm:@fortawesome/free-solid-svg-icons@6": + version "6.7.1" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.1.tgz#c1f9a6c25562a12c283e87e284f9d82a5b0dbcc0" + integrity sha512-BTKc0b0mgjWZ2UDKVgmwaE0qt0cZs6ITcDgjrti5f/ki7aF5zs+N91V6hitGo3TItCFtnKg6cUVGdTmBFICFRg== + dependencies: + "@fortawesome/fontawesome-common-types" "6.7.1" + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 9348e25a977b..b52d495d40e6 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -531,12 +531,12 @@ def build_module(self, trans: GalaxyWebTransaction, payload=None): inputs = payload.get("inputs", {}) trans.workflow_building_mode = workflow_building_modes.ENABLED module = module_factory.from_dict(trans, payload, from_tool_form=True) - if "tool_state" not in payload: - module_state: Dict[str, Any] = {} - errors: ParameterValidationErrorsT = {} - populate_state(trans, module.get_inputs(), inputs, module_state, errors=errors, check=True) - module.recover_state(module_state, from_tool_form=True) - module.check_and_update_state() + + module_state: Dict[str, Any] = {} + errors: ParameterValidationErrorsT = {} + populate_state(trans, module.get_inputs(), inputs, module_state, errors=errors, check=True) + module.recover_state(module_state, from_tool_form=True) + module.check_and_update_state() step_dict = { "name": module.get_name(), "tool_state": module_state, From 73096b4e73b3348918c8d3685a066d4fa81dbd47 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 3 Dec 2024 11:56:40 -0500 Subject: [PATCH 04/20] Only allow moving activity bar icons when editing the bar. Add small visual indicator that they can be moved. --- .../src/components/ActivityBar/ActivityBar.vue | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/client/src/components/ActivityBar/ActivityBar.vue b/client/src/components/ActivityBar/ActivityBar.vue index 5371d14f84fe..aee942c40ee9 100644 --- a/client/src/components/ActivityBar/ActivityBar.vue +++ b/client/src/components/ActivityBar/ActivityBar.vue @@ -189,6 +189,10 @@ function setActiveSideBar(key: string) { activityStore.toggledSideBar = key; } +const canDrag = computed(() => { + return isActiveSideBar("settings"); +}); + defineExpose({ isActiveSideBar, setActiveSideBar, @@ -209,6 +213,7 @@ defineExpose({ -
+
From 66565dbcbbf7b0316c6583c287b37efb717fb47d Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sun, 1 Dec 2024 22:11:57 -0500 Subject: [PATCH 05/20] Fix connection from best practice panel to attributes. --- .../src/components/Workflow/Editor/Index.vue | 13 ++- .../src/components/Workflow/Editor/Lint.vue | 32 +++--- .../Workflow/Editor/WorkflowAttributes.vue | 107 +++++++++++++++++- .../Workflow/Editor/modules/linting.ts | 7 ++ 4 files changed, 139 insertions(+), 20 deletions(-) diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 45225d211533..31365fa3d4f6 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -60,7 +60,11 @@ :license="license" :steps="steps" :datatypes-mapper="datatypesMapper" - @onAttributes="showAttributes" + @onAttributes=" + (e) => { + showAttributes(e); + } + " @onHighlight="onHighlight" @onUnhighlight="onUnhighlight" @onRefactor="onAttemptRefactor" @@ -75,6 +79,7 @@ v-else-if="isActiveSideBar('workflow-editor-attributes')" :id="id" :tags="tags" + :highlight="highlightAttribute" :parameters="parameters" :annotation="annotation" :name="name" @@ -296,10 +301,13 @@ export default { parameters.value = getUntypedWorkflowParameters(steps.value); } - function showAttributes() { + function showAttributes(args) { ensureParametersSet(); stateStore.activeNodeId = null; activityBar.value?.setActiveSideBar("workflow-editor-attributes"); + if (args.highlight) { + this.highlightAttribute = args.highlight; + } } const name = ref("Unnamed Workflow"); @@ -521,6 +529,7 @@ export default { refactorActions: [], scrollToId: null, highlightId: null, + highlightAttribute: null, messageTitle: null, messageBody: null, messageIsError: false, diff --git a/client/src/components/Workflow/Editor/Lint.vue b/client/src/components/Workflow/Editor/Lint.vue index 3cb265c9d90c..88d3781ccca4 100644 --- a/client/src/components/Workflow/Editor/Lint.vue +++ b/client/src/components/Workflow/Editor/Lint.vue @@ -7,26 +7,21 @@ :okay="checkAnnotation" success-message="This workflow is annotated. Ideally, this helps the executors of the workflow understand the purpose and usage of the workflow." - warning-message="This workflow is not annotated. Providing an annotation helps workflow executors - understand the purpose and usage of the workflow." + :warning-message="bestPracticeWarningAnnotation" attribute-link="Annotate your Workflow." - @onClick="onAttributes" /> + @onClick="onAttributes('annotation')" /> + @onClick="onAttributes('creator')" /> + @onClick="onAttributes('license')" />
-
+
Annotation
These notes will be visible when this workflow is viewed.
+ +
-
+
License - + + +
-
+
Creator - + + +
Tags @@ -60,6 +99,11 @@ import { format, parseISO } from "date-fns"; import { Services } from "@/components/Workflow/services"; +import { + bestPracticeWarningAnnotation, + bestPracticeWarningCreator, + bestPracticeWarningLicense, +} from "./modules/linting"; import { UntypedParameters } from "./modules/parameters"; import LicenseSelector from "@/components/License/LicenseSelector.vue"; @@ -67,6 +111,8 @@ import ActivityPanel from "@/components/Panels/ActivityPanel.vue"; import CreatorEditor from "@/components/SchemaOrg/CreatorEditor.vue"; import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue"; +const bestPracticeHighlightTime = 10000; + export default { name: "WorkflowAttributes", components: { @@ -84,6 +130,10 @@ export default { type: String, default: null, }, + highlight: { + type: String, + default: null, + }, tags: { type: Array, required: true, @@ -115,11 +165,17 @@ export default { }, data() { return { + bestPracticeWarningAnnotation: bestPracticeWarningAnnotation, + bestPracticeWarningCreator: bestPracticeWarningCreator, + bestPracticeWarningLicense: bestPracticeWarningLicense, message: null, messageVariant: null, versionCurrent: this.version, annotationCurrent: this.annotation, nameCurrent: this.name, + showAnnotationHightlight: false, + showLicenseHightlight: false, + showCreatorHightlight: false, }; }, computed: { @@ -179,6 +235,36 @@ export default { name() { this.nameCurrent = this.name; }, + highlight: { + immediate: true, + handler(newHighlight, oldHighlight) { + if (newHighlight == oldHighlight) { + return; + } + if (newHighlight == "annotation") { + this.showAnnotationHightlight = true; + this.showCreatorHightlight = false; + this.showLicenseHightlight = false; + setTimeout(() => { + this.showAnnotationHightlight = false; + }, bestPracticeHighlightTime); + } else if (newHighlight == "creator") { + this.showAnnotationHightlight = false; + this.showCreatorHightlight = true; + this.showLicenseHightlight = false; + setTimeout(() => { + this.showCreatorHightlight = false; + }, bestPracticeHighlightTime); + } else if (newHighlight == "license") { + this.showAnnotationHightlight = false; + this.showCreatorHightlight = false; + this.showLicenseHightlight = true; + setTimeout(() => { + this.showLicenseHightlight = false; + }, bestPracticeHighlightTime); + } + }, + }, }, created() { this.services = new Services(); @@ -211,3 +297,14 @@ export default { }, }; + + diff --git a/client/src/components/Workflow/Editor/modules/linting.ts b/client/src/components/Workflow/Editor/modules/linting.ts index 21d02cafff88..75a7c142ab43 100644 --- a/client/src/components/Workflow/Editor/modules/linting.ts +++ b/client/src/components/Workflow/Editor/modules/linting.ts @@ -15,6 +15,13 @@ interface LintState { autofix?: boolean; } +export const bestPracticeWarningAnnotation = + "This workflow is not annotated. Providing an annotation helps workflow executors understand the purpose and usage of the workflow."; +export const bestPracticeWarningCreator = + "This workflow does not specify creator(s). This is important metadata for workflows that will be published and/or shared to help workflow executors know how to cite the workflow authors."; +export const bestPracticeWarningLicense = + "This workflow does not specify a license. This is important metadata for workflows that will be published and/or shared to help workflow executors understand how it may be used."; + export function getDisconnectedInputs( steps: Steps = {}, datatypesMapper: DatatypesMapperModel, From fa384b7ed0f10883933494dfc369fdea7b31c855 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:27:33 +0100 Subject: [PATCH 06/20] add missing required prop to test --- .../src/components/Workflow/Editor/Forms/FormDefault.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.test.js b/client/src/components/Workflow/Editor/Forms/FormDefault.test.js index 2a09dc0d7963..7fb84f507636 100644 --- a/client/src/components/Workflow/Editor/Forms/FormDefault.test.js +++ b/client/src/components/Workflow/Editor/Forms/FormDefault.test.js @@ -24,6 +24,7 @@ describe("FormDefault", () => { contentId: "id", annotation: "annotation", label: "label", + name: "name", type: "subworkflow", configForm: { inputs: [], @@ -42,7 +43,7 @@ describe("FormDefault", () => { it("check initial value and value change", async () => { const title = wrapper.find(".portlet-title-text").text(); - expect(title).toBe("label"); + expect(title).toBe("name"); const inputCount = wrapper.findAll("input").length; expect(inputCount).toBe(4); const outputLabelCount = wrapper.findAll("#__label__output-name").length; From d0da7e75e0fd8727082ddad431738f48eef63d6e Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:51:57 +0100 Subject: [PATCH 07/20] fix parameters inputs function --- client/src/components/Panels/InputPanel.vue | 7 +++--- .../Workflow/Editor/modules/inputs.ts | 24 ++++++++++++------- client/src/utils/navigation/navigation.yml | 5 ++++ lib/galaxy/selenium/navigates_galaxy.py | 9 ++++--- .../selenium/test_workflow_editor.py | 8 +++---- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/client/src/components/Panels/InputPanel.vue b/client/src/components/Panels/InputPanel.vue index f62dd45e5bf3..2150d969050a 100644 --- a/client/src/components/Panels/InputPanel.vue +++ b/client/src/components/Panels/InputPanel.vue @@ -20,11 +20,12 @@ const emit = defineEmits<{
diff --git a/client/src/components/Workflow/Editor/modules/inputs.ts b/client/src/components/Workflow/Editor/modules/inputs.ts index 1e299c4a5bd4..c152058d4360 100644 --- a/client/src/components/Workflow/Editor/modules/inputs.ts +++ b/client/src/components/Workflow/Editor/modules/inputs.ts @@ -3,7 +3,8 @@ import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"; import { type IconDefinition } from "font-awesome-6"; export interface WorkflowInput { - id: string; + id?: string; // unique ID. defaults to module ID + moduleId: string; title: string; description: string; stateOverwrites?: { @@ -15,19 +16,19 @@ export interface WorkflowInput { export function getWorkflowInputs(): WorkflowInput[] { return [ { - id: "data_input", + moduleId: "data_input", title: "Input Dataset", description: "Single dataset input", icon: faFile, }, { - id: "data_collection_input", + moduleId: "data_collection_input", title: "Input Dataset Collection", description: "Input for a collection of datasets", icon: faFolder, }, { - id: "parameter_input", + moduleId: "parameter_input", title: "Text Input", description: "Text parameter used for workflow logic", icon: faPencilAlt, @@ -36,7 +37,8 @@ export function getWorkflowInputs(): WorkflowInput[] { }, }, { - id: "parameter_input", + id: "parameter_input_integer", + moduleId: "parameter_input", title: "Integer Input", description: "Whole number parameter used for workflow logic", icon: faPencilAlt, @@ -45,7 +47,8 @@ export function getWorkflowInputs(): WorkflowInput[] { }, }, { - id: "parameter_input", + id: "parameter_input_float", + moduleId: "parameter_input", title: "Float Input", description: "Imprecise decimal number parameter used for workflow logic", icon: faPencilAlt, @@ -54,7 +57,8 @@ export function getWorkflowInputs(): WorkflowInput[] { }, }, { - id: "parameter_input", + id: "parameter_input_boolean", + moduleId: "parameter_input", title: "Boolean Input", description: "True / False parameter used for workflow logic", icon: faPencilAlt, @@ -63,7 +67,8 @@ export function getWorkflowInputs(): WorkflowInput[] { }, }, { - id: "parameter_input", + id: "parameter_input_color", + moduleId: "parameter_input", title: "Color Input", description: "Color parameter used for workflow logic", icon: faPencilAlt, @@ -72,7 +77,8 @@ export function getWorkflowInputs(): WorkflowInput[] { }, }, { - id: "parameter_input", + id: "parameter_input_directory_uri", + moduleId: "parameter_input", title: "Directory Input", description: "Directory parameter used for workflow logic", icon: faPencilAlt, diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index 0612bdf75aff..a20d2d82ecac 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -750,6 +750,11 @@ workflow_editor: freehand_comment: ".freehand-workflow-comment" freehand_path: ".freehand-workflow-comment path" delete: "button[title='Delete comment']" + inputs: + selectors: + activity_button: "#activity-workflow-editor-inputs" + activity_panel: ".activity-panel[data-description='Inputs']" + input: ".workflow-input-button[data-id='${id}']" selectors: node_inspector: '.tool-inspector' node_inspector_close: ".tool-inspector [title='close']" diff --git a/lib/galaxy/selenium/navigates_galaxy.py b/lib/galaxy/selenium/navigates_galaxy.py index 141d6e1c28ec..10abaf0ed8d5 100644 --- a/lib/galaxy/selenium/navigates_galaxy.py +++ b/lib/galaxy/selenium/navigates_galaxy.py @@ -1209,12 +1209,11 @@ def workflow_editor_add_input(self, item_name="data_input"): # Make sure we're on the workflow editor and not clicking the main tool panel. editor.canvas_body.wait_for_visible() - self.open_toolbox() - editor.tool_menu_section_link(section_name="inputs").wait_for_and_click() - editor.tool_menu_item_link(item_name=item_name).wait_for_and_click() + if editor.inputs.activity_panel.is_absent: + editor.inputs.activity_button.wait_for_and_click() - def workflow_editor_add_parameter_input(self): - self.workflow_editor_add_input(item_name="parameter_input") + editor.inputs.input(id=item_name).wait_for_and_click() + self.sleep_for(self.wait_types.UX_RENDER) def workflow_editor_set_license(self, license: str) -> None: license_selector = self.components.workflow_editor.license_selector diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index 229b76634b36..8ad6d81a606f 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -102,7 +102,7 @@ def test_parameter_regex_validation(self): parameter_name = "text_param" name = self.create_and_wait_for_new_workflow_in_editor() - self.workflow_editor_add_parameter_input() + self.workflow_editor_add_input("parameter_input_text") editor.label_input.wait_for_and_send_keys(parameter_name) # this really should be parameterized with the repeat name self.components.tool_form.repeat_insert.wait_for_and_click() @@ -131,7 +131,7 @@ def test_int_parameter_minimum_validation(self): parameter_name = "int_param" name = self.create_and_wait_for_new_workflow_in_editor() - self.workflow_editor_add_parameter_input() + self.workflow_editor_add_input("parameter_input_text") editor.label_input.wait_for_and_send_keys(parameter_name) select_field = self.components.tool_form.parameter_select(parameter="parameter_definition|parameter_type") self.select_set_value(select_field, "integer") @@ -157,10 +157,8 @@ def test_float_parameter_maximum_validation(self): parameter_name = "float_param" name = self.create_and_wait_for_new_workflow_in_editor() - self.workflow_editor_add_parameter_input() + self.workflow_editor_add_input("parameter_input_float") editor.label_input.wait_for_and_send_keys(parameter_name) - select_field = self.components.tool_form.parameter_select(parameter="parameter_definition|parameter_type") - self.select_set_value(select_field, "float") self.components.tool_form.parameter_input(parameter="parameter_definition|max").wait_for_and_send_keys("3.14") self.save_after_node_form_changes() From f1e3ba4d82d5b8a6879428b25a55547cbc480765 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:26:10 +0100 Subject: [PATCH 08/20] fix input id --- lib/galaxy_test/selenium/test_workflow_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy_test/selenium/test_workflow_editor.py b/lib/galaxy_test/selenium/test_workflow_editor.py index 8ad6d81a606f..5e1f37e2a984 100644 --- a/lib/galaxy_test/selenium/test_workflow_editor.py +++ b/lib/galaxy_test/selenium/test_workflow_editor.py @@ -102,7 +102,7 @@ def test_parameter_regex_validation(self): parameter_name = "text_param" name = self.create_and_wait_for_new_workflow_in_editor() - self.workflow_editor_add_input("parameter_input_text") + self.workflow_editor_add_input("parameter_input") editor.label_input.wait_for_and_send_keys(parameter_name) # this really should be parameterized with the repeat name self.components.tool_form.repeat_insert.wait_for_and_click() @@ -131,7 +131,7 @@ def test_int_parameter_minimum_validation(self): parameter_name = "int_param" name = self.create_and_wait_for_new_workflow_in_editor() - self.workflow_editor_add_input("parameter_input_text") + self.workflow_editor_add_input("parameter_input") editor.label_input.wait_for_and_send_keys(parameter_name) select_field = self.components.tool_form.parameter_select(parameter="parameter_definition|parameter_type") self.select_set_value(select_field, "integer") From f80c777717017d05f849f74d8ef860b55972b8c2 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 4 Dec 2024 10:27:09 -0500 Subject: [PATCH 09/20] ListPairs Usability - don't auto-pair on empty filters. Disable the button and make it clear in the message it has to do with empty filters. --- .../Collections/PairedListCollectionCreator.vue | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/components/Collections/PairedListCollectionCreator.vue b/client/src/components/Collections/PairedListCollectionCreator.vue index ba46d2139934..2f214b759e4a 100644 --- a/client/src/components/Collections/PairedListCollectionCreator.vue +++ b/client/src/components/Collections/PairedListCollectionCreator.vue @@ -93,6 +93,10 @@ const hasFilter = computed(() => forwardFilter.value || reverseFilter.value); const strategy = ref(autoPairLCS); const duplicatePairNames = ref([]); +const canAutoPair = computed(() => { + return forwardFilter.value && reverseFilter.value; +}); + const forwardElements = computed(() => { return filterElements(workingElements.value, forwardFilter.value); }); @@ -117,7 +121,11 @@ const autoPairButton = computed(() => { let variant; let icon; let text; - if (!firstAutoPairDone.value && pairableElements.value.length > 0) { + if (!canAutoPair.value) { + variant = "secondary"; + icon = faLink; + text = localize("Specify simple filters to divide datasets into forward and reverse reads for pairing."); + } else if (!firstAutoPairDone.value && pairableElements.value.length > 0) { variant = "primary"; icon = faExclamationCircle; text = localize("Click to auto-pair datasets based on the current filters"); @@ -1141,6 +1149,7 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) { Date: Wed, 4 Dec 2024 10:42:39 -0500 Subject: [PATCH 10/20] ListPairs Usability - use new Galaxy green for component. How is a shade of green so evocative of a time. --- .../components/Collections/PairedListCollectionCreator.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/components/Collections/PairedListCollectionCreator.vue b/client/src/components/Collections/PairedListCollectionCreator.vue index 2f214b759e4a..e6cd629ea75a 100644 --- a/client/src/components/Collections/PairedListCollectionCreator.vue +++ b/client/src/components/Collections/PairedListCollectionCreator.vue @@ -1318,6 +1318,8 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free/webfonts/"; @import "~@fortawesome/fontawesome-free/scss/solid"; @import "~@fortawesome/fontawesome-free/scss/fontawesome"; @import "~@fortawesome/fontawesome-free/scss/brands"; +@import "~bootstrap/scss/_functions.scss"; +@import "theme/blue.scss"; .paired-column { text-align: center; // mess with these two to make center more/scss priority @@ -1364,7 +1366,7 @@ li.dataset.paired { white-space: nowrap; overflow: hidden; border: 2px solid grey; - background: #aff1af; + background: $state-success-bg; text-align: center; span { display: inline-block; From 3e6b12b0dafc137bfc8e72112928f8ce166acb7c Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 4 Dec 2024 10:53:58 -0500 Subject: [PATCH 11/20] ListPairs Usability - don't attempt failed auto-pair initially. It results in hiding all the data and a scary red messages when the user did literally nothing wrong. I think this is single biggest issue - it makes it seem like you cannot just manually pair these quickly. --- .../components/Collections/PairedListCollectionCreator.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/components/Collections/PairedListCollectionCreator.vue b/client/src/components/Collections/PairedListCollectionCreator.vue index e6cd629ea75a..1287d64b7f01 100644 --- a/client/src/components/Collections/PairedListCollectionCreator.vue +++ b/client/src/components/Collections/PairedListCollectionCreator.vue @@ -243,8 +243,11 @@ function initialFiltersSet() { illumina++; } }); - - if (illumina > dot12s && illumina > Rs) { + // if we cannot filter don't set an initial filter and hide all the data + if (illumina == 0 && dot12s == 0 && Rs == 0) { + forwardFilter.value = ""; + reverseFilter.value = ""; + } else if (illumina > dot12s && illumina > Rs) { changeFilters("illumina"); } else if (dot12s > illumina && dot12s > Rs) { changeFilters("dot12s"); From 0cc2bab49bb0ad745d11af3885f18b4a7178b760 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 4 Dec 2024 11:05:55 -0500 Subject: [PATCH 12/20] ListPairs Usability - disable the clear filter button if nothing to clear. --- .../Collections/PairedListCollectionCreator.vue | 5 +++++ lib/galaxy/selenium/navigates_galaxy.py | 12 +++++++++--- lib/galaxy_test/selenium/test_collection_builders.py | 3 +-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/client/src/components/Collections/PairedListCollectionCreator.vue b/client/src/components/Collections/PairedListCollectionCreator.vue index 1287d64b7f01..d9150541ca90 100644 --- a/client/src/components/Collections/PairedListCollectionCreator.vue +++ b/client/src/components/Collections/PairedListCollectionCreator.vue @@ -93,6 +93,10 @@ const hasFilter = computed(() => forwardFilter.value || reverseFilter.value); const strategy = ref(autoPairLCS); const duplicatePairNames = ref([]); +const canClearFilters = computed(() => { + return forwardFilter.value || reverseFilter.value; +}); + const canAutoPair = computed(() => { return forwardFilter.value && reverseFilter.value; }); @@ -1144,6 +1148,7 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) { diff --git a/lib/galaxy/selenium/navigates_galaxy.py b/lib/galaxy/selenium/navigates_galaxy.py index 141d6e1c28ec..bccafc32e933 100644 --- a/lib/galaxy/selenium/navigates_galaxy.py +++ b/lib/galaxy/selenium/navigates_galaxy.py @@ -914,8 +914,7 @@ def upload_paired_list(self, test_paths, name="test", ext=None, genome=None, hid if not hide_source_items: self.collection_builder_hide_originals() - self.collection_builder_clear_filters() - # TODO: generalize and loop these clicks so we don't need the assert + self.ensure_collection_builder_filters_cleared() assert len(test_paths) == 2 self.collection_builder_click_paired_item("forward", 0) self.collection_builder_click_paired_item("reverse", 1) @@ -2053,8 +2052,15 @@ def collection_builder_hide_originals(self): def collection_builder_create(self): self.wait_for_and_click_selector("button.create-collection") + def ensure_collection_builder_filters_cleared(self): + clear_filters = self.components.collection_builders.clear_filters + element = clear_filters.wait_for_present() + if "disabled" not in element.get_attribute("class").split(" "): + self.collection_builder_clear_filters() + def collection_builder_clear_filters(self): - self.wait_for_and_click_selector("button.clear-filters-link") + clear_filters = self.components.collection_builders.clear_filters + clear_filters.wait_for_and_click() def collection_builder_click_paired_item(self, forward_or_reverse, item): assert forward_or_reverse in ["forward", "reverse"] diff --git a/lib/galaxy_test/selenium/test_collection_builders.py b/lib/galaxy_test/selenium/test_collection_builders.py index 24b47a825cf3..2c57e632e8b5 100644 --- a/lib/galaxy_test/selenium/test_collection_builders.py +++ b/lib/galaxy_test/selenium/test_collection_builders.py @@ -39,7 +39,6 @@ def test_build_paired_list_simple(self): self.perform_upload(self.get_filename("2.tabular")) self._wait_for_and_select([1, 2]) self._collection_dropdown("build list of pairs") - self.collection_builder_clear_filters() self.collection_builder_click_paired_item("forward", 0) self.collection_builder_click_paired_item("reverse", 1) self.collection_builder_set_name("my awesome paired list") @@ -61,7 +60,7 @@ def test_build_paired_list_show_original(self): self._wait_for_and_select([1, 2]) self._collection_dropdown("build list of pairs") collection_builders = self.components.collection_builders - collection_builders.clear_filters.wait_for_and_click() + self.ensure_collection_builder_filters_cleared() forward_column = collection_builders.forward_datasets.wait_for_visible() first_datset_forward = forward_column.find_elements(self.by.CSS_SELECTOR, "li")[0] first_datset_forward.click() From e017a72c0f8b82d3288fa4d966c005287b502be0 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:41:42 +0100 Subject: [PATCH 13/20] Fix CollectionCreatorModal being reopened on selection after creating a collection The CollectionCreatorModal.hideModal() function is only called on Cancel or when we explicitly set the prop hideModalOnCreate to true. If we don't set this prop, it leaves the `collectionModalShow` ref set to true as the event to change it to false never gets fired. --- .../CurrentHistory/HistoryOperations/SelectionOperations.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/History/CurrentHistory/HistoryOperations/SelectionOperations.vue b/client/src/components/History/CurrentHistory/HistoryOperations/SelectionOperations.vue index 28f55ff9385e..5463baab1b59 100644 --- a/client/src/components/History/CurrentHistory/HistoryOperations/SelectionOperations.vue +++ b/client/src/components/History/CurrentHistory/HistoryOperations/SelectionOperations.vue @@ -145,6 +145,7 @@ :filter-text="filterText" :selected-items="collectionSelection" :show-modal.sync="collectionModalShow" + hide-modal-on-create default-hide-source-items @created-collection="createdCollection" /> From 8c73b88eaa95fe43b8fd983707a00322a380126e Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 6 Dec 2024 09:32:21 -0500 Subject: [PATCH 14/20] Improve language in collection builders... --- .../Collections/CollectionCreatorModal.vue | 21 +++++++++++-------- .../Collections/ListCollectionCreator.vue | 2 +- .../Collections/PairCollectionCreator.vue | 3 ++- .../PairedListCollectionCreator.vue | 3 ++- .../Collections/common/CollectionCreator.vue | 18 ++++++++++++++-- .../Form/Elements/FormData/FormData.vue | 12 ++++++++++- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/client/src/components/Collections/CollectionCreatorModal.vue b/client/src/components/Collections/CollectionCreatorModal.vue index 334f6586e0da..8db25d243c6d 100644 --- a/client/src/components/Collections/CollectionCreatorModal.vue +++ b/client/src/components/Collections/CollectionCreatorModal.vue @@ -113,22 +113,25 @@ watch( } ); +const extensionInTitle = computed(() => { + const extensions = props.extensions; + if (!extensions || extensions.length == 0 || extensions.indexOf("data") >= 0) { + return ""; + } else { + return orList(extensions); + } +}); + const modalTitle = computed(() => { if (props.collectionType === "list") { - return localize( - `Create a collection from a list of ${fromSelection.value ? "selected" : ""} ${ - props.extensions?.length ? orList(props.extensions) : "" - } datasets` - ); + return localize(`Create a list of ${fromSelection.value ? "selected" : ""} ${extensionInTitle.value} datasets`); } else if (props.collectionType === "list:paired") { return localize( - `Create a collection of ${fromSelection.value ? "selected" : ""} ${ - props.extensions?.length ? orList(props.extensions) : "" - } dataset pairs` + `Create a list of ${fromSelection.value ? "selected" : ""} ${extensionInTitle.value} paired datasets` ); } else if (props.collectionType === "paired") { return localize( - `Create a ${props.extensions?.length ? orList(props.extensions) : ""} dataset pair collection ${ + `Create a ${extensionInTitle.value} paired dataset collection ${ fromSelection.value ? "from selected items" : "" }` ); diff --git a/client/src/components/Collections/ListCollectionCreator.vue b/client/src/components/Collections/ListCollectionCreator.vue index 841a206a3635..b68f573c76ae 100644 --- a/client/src/components/Collections/ListCollectionCreator.vue +++ b/client/src/components/Collections/ListCollectionCreator.vue @@ -412,13 +412,13 @@ function renameElement(element: any, name: string) { :history-id="props.historyId" :hide-source-items="hideSourceItems" :extensions="extensions" + collectionType="list" :no-items="props.initialElements.length == 0 && !props.fromSelection" @add-uploaded-files="addUploadedFiles" @on-update-datatype-toggle="changeDatatypeFilter" @onUpdateHideSourceItems="onUpdateHideSourceItems" @clicked-create="clickedCreate">