From 7eb3cc90591d82a1b3ee071c0c2bcbf798da5cff Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Mon, 18 Mar 2024 14:44:02 -0500 Subject: [PATCH] allow exact query search (surrounded by quotes) in grids Users can bypass the client filtering by surrounding their raw search query by quotes and sending the unprocessed filter to the backend. --- client/src/components/Grid/GridList.vue | 19 +++++++++++++--- .../src/components/Workflow/WorkflowList.vue | 22 +++++++++++++++---- test/integration_selenium/test_trs_import.py | 3 ++- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/client/src/components/Grid/GridList.vue b/client/src/components/Grid/GridList.vue index 42ce5ca1e9cb..8c73ba0e5d44 100644 --- a/client/src/components/Grid/GridList.vue +++ b/client/src/components/Grid/GridList.vue @@ -82,7 +82,8 @@ const filterClass = props.gridConfig.filtering; const rawFilters = computed(() => Object.fromEntries(filterClass.getFiltersForText(filterText.value, true, false))); const validFilters = computed(() => filterClass.getValidFilters(rawFilters.value, true).validFilters); const invalidFilters = computed(() => filterClass.getValidFilters(rawFilters.value, true).invalidFilters); -const hasInvalidFilters = computed(() => Object.keys(invalidFilters.value).length > 0); +const isSurroundedByQuotes = computed(() => /^["'].*["']$/.test(filterText.value)); +const hasInvalidFilters = computed(() => !isSurroundedByQuotes.value && Object.keys(invalidFilters.value).length > 0); // hide message helper const hideMessage = useDebounceFn(() => { @@ -221,8 +222,11 @@ function onSelectAll(current: boolean): void { * A valid filter/query for the backend */ function validatedFilterText() { - // there are no filters derived from the `filterText`; return the `filterText` as is - if (Object.keys(rawFilters.value).length === 0) { + if (isSurroundedByQuotes.value) { + // the filterText is surrounded by quotes, remove them + return filterText.value.slice(1, -1); + } else if (Object.keys(rawFilters.value).length === 0) { + // there are no filters derived from the `filterText` return filterText.value; } // there are valid filters derived from the `filterText` @@ -311,6 +315,15 @@ watch(operationMessage, () => { Remove invalid filters from query + or + + Match the exact query provided + diff --git a/client/src/components/Workflow/WorkflowList.vue b/client/src/components/Workflow/WorkflowList.vue index 85b93caeb86f..45a1b9dee14d 100644 --- a/client/src/components/Workflow/WorkflowList.vue +++ b/client/src/components/Workflow/WorkflowList.vue @@ -57,7 +57,7 @@ const searchPlaceHolder = computed(() => { placeHolder = "Search workflows shared with me"; } - placeHolder += " by name or use the advanced filtering options"; + placeHolder += " by query or use the advanced filtering options"; return placeHolder; }); @@ -72,13 +72,15 @@ const sortBy = computed(() => (listHeader.value && listHeader.value.sortBy) || " const noItems = computed(() => !loading.value && workflowsLoaded.value.length === 0 && !filterText.value); const noResults = computed(() => !loading.value && workflowsLoaded.value.length === 0 && filterText.value); +// Filtering computed refs const workflowFilters = computed(() => WorkflowFilters(props.activeList)); const rawFilters = computed(() => Object.fromEntries(workflowFilters.value.getFiltersForText(filterText.value, true, false)) ); const validFilters = computed(() => workflowFilters.value.getValidFilters(rawFilters.value, true).validFilters); const invalidFilters = computed(() => workflowFilters.value.getValidFilters(rawFilters.value, true).invalidFilters); -const hasInvalidFilters = computed(() => Object.keys(invalidFilters.value).length > 0); +const isSurroundedByQuotes = computed(() => /^["'].*["']$/.test(filterText.value)); +const hasInvalidFilters = computed(() => !isSurroundedByQuotes.value && Object.keys(invalidFilters.value).length > 0); function updateFilterValue(filterKey: string, newValue: any) { const currentFilterText = filterText.value; @@ -155,8 +157,11 @@ async function onPageChange(page: number) { } function validatedFilterText() { - // there are no filters derived from the `filterText` - if (Object.keys(rawFilters.value).length === 0) { + if (isSurroundedByQuotes.value) { + // the `filterText` is surrounded by quotes, remove them + return filterText.value.slice(1, -1); + } else if (Object.keys(rawFilters.value).length === 0) { + // there are no filters derived from the `filterText` return filterText.value; } // there are valid filters derived from the `filterText` @@ -277,6 +282,15 @@ onMounted(() => { Remove invalid filters from query + or + + Match the exact query provided + diff --git a/test/integration_selenium/test_trs_import.py b/test/integration_selenium/test_trs_import.py index 7d6f4395227a..d6031a805377 100644 --- a/test/integration_selenium/test_trs_import.py +++ b/test/integration_selenium/test_trs_import.py @@ -49,7 +49,8 @@ def trs_config_dir(cls): return cls.temp_config_dir("trs") def assert_workflow_imported(self, name): - self.workflow_index_search_for(name) + # surround name with quotes to consider case where name contains colons + self.workflow_index_search_for(f'"{name}"') assert len(self.workflow_card_elements()) == 1, f"workflow ${name} not imported" def test_import_workflow_by_url_dockstore(self):