From 8d8f6cf84b493e5d85e8be4b356a33472106a000 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 2 Feb 2022 13:19:15 +0100 Subject: [PATCH 01/20] Fix construction of SelectToolParameter default value --- lib/galaxy/workflow/modules.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 586ae579358..79dd9b37db4 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -828,7 +828,7 @@ class InputParameterModule(WorkflowModule): name = "Input parameter" default_parameter_type = "text" default_optional = False - default_default_value = '' + default_default_value = None parameter_type = default_parameter_type optional = default_optional default_value = default_default_value @@ -1015,6 +1015,7 @@ class InputParameterModule(WorkflowModule): parameter_def = self._parse_state_into_dict() parameter_type = parameter_def["parameter_type"] optional = parameter_def["optional"] + default_value = parameter_def.get("default", self.default_default_value) if parameter_type not in ["text", "boolean", "integer", "float", "color"]: raise ValueError("Invalid parameter type for workflow parameters encountered.") @@ -1044,7 +1045,7 @@ class InputParameterModule(WorkflowModule): options = None if static_options and len(static_options) == 1: # If we are connected to a single option, just use it as is so order is preserved cleanly and such. - options = [{"label": o[0], "value": o[1]} for o in static_options[0]] + options = [{"label": o[0], "value": o[1], "selected": default_value and o[1] == default_value} for o in static_options[0]] elif static_options: # Intersection based on values of multiple option connections. intxn_vals = set.intersection(*({option[1] for option in options} for options in static_options)) @@ -1052,7 +1053,7 @@ class InputParameterModule(WorkflowModule): d = defaultdict(set) # Collapse labels with same values for label, value, _ in intxn_opts: d[value].add(label) - options = [{"label": ', '.join(label), "value": value, "selected": False} for value, label in d.items()] + options = [{"label": ', '.join(label), "value": value, "selected": default_value and value == default_value} for value, label in d.items()] if options is not None: parameter_kwds["options"] = options @@ -1089,8 +1090,10 @@ class InputParameterModule(WorkflowModule): parameter_class = parameter_types[client_parameter_type] if optional: - default_value = parameter_def.get("default", self.default_default_value) - parameter_kwds["value"] = default_value + if client_parameter_type == "select": + parameter_kwds['selected'] = default_value + else: + parameter_kwds["value"] = default_value if parameter_type == 'boolean': parameter_kwds['checked'] = default_value From 6b65de8f3859ed94ab19289a8c6440a2c4321454 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 2 Feb 2022 15:35:25 +0100 Subject: [PATCH 02/20] Add selenium test for restrictOnConnections --- lib/galaxy/selenium/navigation.yml | 3 +++ lib/galaxy_test/selenium/test_workflow_run.py | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/galaxy/selenium/navigation.yml b/lib/galaxy/selenium/navigation.yml index ebc97f3e2b7..debfd06a3d9 100644 --- a/lib/galaxy/selenium/navigation.yml +++ b/lib/galaxy/selenium/navigation.yml @@ -526,6 +526,9 @@ workflow_run: subworkflow_step_icon: ".portlet-title-icon.fa-sitemap" run_workflow: "#run-workflow" validation_error: ".validation-error" + input_select_field: + type: xpath + selector: '//div[@data-label="${label}"]//span[@class="select2-chosen"]' workflow_editor: diff --git a/lib/galaxy_test/selenium/test_workflow_run.py b/lib/galaxy_test/selenium/test_workflow_run.py index c8197651146..e955fc829ea 100644 --- a/lib/galaxy_test/selenium/test_workflow_run.py +++ b/lib/galaxy_test/selenium/test_workflow_run.py @@ -188,6 +188,32 @@ steps: content = self.dataset_populator.get_history_dataset_content(history_id, hid=7) self.assertEqual("10.0\n30.0\n20.0\n40.0\n", content) + @selenium_test + @managed_history + def test_execution_with_text_default_value_connected_to_restricted_select(self): + self.open_in_workflow_run(""" +class: GalaxyWorkflow +inputs: + text_param: + optional: true + default: ex2 + restrictOnConnections: true + type: text +steps: + multi_select: + tool_id: multi_select + in: + select_ex: + source: text_param +""") + element = self.components.workflow_run.input_select_field(label="text_param").wait_for_present() + assert element.text == 'Ex2' + self.workflow_run_submit() + history_id = self.current_history_id() + self.workflow_populator.wait_for_history_workflows(history_id, expected_invocation_count=1) + content = self.dataset_populator.get_history_dataset_content(history_id, hid=1) + assert content == 'ex2' + @selenium_test @managed_history def test_execution_with_rules(self): From 51d728b4c2cb437f06e280cd4343984b8ec9bbd2 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 4 Feb 2022 14:02:52 +0100 Subject: [PATCH 03/20] Move option restriction to separate method --- lib/galaxy/model/__init__.py | 2 +- lib/galaxy/workflow/modules.py | 75 ++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6e7b6ff84bb..6af18041e32 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -6695,7 +6695,7 @@ class WorkflowStepConnection(Base, RepresentById): return self.input_step_input.name @property - def input_step(self): + def input_step(self) -> Union[WorkflowStep, None]: return self.input_step_input and self.input_step_input.workflow_step @property diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 79dd9b37db4..262c9bf961e 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -5,7 +5,7 @@ import json import logging import re from collections import defaultdict -from typing import Any, cast, Dict, List, Optional, Union +from typing import Any, cast, Dict, Iterable, List, Optional, Union import packaging.version from typing_extensions import TypedDict @@ -17,7 +17,7 @@ from galaxy import ( ) from galaxy.exceptions import ToolMissingException from galaxy.job_execution.actions.post import ActionBox -from galaxy.model import PostJobAction, Workflow +from galaxy.model import PostJobAction, Workflow, WorkflowStepConnection from galaxy.model.dataset_collections import matching from galaxy.tool_util.parser.output_objects import ToolExpressionOutput from galaxy.tools import ( @@ -1011,7 +1011,38 @@ class InputParameterModule(WorkflowModule): parameter_type_cond.cases = cases return {"parameter_definition": parameter_type_cond} - def get_runtime_inputs(self, connections=None, **kwds): + def restrict_options(self, connections: Iterable[WorkflowStepConnection], default_value): + try: + static_options = [] + # Retrieve possible runtime options for 'select' type inputs + for connection in connections: + # Well this isn't a great assumption... + module = connection.input_step.module # type: ignore[union-attr] + tool_inputs = module.tool.inputs # may not be set, but we're catching the Exception below. + + def callback(input, prefixed_name, context, **kwargs): + if prefixed_name == connection.input_name and hasattr(input, 'get_options'): + static_options.append(input.get_options(self.trans, {})) + visit_input_values(tool_inputs, module.state.inputs, callback) + + options = None + if static_options and len(static_options) == 1: + # If we are connected to a single option, just use it as is so order is preserved cleanly and such. + options = [{"label": o[0], "value": o[1], "selected": default_value and o[1] == default_value} for o in static_options[0]] + elif static_options: + # Intersection based on values of multiple option connections. + intxn_vals = set.intersection(*({option[1] for option in options} for options in static_options)) + intxn_opts = {option for options in static_options for option in options if option[1] in intxn_vals} + d = defaultdict(set) # Collapse labels with same values + for label, value, _ in intxn_opts: + d[value].add(label) + options = [{"label": ', '.join(label), "value": value, "selected": default_value and value == default_value} for value, label in d.items()] + + return options + except Exception: + log.debug("Failed to generate options for text parameter, falling back to free text.", exc_info=True) + + def get_runtime_inputs(self, connections: Optional[Iterable[WorkflowStepConnection]] = None, **kwds): parameter_def = self._parse_state_into_dict() parameter_type = parameter_def["parameter_type"] optional = parameter_def["optional"] @@ -1028,38 +1059,12 @@ class InputParameterModule(WorkflowModule): # Really is just an attempt - tool module may not be available (small problem), get_options may really depend on other # values we are not setting, so this isn't great. Be sure to just fallback to text in this case. attemptRestrictOnConnections = is_text and parameter_def.get("restrictOnConnections") and connections - try: - if attemptRestrictOnConnections: - static_options = [] - # Retrieve possible runtime options for 'select' type inputs - for connection in connections: - # Well this isn't a great assumption... - module = connection.input_step.module - tool_inputs = module.tool.inputs # may not be set, but we're catching the Exception below. - - def callback(input, prefixed_name, context, **kwargs): - if prefixed_name == connection.input_name and hasattr(input, 'get_options'): - static_options.append(input.get_options(self.trans, {})) - visit_input_values(tool_inputs, module.state.inputs, callback) - - options = None - if static_options and len(static_options) == 1: - # If we are connected to a single option, just use it as is so order is preserved cleanly and such. - options = [{"label": o[0], "value": o[1], "selected": default_value and o[1] == default_value} for o in static_options[0]] - elif static_options: - # Intersection based on values of multiple option connections. - intxn_vals = set.intersection(*({option[1] for option in options} for options in static_options)) - intxn_opts = {option for options in static_options for option in options if option[1] in intxn_vals} - d = defaultdict(set) # Collapse labels with same values - for label, value, _ in intxn_opts: - d[value].add(label) - options = [{"label": ', '.join(label), "value": value, "selected": default_value and value == default_value} for value, label in d.items()] - - if options is not None: - parameter_kwds["options"] = options - restricted_inputs = True - except Exception: - log.debug("Failed to generate options for text parameter, falling back to free text.", exc_info=True) + if attemptRestrictOnConnections: + connections = cast(Iterable[WorkflowStepConnection], connections) + restricted_options = self.restrict_options(connections=connections, default_value=default_value) + if restricted_options is not None: + restricted_inputs = True + parameter_kwds["options"] = restricted_options def _parameter_def_list_to_options(parameter_value): options = [] From 8da2f9eb018a9ffb4a70c3a5ce3d93d76fc6392c Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 4 Feb 2022 15:16:32 +0100 Subject: [PATCH 04/20] Produce boolean --- lib/galaxy/workflow/modules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 262c9bf961e..0bfa02e03dd 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -1028,7 +1028,7 @@ class InputParameterModule(WorkflowModule): options = None if static_options and len(static_options) == 1: # If we are connected to a single option, just use it as is so order is preserved cleanly and such. - options = [{"label": o[0], "value": o[1], "selected": default_value and o[1] == default_value} for o in static_options[0]] + options = [{"label": o[0], "value": o[1], "selected": bool(default_value and o[1] == default_value)} for o in static_options[0]] elif static_options: # Intersection based on values of multiple option connections. intxn_vals = set.intersection(*({option[1] for option in options} for options in static_options)) @@ -1036,7 +1036,7 @@ class InputParameterModule(WorkflowModule): d = defaultdict(set) # Collapse labels with same values for label, value, _ in intxn_opts: d[value].add(label) - options = [{"label": ', '.join(label), "value": value, "selected": default_value and value == default_value} for value, label in d.items()] + options = [{"label": ', '.join(label), "value": value, "selected": bool(default_value and value == default_value)} for value, label in d.items()] return options except Exception: From 43474de3682bd862a2774d8a642cf2be6f837876 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Fri, 4 Feb 2022 15:33:32 +0100 Subject: [PATCH 05/20] Use Optional instead of Union with None MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David López <46503462+davelopez@users.noreply.github.com> --- lib/galaxy/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6af18041e32..ffbc61dedde 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -6695,7 +6695,7 @@ class WorkflowStepConnection(Base, RepresentById): return self.input_step_input.name @property - def input_step(self) -> Union[WorkflowStep, None]: + def input_step(self) -> Optional[WorkflowStep]: return self.input_step_input and self.input_step_input.workflow_step @property From 87337bad17c2cc7698a3ad150def0a1cf4ebc273 Mon Sep 17 00:00:00 2001 From: Matthias Bernt Date: Wed, 9 Feb 2022 19:16:13 +0100 Subject: [PATCH 06/20] allow univa destinations wo nativespec if no nativespec is given for a univa destination the runner does not know how much run time / memory was given to a job which led to a comparison with `None`. Now we default to infinite runtime and memory. --- lib/galaxy/jobs/runners/univa.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/jobs/runners/univa.py b/lib/galaxy/jobs/runners/univa.py index 645a325dc2e..21999e831fa 100644 --- a/lib/galaxy/jobs/runners/univa.py +++ b/lib/galaxy/jobs/runners/univa.py @@ -32,6 +32,7 @@ import logging import re import signal import time +from math import inf from galaxy.jobs.runners.drmaa import DRMAAJobRunner from galaxy.util import ( @@ -566,8 +567,8 @@ def _parse_native_specs(job_id, native_spec): specification string passed to GE return time,mem (or None,None if nothing found) """ - tme = None - mem = None + tme = inf + mem = inf # parse time m = re.search(r"rt=([0-9:]+)[\s,]*", native_spec) if m is not None: From 3e8b6a70927b08f1eecf8374dc05be9b0fa5113b Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 10 Feb 2022 08:29:55 -0500 Subject: [PATCH 07/20] Force encoding of toolbox's tool_id routing to use '%20' instead of '+', since we ignore + because it's a valid character. --- client/src/components/Panels/ToolBox.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/Panels/ToolBox.vue b/client/src/components/Panels/ToolBox.vue index 3af9756d9b7..5abc1cd1701 100644 --- a/client/src/components/Panels/ToolBox.vue +++ b/client/src/components/Panels/ToolBox.vue @@ -166,8 +166,9 @@ export default { } else if (tool.form_style === "regular") { evt.preventDefault(); const Galaxy = getGalaxyInstance(); + // encode spaces in tool.id Galaxy.router.push("/", { - tool_id: tool.id, + tool_id: tool.id.replace(/ /g, "%20"), version: tool.version, }); } From 972faaaea1be93e24c0a2b54076c026b318e6377 Mon Sep 17 00:00:00 2001 From: Alexander OSTROVSKY Date: Fri, 11 Feb 2022 15:00:08 -0800 Subject: [PATCH 08/20] add optional for workflow tag input collections --- lib/galaxy/workflow/modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py index 0bfa02e03dd..69087aab503 100644 --- a/lib/galaxy/workflow/modules.py +++ b/lib/galaxy/workflow/modules.py @@ -772,7 +772,7 @@ class InputDataCollectionModule(InputModule): {"value": "list:paired", "label": "List of Dataset Pairs"}, ] input_collection_type = TextToolParameter(None, collection_type_source) - tag_source = dict(name="tag", label="Tag filter", type="text", value=tag, help="Tags to automatically filter inputs") + tag_source = dict(name="tag", label="Tag filter", type="text", optional="true", value=tag, help="Tags to automatically filter inputs") input_tag = TextToolParameter(None, tag_source) inputs = {} inputs["collection_type"] = input_collection_type From 66081375dbea6633e62459edfa2830f1a44e5dd8 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 8 Feb 2022 21:42:51 -0500 Subject: [PATCH 09/20] Drop vuex-persistedstate (deprecated now anyway), use vuex-persist instead --- client/package.json | 1 - client/src/store/index.js | 10 ++++++---- client/src/store/panelStore.js | 2 ++ client/yarn.lock | 13 ------------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/client/package.json b/client/package.json index faedd886b87..c076cee4a0d 100644 --- a/client/package.json +++ b/client/package.json @@ -99,7 +99,6 @@ "vuex": "^3.4.0", "vuex-cache": "^3.2.0", "vuex-persist": "^3.1.3", - "vuex-persistedstate": "^4.1.0", "xml-beautifier": "^0.5.0" }, "scripts": { diff --git a/client/src/store/index.js b/client/src/store/index.js index b6b06ed2963..dd99dabb3ca 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -5,7 +5,7 @@ import Vue from "vue"; import Vuex from "vuex"; import createCache from "vuex-cache"; -import createPersistedState from "vuex-persistedstate"; +import VuexPersistence from "vuex-persist"; import config from "config"; @@ -35,13 +35,15 @@ import { syncVuextoGalaxy } from "./syncVuextoGalaxy"; Vue.use(Vuex); -const panelsState = createPersistedState({ - paths: ["panels"], +// Create vuexpersistence +const panelsPersistence = new VuexPersistence({ + storage: window.localStorage, + modules: ["panels"], }); export function createStore() { const storeConfig = { - plugins: [createCache(), panelsState], + plugins: [createCache(), panelsPersistence.plugin], modules: { user: userStore, config: configStore, diff --git a/client/src/store/panelStore.js b/client/src/store/panelStore.js index 09614b9f9d6..56d9b01d21e 100644 --- a/client/src/store/panelStore.js +++ b/client/src/store/panelStore.js @@ -44,12 +44,14 @@ const actions = { }, fetchPanel: async ({ commit }, panelView) => { const { data } = await axios.get(`${getAppRoot()}api/tools?in_panel=true&view=${panelView}`); + // wait for 15 seconds before committing commit("savePanelView", { panelView, panel: data }); }, }; const mutations = { savePanelView: (state, { panelView, panel }) => { + console.debug("savePanelView data, size is", JSON.stringify(panel).length); Vue.set(state.panel, panelView, panel); }, setCurrentPanelView: (state, { panelView }) => { diff --git a/client/yarn.lock b/client/yarn.lock index 472416550fe..35640d3acdf 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -11402,11 +11402,6 @@ shell-quote@1.7.2: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shvl@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/shvl/-/shvl-2.0.3.tgz#eb4bd37644f5684bba1fc52c3010c96fb5e6afd1" - integrity sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw== - side-channel@^1.0.3, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -13129,14 +13124,6 @@ vuex-persist@^3.1.3: deepmerge "^4.2.2" flatted "^3.0.5" -vuex-persistedstate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz#127165f85f5b4534fb3170a5d3a8be9811bd2a53" - integrity sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ== - dependencies: - deepmerge "^4.2.2" - shvl "^2.0.3" - vuex@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71" From 9da5ef6c93abdf7baf28edfb9a1286ace96ccb36 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 8 Feb 2022 22:13:39 -0500 Subject: [PATCH 10/20] Drop debugging --- client/src/store/panelStore.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/store/panelStore.js b/client/src/store/panelStore.js index 56d9b01d21e..1cd405a7bf5 100644 --- a/client/src/store/panelStore.js +++ b/client/src/store/panelStore.js @@ -51,7 +51,6 @@ const actions = { const mutations = { savePanelView: (state, { panelView, panel }) => { - console.debug("savePanelView data, size is", JSON.stringify(panel).length); Vue.set(state.panel, panelView, panel); }, setCurrentPanelView: (state, { panelView }) => { From a9f8ca99aac664c3cf695cdeb417b43508ec786b Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 8 Feb 2022 23:45:54 -0500 Subject: [PATCH 11/20] Swap in localForage for toolbox persistence; much larger capacity here and it's async. --- client/package.json | 1 + client/src/store/index.js | 6 +++--- client/src/store/panelStore.js | 1 - client/yarn.lock | 24 ++++++++++++++++++++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/client/package.json b/client/package.json index c076cee4a0d..4e0c6dfdb95 100644 --- a/client/package.json +++ b/client/package.json @@ -56,6 +56,7 @@ "jquery.cookie": "^1.4.1", "jspdf": "^2.4.0", "linkifyjs": "^2.1.9", + "localforage": "^1.10.0", "markdown-it": "^12.3.2", "markdown-it-regexp": "^0.4.0", "moment": "2.29.1", diff --git a/client/src/store/index.js b/client/src/store/index.js index dd99dabb3ca..f5ea87be2d3 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -6,6 +6,7 @@ import Vue from "vue"; import Vuex from "vuex"; import createCache from "vuex-cache"; import VuexPersistence from "vuex-persist"; +import localForage from "localforage"; import config from "config"; @@ -35,9 +36,9 @@ import { syncVuextoGalaxy } from "./syncVuextoGalaxy"; Vue.use(Vuex); -// Create vuexpersistence const panelsPersistence = new VuexPersistence({ - storage: window.localStorage, + storage: localForage, + asyncStorage: true, modules: ["panels"], }); @@ -49,7 +50,6 @@ export function createStore() { config: configStore, betaHistory: betaHistoryStore, panels: panelStore, - // TODO: please namespace all store modules gridSearch: gridSearchStore, histories: historyStore, diff --git a/client/src/store/panelStore.js b/client/src/store/panelStore.js index 1cd405a7bf5..09614b9f9d6 100644 --- a/client/src/store/panelStore.js +++ b/client/src/store/panelStore.js @@ -44,7 +44,6 @@ const actions = { }, fetchPanel: async ({ commit }, panelView) => { const { data } = await axios.get(`${getAppRoot()}api/tools?in_panel=true&view=${panelView}`); - // wait for 15 seconds before committing commit("savePanelView", { panelView, panel: data }); }, }; diff --git a/client/yarn.lock b/client/yarn.lock index 35640d3acdf..717dd855c8f 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -8093,6 +8093,13 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4 emojis-list "^3.0.0" json5 "^1.0.1" +localforage@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -8966,12 +8973,17 @@ object-hash@^2.2.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.11.0, object-inspect@^1.9.0: +object-inspect@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-is@^1.0.1, object-is@^1.1.4: +object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-is@^1.0.1: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -8979,6 +8991,14 @@ object-is@^1.0.1, object-is@^1.1.4: call-bind "^1.0.2" define-properties "^1.1.3" +object-is@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068" + integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" From 02df329f4c97ea2e81315c695bba8d1211f88dba Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 14 Feb 2022 19:27:43 -0500 Subject: [PATCH 12/20] More explicit configuration of indexeddb for use in store --- client/src/store/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/store/index.js b/client/src/store/index.js index f5ea87be2d3..adbf246e49a 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -36,8 +36,16 @@ import { syncVuextoGalaxy } from "./syncVuextoGalaxy"; Vue.use(Vuex); +const galaxyStorage = localForage.createInstance({}) +galaxyStorage.config({ + driver : localForage.INDEXEDDB, + name : 'galaxyIndexedDB', + version : 1.0, + storeName : 'galaxyStore', +}) + const panelsPersistence = new VuexPersistence({ - storage: localForage, + storage: galaxyStorage, asyncStorage: true, modules: ["panels"], }); From 9ca28b29bb943e9fbd8bd940fab8378afa76f075 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 14 Feb 2022 22:34:54 -0500 Subject: [PATCH 13/20] deepmerge breaks vuex stores that shouldn't even be touched here -- back down to vuex-persist 2.3 until we replace all this with pinia and isolated stores --- client/package.json | 2 +- client/yarn.lock | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index 4e0c6dfdb95..13d2b2078ff 100644 --- a/client/package.json +++ b/client/package.json @@ -99,7 +99,7 @@ "vueisotope": "^3.1.2", "vuex": "^3.4.0", "vuex-cache": "^3.2.0", - "vuex-persist": "^3.1.3", + "vuex-persist": "2.3", "xml-beautifier": "^0.5.0" }, "scripts": { diff --git a/client/yarn.lock b/client/yarn.lock index 717dd855c8f..087f8343141 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -5469,7 +5469,12 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" -flatted@^3.0.5, flatted@^3.1.0, flatted@^3.2.4: +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flatted@^3.1.0, flatted@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== @@ -13136,13 +13141,13 @@ vuex-cache@^3.2.0: resolved "https://registry.yarnpkg.com/vuex-cache/-/vuex-cache-3.4.0.tgz#0aec66b7abb370b1a089f678681af8efe5a9289b" integrity sha512-C0HJvVTEdjn8gq0EXemcJCBgAyPLnms8VGz/kMakrpaF6XNwkZY5KvySkdBJSY6rp0eTdWMoHPVU2Oq+dK7WEQ== -vuex-persist@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/vuex-persist/-/vuex-persist-3.1.3.tgz#518c722a2ca3026bcee5732f99d24f75cee0f3b6" - integrity sha512-QWOpP4SxmJDC5Y1+0+Yl/F4n7z27syd1St/oP+IYCGe0X0GFio0Zan6kngZFufdIhJm+5dFGDo3VG5kdkCGeRQ== +vuex-persist@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/vuex-persist/-/vuex-persist-2.3.0.tgz#f3d21b6d631078667d6834f2f74442ee332ac281" + integrity sha512-0QPZQYgQ72SbXkdwctXGFQJhlRiySP0z9wHDBTlyHvNy3CneKvgyJ7Lfob8NUiYbaurJepaC+JFynwshp59FGA== dependencies: - deepmerge "^4.2.2" - flatted "^3.0.5" + flatted "^2.0.0" + lodash "^4.17.19" vuex@^3.4.0: version "3.6.2" From f7e6061903f15165b42c241db964f299e60eef06 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 14 Feb 2022 22:41:40 -0500 Subject: [PATCH 14/20] Fall back to localStorage when indexeddb is unavailable --- client/src/store/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/store/index.js b/client/src/store/index.js index adbf246e49a..734d60ffb22 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -36,13 +36,13 @@ import { syncVuextoGalaxy } from "./syncVuextoGalaxy"; Vue.use(Vuex); -const galaxyStorage = localForage.createInstance({}) +const galaxyStorage = localForage.createInstance({}); galaxyStorage.config({ - driver : localForage.INDEXEDDB, - name : 'galaxyIndexedDB', - version : 1.0, - storeName : 'galaxyStore', -}) + driver: [localForage.INDEXEDDB, localForage.LOCALSTORAGE], + name: "galaxyIndexedDB", + version: 1.0, + storeName: "galaxyStore", +}); const panelsPersistence = new VuexPersistence({ storage: galaxyStorage, From cea780cbf804cd1e4bdb36599f74cd1f7cdd2641 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 16 Feb 2022 11:36:06 +0100 Subject: [PATCH 15/20] Test that include_hidden is handled correctly --- test/unit/test_model_store.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/unit/test_model_store.py b/test/unit/test_model_store.py index 58fd0d5d2ce..f075fcd7ea8 100644 --- a/test/unit/test_model_store.py +++ b/test/unit/test_model_store.py @@ -32,6 +32,30 @@ def test_import_export_history_failed_job(): _assert_simple_cat_job_imported(imported_history, state='error') +def test_import_export_history_hidden_false_with_hidden_dataset(): + app = _mock_app() + + u, h, d1, d2, j = _setup_simple_cat_job(app) + d2.visible = False + app.model.session.flush() + + imported_history = _import_export_history(app, h, export_files="copy", include_hidden=False) + assert d2.dataset.get_size() > 0 + assert imported_history.datasets[-1].get_size() == 0 + + +def test_import_export_history_hidden_true_with_hidden_dataset(): + app = _mock_app() + + u, h, d1, d2, j = _setup_simple_cat_job(app) + d2.visible = False + app.model.session.flush() + + imported_history = _import_export_history(app, h, export_files="copy", include_hidden=True) + assert d2.dataset.get_size() > 0 + assert imported_history.datasets[-1].get_size() > 0 + + def test_import_export_bag_archive(): """Test a simple job import/export using a BagIt archive.""" dest_parent = mkdtemp() @@ -460,13 +484,13 @@ def _setup_simple_cat_job(app, state='ok'): return u, h, d1, d2, j -def _import_export_history(app, h, dest_export=None, export_files=None): +def _import_export_history(app, h, dest_export=None, export_files=None, include_hidden=False): if dest_export is None: dest_parent = mkdtemp() dest_export = os.path.join(dest_parent, "moo.tgz") with store.TarModelExportStore(dest_export, app=app, export_files=export_files) as export_store: - export_store.export_history(h) + export_store.export_history(h, include_hidden=include_hidden) imported_history = import_archive(dest_export, app, h.user) assert imported_history From baed73d3111cc6da4929616f44f479f41dcb0569 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 16 Feb 2022 11:38:36 +0100 Subject: [PATCH 16/20] Fix history_export include_hidden=False handling Fixes https://github.com/galaxyproject/galaxy/issues/13386 --- lib/galaxy/model/store/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/store/__init__.py b/lib/galaxy/model/store/__init__.py index dd580755442..d2c1c323c50 100644 --- a/lib/galaxy/model/store/__init__.py +++ b/lib/galaxy/model/store/__init__.py @@ -1142,7 +1142,7 @@ class DirectoryModelExportStore(ModelExportStore): datasets = query.all() for dataset in datasets: dataset.annotation = get_item_annotation_str(sa_session, history.user, dataset) - add_dataset = (not dataset.visible or not include_hidden) and (not dataset.deleted or include_deleted) + add_dataset = (dataset.visible or include_hidden) and (not dataset.deleted or include_deleted) if dataset.id in self.collection_datasets: add_dataset = True From 3139cbc74b9f1e690c44371bd43ef84ed47653d9 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Wed, 16 Feb 2022 12:41:58 +0100 Subject: [PATCH 17/20] Fix history search popover not closing on click outside This is only a partial fix, since the closing event will not be fired when clicking on the central panel --- client/src/ui/search-input.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/src/ui/search-input.js b/client/src/ui/search-input.js index 21c36f4e7dd..06acd5ce212 100644 --- a/client/src/ui/search-input.js +++ b/client/src/ui/search-input.js @@ -152,6 +152,18 @@ function searchInput(parentNode, options) { .popover("show"); }); } + // Hack to hide the advanced search popover when clicking outside + $("body").on("click", function (e) { + $('[data-toggle="advSearchPopover"]').each(function () { + if ( + !$(this).is(e.target) && + $(this).has(e.target).length === 0 && + $(".popover").has(e.target).length === 0 + ) { + $(this).popover("hide"); + } + }); + }); // .................................................................... loadingIndicator rendering // a button for clearing the search bar, placed on the right hand side From cd94efaba9f855ff68800a5b0742cd9cc740e6d0 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 16 Feb 2022 12:22:38 +0100 Subject: [PATCH 18/20] Clarify that history archive links are not meant to be forever --- client/src/components/HistoryExport/ToLink.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/HistoryExport/ToLink.vue b/client/src/components/HistoryExport/ToLink.vue index e468c02fc76..041f95effdd 100644 --- a/client/src/components/HistoryExport/ToLink.vue +++ b/client/src/components/HistoryExport/ToLink.vue @@ -17,7 +17,11 @@
Link for download ready - . Use this link to download the archive or import it on another Galaxy server. +

