diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 9e933338ca54..a2aa6e3d6c75 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -33135,9 +33135,18 @@ export interface operations { * `is:published` * : Include only published workflows in the final result. Be sure the query parameter `show_published` is set to `true` if to include all published workflows and not just the requesting user's. * - * `is:share_with_me` + * `is:importable` + * : Include only importable workflows in the final result. + * + * `is:deleted` + * : Include only deleted workflows in the final result. + * + * `is:shared_with_me` * : Include only workflows shared with the requesting user. Be sure the query parameter `show_shared` is set to `true` if to include shared workflows. * + * `is:bookmarked` + * : Include only workflows bookmarked by the requesting user. + * * ## Free Text * * Free text search terms will be searched against the following attributes of the diff --git a/client/src/components/Workflow/List/WorkflowFilters.js b/client/src/components/Workflow/List/WorkflowFilters.js index 1280b6e450a5..a7c1f3f3b3c8 100644 --- a/client/src/components/Workflow/List/WorkflowFilters.js +++ b/client/src/components/Workflow/List/WorkflowFilters.js @@ -116,6 +116,13 @@ export function WorkflowFilters(activeList = "my") { handler: equals("deleted", "deleted", toBool), menuItem: true, }, + bookmarked: { + placeholder: "Bookmarked", + type: Boolean, + boolType: "is", + handler: equals("bookmarked", "bookmarked", toBool), + menuItem: true, + }, }, undefined, false, diff --git a/client/src/components/Workflow/List/WorkflowList.vue b/client/src/components/Workflow/List/WorkflowList.vue index 3bcb98b99dcd..9db7e4865c03 100644 --- a/client/src/components/Workflow/List/WorkflowList.vue +++ b/client/src/components/Workflow/List/WorkflowList.vue @@ -44,7 +44,6 @@ const overlay = ref(false); const filterText = ref(""); const totalWorkflows = ref(0); const showAdvanced = ref(false); -const showBookmarked = ref(false); const listHeader = ref(null); const workflowsLoaded = ref([]); @@ -65,6 +64,7 @@ const searchPlaceHolder = computed(() => { const published = computed(() => props.activeList === "published"); const sharedWithMe = computed(() => props.activeList === "shared_with_me"); const showDeleted = computed(() => filterText.value.includes("is:deleted")); +const showBookmarked = computed(() => filterText.value.includes("is:bookmarked")); const currentPage = computed(() => Math.floor(offset.value / limit.value) + 1); const view = computed(() => (userStore.preferredListViewMode as ListView) || "grid"); const sortDesc = computed(() => (listHeader.value && listHeader.value.sortDesc) ?? true); @@ -91,17 +91,12 @@ function updateFilterValue(filterKey: string, newValue: any) { filterText.value = workflowFilters.value.setFilterValue(currentFilterText, filterKey, newValue); } -function toggleBookmarked(bookmarked?: boolean) { - showBookmarked.value = bookmarked ?? !showBookmarked.value; -} - function onToggleBookmarked() { - toggleBookmarked(); + updateFilterValue("bookmarked", true); } function onToggleDeleted() { updateFilterValue("deleted", true); - toggleBookmarked(false); } async function load(overlayLoading = false, silent = false) { @@ -149,9 +144,7 @@ async function load(overlayLoading = false, silent = false) { rethrowSimple(error); } - let filteredWorkflows = showBookmarked.value - ? filter(data, (workflow: any) => workflow.show_in_tool_panel) - : data; + let filteredWorkflows = data; if (props.activeList === "my") { filteredWorkflows = filter(filteredWorkflows, (w: any) => userStore.matchesCurrentUsername(w.owner)); @@ -159,11 +152,7 @@ async function load(overlayLoading = false, silent = false) { workflowsLoaded.value = filteredWorkflows; - if (showBookmarked.value) { - totalWorkflows.value = filteredWorkflows.length; - } else { - totalWorkflows.value = parseInt(response.headers.get("Total_matches") || "0", 10) || 0; - } + totalWorkflows.value = parseInt(response.headers.get("Total_matches") || "0", 10) || 0; } catch (e) { Toast.error(`Failed to load workflows: ${e}`); } finally { @@ -189,7 +178,7 @@ function validatedFilterText() { return workflowFilters.value.getFilterText(validFilters.value, true); } -watch([filterText, sortBy, sortDesc, showBookmarked], async () => { +watch([filterText, sortBy, sortDesc], async () => { offset.value = 0; await load(true); }); @@ -269,7 +258,6 @@ onMounted(() => { size="sm" :title="bookmarkButtonTitle" :pressed="showBookmarked" - :disabled="showDeleted" variant="outline-primary" @click="onToggleBookmarked"> diff --git a/config/plugins/visualizations/editor/templates/editor.mako b/config/plugins/visualizations/editor/templates/editor.mako index 385edbba6205..1046842c3417 100755 --- a/config/plugins/visualizations/editor/templates/editor.mako +++ b/config/plugins/visualizations/editor/templates/editor.mako @@ -62,11 +62,11 @@ const ajax_url = "${h.url_for( controller='/datasets', action='index')}/" + hda_id + "/display"; const data = httpGet(ajax_url); - document.getElementById("editor").innerHTML = data; var editor = ace.edit("editor", { mode: "ace/mode/powershell", theme: "ace/theme/textmate" }); + editor.setValue(data, -1); diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index bb81a5390f44..ecf8f8aebcfb 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -597,7 +597,7 @@ def admin_users(self): @admin_users.setter def admin_users(self, value): self._admin_users = value - self.admin_users_list = listify(value) + self.admin_users_list = listify(value, do_strip=True) def is_admin_user(self, user: Optional["User"]) -> bool: """Determine if the provided user is listed in `admin_users`.""" diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index 5c9b2e2a10f1..da341fa446c4 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -242,6 +242,12 @@ def name_filter(term): message = "Can only use tag is:shared_with_me if show_shared parameter also true." raise exceptions.RequestParameterInvalidException(message) stmt = stmt.where(StoredWorkflowUserShareAssociation.user == user) + elif q == "bookmarked": + stmt = ( + stmt.join(model.StoredWorkflowMenuEntry) + .where(model.StoredWorkflowMenuEntry.stored_workflow_id == StoredWorkflow.id) + .where(model.StoredWorkflowMenuEntry.user_id == user.id) + ) elif isinstance(term, RawTextTerm): tf = w_tag_filter(term.text, False) alias = aliased(User) diff --git a/lib/galaxy/model/store/__init__.py b/lib/galaxy/model/store/__init__.py index d093e3c24838..a27e8c57b10c 100644 --- a/lib/galaxy/model/store/__init__.py +++ b/lib/galaxy/model/store/__init__.py @@ -578,10 +578,11 @@ def handle_dataset_object_edit(dataset_instance, dataset_attrs): self._attach_raw_id_if_editing(dataset_instance, dataset_attrs) # Older style... - if "uuid" in dataset_attrs: - dataset_instance.dataset.uuid = dataset_attrs["uuid"] - if "dataset_uuid" in dataset_attrs: - dataset_instance.dataset.uuid = dataset_attrs["dataset_uuid"] + if self.import_options.allow_edit: + if "uuid" in dataset_attrs: + dataset_instance.dataset.uuid = dataset_attrs["uuid"] + if "dataset_uuid" in dataset_attrs: + dataset_instance.dataset.uuid = dataset_attrs["dataset_uuid"] self._session_add(dataset_instance) diff --git a/lib/galaxy/model/store/discover.py b/lib/galaxy/model/store/discover.py index 2fb7f386be7e..b59e8d54eae2 100644 --- a/lib/galaxy/model/store/discover.py +++ b/lib/galaxy/model/store/discover.py @@ -132,11 +132,6 @@ def create_dataset( ) self.persist_object(primary_data) - if init_from: - self.permission_provider.copy_dataset_permissions(init_from, primary_data) - primary_data.state = init_from.state - else: - self.permission_provider.set_default_hda_permissions(primary_data) else: ld = galaxy.model.LibraryDataset(folder=library_folder, name=name) ldda = galaxy.model.LibraryDatasetDatasetAssociation( @@ -208,6 +203,7 @@ def create_dataset( filename=filename, link_data=link_data, output_name=output_name, + init_from=init_from, ) else: storage_callbacks.append( @@ -218,11 +214,14 @@ def create_dataset( filename=filename, link_data=link_data, output_name=output_name, + init_from=init_from, ) ) return primary_data - def finalize_storage(self, primary_data, dataset_attributes, extra_files, filename, link_data, output_name): + def finalize_storage( + self, primary_data, dataset_attributes, extra_files, filename, link_data, output_name, init_from + ): if primary_data.dataset.purged: # metadata won't be set, maybe we should do that, then purge ? primary_data.dataset.file_size = 0 @@ -243,6 +242,13 @@ def finalize_storage(self, primary_data, dataset_attributes, extra_files, filena else: # We are sure there are no extra files, so optimize things that follow by settting total size also. primary_data.set_size(no_extra_files=True) + + if init_from: + self.permission_provider.copy_dataset_permissions(init_from, primary_data) + primary_data.state = init_from.state + else: + self.permission_provider.set_default_hda_permissions(primary_data) + # TODO: this might run set_meta after copying the file to the object store, which could be inefficient if job working directory is closer to the node. self.set_datasets_metadata(datasets=[primary_data], datasets_attributes=[dataset_attributes]) diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index 4e8295a6ea5d..05a475d430e6 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -858,9 +858,21 @@ def __get_stored_workflow(self, trans, workflow_id, **kwd): "Include only published workflows in the final result. Be sure the query parameter `show_published` is set to `true` if to include all published workflows and not just the requesting user's.", ), IndexQueryTag( - "is:share_with_me", + "is:importable", + "Include only importable workflows in the final result.", + ), + IndexQueryTag( + "is:deleted", + "Include only deleted workflows in the final result.", + ), + IndexQueryTag( + "is:shared_with_me", "Include only workflows shared with the requesting user. Be sure the query parameter `show_shared` is set to `true` if to include shared workflows.", ), + IndexQueryTag( + "is:bookmarked", + "Include only workflows bookmarked by the requesting user.", + ), ] SearchQueryParam: Optional[str] = search_query_param( diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index c4a2522e13b4..b325c0d7aa1c 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -21,6 +21,7 @@ from galaxy.datatypes.sniff import guess_ext from galaxy.exceptions import ( MessageException, + InsufficientPermissionsException, RequestParameterInvalidException, ) from galaxy.managers.hdas import ( @@ -106,7 +107,7 @@ def _check_dataset(self, trans, hda_id): if not data: raise web.httpexceptions.HTTPNotFound(f"Invalid reference dataset id: {str(hda_id)}.") if not self._can_access_dataset(trans, data): - return trans.show_error_message("You are not allowed to access this dataset") + raise InsufficientPermissionsException("You are not allowed to access this dataset") self.app.hda_manager.ensure_dataset_on_disk(trans, data) return data diff --git a/test/integration/objectstore/test_jobs.py b/test/integration/objectstore/test_jobs.py index 30383b8e5372..3d1bab41e37e 100644 --- a/test/integration/objectstore/test_jobs.py +++ b/test/integration/objectstore/test_jobs.py @@ -3,12 +3,17 @@ import os import string +from galaxy_test.driver.integration_util import ( + integration_module_instance, + integration_tool_runner, +) from ._base import ( BaseObjectStoreIntegrationTestCase, files_count, ) +from .test_selection_with_resource_parameters import DISTRIBUTED_OBJECT_STORE_CONFIG_TEMPLATE -DISTRIBUTED_OBJECT_STORE_CONFIG_TEMPLATE = string.Template( +HIERARCHICAL_OBJECT_STORE_CONFIG_TEMPLATE = string.Template( """ @@ -39,7 +44,20 @@ TEST_INPUT_FILES_CONTENT = "1 2 3" -class TestObjectStoreJobsIntegration(BaseObjectStoreIntegrationTestCase): +class TestDistributedObjectStore(BaseObjectStoreIntegrationTestCase): + @classmethod + def handle_galaxy_config_kwds(cls, config): + super().handle_galaxy_config_kwds(config) + config["metadata_strategy"] = "directory" + config["object_store_store_by"] = "uuid" + cls._configure_object_store(DISTRIBUTED_OBJECT_STORE_CONFIG_TEMPLATE, config) + + +instance = integration_module_instance(TestDistributedObjectStore) +test_tools = integration_tool_runner(["all_output_types"]) + + +class TestObjectStoreJobsIntegration(TestDistributedObjectStore): # setup by _configure_object_store files1_path: str files2_path: str @@ -48,7 +66,7 @@ class TestObjectStoreJobsIntegration(BaseObjectStoreIntegrationTestCase): @classmethod def handle_galaxy_config_kwds(cls, config): super().handle_galaxy_config_kwds(config) - cls._configure_object_store(DISTRIBUTED_OBJECT_STORE_CONFIG_TEMPLATE, config) + cls._configure_object_store(HIERARCHICAL_OBJECT_STORE_CONFIG_TEMPLATE, config) def setUp(self): super().setUp() @@ -68,7 +86,7 @@ def setUp(self): def test_files_count_and_content_in_each_objectstore_backend(self): """ According to the ObjectStore configuration given in the - `DISTRIBUTED_OBJECT_STORE_CONFIG_TEMPLATE` variable, datasets + `HIERARCHICAL_OBJECT_STORE_CONFIG_TEMPLATE` variable, datasets can be stored on three backends, named: - primary/files1; - primary/files2; diff --git a/test/integration/test_extended_metadata.py b/test/integration/test_extended_metadata.py index 50c21940cfba..94240052147a 100644 --- a/test/integration/test_extended_metadata.py +++ b/test/integration/test_extended_metadata.py @@ -43,6 +43,7 @@ "collection_creates_dynamic_nested_from_json_elements", "implicit_conversion", "environment_variables", + "all_output_types", ]