Use this link to download the archive or import it on another Galaxy server.

+ History archives are removed at regular intervals. For permanent storage download the archive, export + to a remote file or import the archive on another Galaxy server. +

An out of date export is ready .

From c58f44314d6261c51668bc924e00115b2177507f Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Wed, 16 Feb 2022 16:00:06 +0100 Subject: [PATCH 19/20] Make test assertions more precise Co-authored-by: Nicola Soranzo --- test/unit/test_model_store.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/test_model_store.py b/test/unit/test_model_store.py index f075fcd7ea8..5b4fe1303eb 100644 --- a/test/unit/test_model_store.py +++ b/test/unit/test_model_store.py @@ -40,8 +40,8 @@ def test_import_export_history_hidden_false_with_hidden_dataset(): app.model.session.flush() imported_history = _import_export_history(app, h, export_files="copy", include_hidden=False) - assert d2.dataset.get_size() > 0 - assert imported_history.datasets[-1].get_size() == 0 + assert d1.dataset.get_size() == imported_history.datasets[0].get_size() + assert imported_history.datasets[1].get_size() == 0 def test_import_export_history_hidden_true_with_hidden_dataset(): @@ -52,8 +52,8 @@ def test_import_export_history_hidden_true_with_hidden_dataset(): app.model.session.flush() imported_history = _import_export_history(app, h, export_files="copy", include_hidden=True) - assert d2.dataset.get_size() > 0 - assert imported_history.datasets[-1].get_size() > 0 + assert d1.dataset.get_size() == imported_history.datasets[0].get_size() + assert d2.dataset.get_size() == imported_history.datasets[1].get_size() def test_import_export_bag_archive(): From 0297eb1bc8d9feac46cdf668126bd67ff78f002b Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Wed, 16 Feb 2022 10:09:50 -0500 Subject: [PATCH 20/20] Ensure $GRAVITY_STATE_DIR is present in venv activate script any time run.sh/common_startup.sh is called normally (without --skip-venv or --no-create-venv). --- scripts/common_startup.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/common_startup.sh b/scripts/common_startup.sh index 146956b38e7..f86d6c682b6 100755 --- a/scripts/common_startup.sh +++ b/scripts/common_startup.sh @@ -122,7 +122,6 @@ if [ $SET_VENV -eq 1 ] && [ $CREATE_VENV -eq 1 ]; then conda_activate fi virtualenv "$GALAXY_VIRTUAL_ENV" - setup_gravity_state_dir else # If $GALAXY_VIRTUAL_ENV does not exist, and there is no conda available, attempt to create it. if [ -z "$GALAXY_PYTHON" ]; then @@ -140,7 +139,6 @@ if [ $SET_VENV -eq 1 ] && [ $CREATE_VENV -eq 1 ]; then echo "existing environment before starting Galaxy." if command -v virtualenv >/dev/null; then virtualenv -p "$GALAXY_PYTHON" "$GALAXY_VIRTUAL_ENV" - setup_gravity_state_dir else vvers=16.7.9 vurl="https://files.pythonhosted.org/packages/source/v/virtualenv/virtualenv-${vvers}.tar.gz" @@ -167,10 +165,10 @@ urlretrieve('$vurl', '$vsrc')" tar zxf "$vsrc" -C "$vtmp" "$GALAXY_PYTHON" "$vtmp/virtualenv-$vvers/virtualenv.py" "$GALAXY_VIRTUAL_ENV" rm -rf "$vtmp" - setup_gravity_state_dir fi fi fi + setup_gravity_state_dir fi # activate virtualenv or conda env, sets $GALAXY_VIRTUAL_ENV and $GALAXY_CONDA_ENV