From 482585f1c1fe873a229766508365b85123ce66fa Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Thu, 17 Nov 2022 14:32:11 -0500 Subject: [PATCH 1/3] Tool Client Search bug fix and add client tests --- .../components/Panels/Common/ToolSearch.vue | 7 +-- client/src/components/Panels/utilities.js | 54 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/client/src/components/Panels/Common/ToolSearch.vue b/client/src/components/Panels/Common/ToolSearch.vue index b5cf4b632f2..221bf5b3f62 100644 --- a/client/src/components/Panels/Common/ToolSearch.vue +++ b/client/src/components/Panels/Common/ToolSearch.vue @@ -21,7 +21,7 @@ size="sm" :placeholder="`any ${sectionLabel}`" list="sectionSelect" /> - + Filter by id: Filter by help text: @@ -88,7 +88,7 @@ export default { const Galaxy = getGalaxyInstance(); return Galaxy.user.getFavorites().tools; }, - sections() { + sectionNames() { return this.toolbox.map((section) => section.name !== undefined && section.name !== "Uncategorized" ? section.name : "" ); @@ -105,7 +105,8 @@ export default { if (this.favorites.includes(q)) { this.$emit("onResults", this.favoritesResults); } else { - const keys = ["name", "description"]; + // keys with sorting order + const keys = { exact: 2, name: 1, description: 0 }; this.$emit("onResults", searchToolsByKeys(this.toolbox, keys, q)); } } else { diff --git a/client/src/components/Panels/utilities.js b/client/src/components/Panels/utilities.js index ccb7c7c7c69..53b77c3509d 100644 --- a/client/src/components/Panels/utilities.js +++ b/client/src/components/Panels/utilities.js @@ -1,11 +1,25 @@ +/** + * Utilities file for Tool Search (panel/client search + advanced/backend search) + */ import { orderBy } from "lodash"; const TOOLS_RESULTS_SORT_LABEL = "apiSort"; const TOOLS_RESULTS_SECTIONS_HIDE = ["Expression Tools"]; +/* + createWhooshQuery() + - Takes filterSettings = {"name": "Tool Name", "section": "Collection", ...} + - Takes panelView (if not 'default', does ontology search at backend) + - Takes toolbox (to find ontology id if given ontology name) + - Returns parsed Whoosh query + + e.g. fn call: createWhooshQuery(filterSettings, 'ontology:edam_topics', toolbox) + can return: + query = "(name:(skew) name_exact:(skew) description:(skew)) AND (edam_topics:(topic_0797) AND )" +*/ export function createWhooshQuery(filterSettings, panelView, toolbox) { let query = "("; - // add description+name_exact fields = name, to do a combined AndGroup at backend + // add description+name_exact fields = name, to do a combined OrGroup at backend const name = filterSettings["name"]; if (name) { query += "name:(" + name + ") "; @@ -38,6 +52,10 @@ export function createWhooshQuery(filterSettings, panelView, toolbox) { return query; } +/* + filterTools() + Given toolbox and search results, returns filtered tool results +*/ export function filterTools(tools, results) { let toolsResults = []; tools = normalizeTools(tools); @@ -47,6 +65,10 @@ export function filterTools(tools, results) { return toolsResults; } +/* + filterToolSections() + Given toolbox and search results, returns filtered tool results by sections +*/ export function filterToolSections(tools, results) { let toolsResults = []; let toolsResultsSection = []; @@ -71,23 +93,37 @@ export function hasResults(results) { return Array.isArray(results) && results.length > 0; } +/* + searchToolsByKeys() + Given toolbox, keys to sort/search results by and a search query, + Returns tool ids sorted by order of keys that are being searched +*/ export function searchToolsByKeys(tools, keys, query) { const returnedTools = []; for (const section of tools) { + let elems = []; if (section.elems) { - for (const tool of section.elems) { - for (const key of keys) { - const actualValue = tool[key]; - if (actualValue && actualValue.toLowerCase().match(query.toLowerCase())) { - returnedTools.push({ id: tool.id, key: key }); - break; + elems = section.elems; + } else { + elems.push(section); + } + for (const tool of elems) { + for (const key of Object.keys(keys)) { + const actualValue = tool[key]; + if (actualValue && actualValue.toLowerCase().match(query.toLowerCase())) { + // do we care for exact matches && is it an exact match + if (keys.exact && actualValue.toLowerCase() === query.toLowerCase()) { + returnedTools.push({ id: tool.id, order: keys.exact }); + } else { + returnedTools.push({ id: tool.id, order: keys[key] }); } + break; } } } } - // sorting results by indexed order of key in keys - return orderBy(returnedTools, ["key"], ["desc"]).map((tool) => tool.id); + // sorting results by indexed order of keys + return orderBy(returnedTools, ["order"], ["desc"]).map((tool) => tool.id); } function normalizeTools(tools) { From cc640a60f05f3ce23c42827dd3ad41a3bc10ba73 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Wed, 23 Nov 2022 13:40:37 -0500 Subject: [PATCH 2/3] Add Toolbox client search test --- .../components/Panels/Common/ToolBox.test.js | 65 -------------- client/src/components/Panels/ToolBox.test.js | 86 +++++++++++++++++++ .../src/components/Panels/testToolsList.json | 72 ++++++++++++++++ client/src/components/Panels/utilities.js | 49 ++++------- 4 files changed, 175 insertions(+), 97 deletions(-) delete mode 100644 client/src/components/Panels/Common/ToolBox.test.js create mode 100644 client/src/components/Panels/ToolBox.test.js create mode 100644 client/src/components/Panels/testToolsList.json diff --git a/client/src/components/Panels/Common/ToolBox.test.js b/client/src/components/Panels/Common/ToolBox.test.js deleted file mode 100644 index dff0b65a812..00000000000 --- a/client/src/components/Panels/Common/ToolBox.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import MockAdapter from "axios-mock-adapter"; -import axios from "axios"; -import { filterToolSections, filterTools } from "../utilities"; - -describe("ToolBox", () => { - const toolsMock = [ - { - model_class: "ToolSection", - id: "group_1", - name: "Group 1", - elems: [ - { - model_class: "Tool1", - id: "tool1", - name: "Tool 1", - link: "/tool_runner?tool_id=tool1", - panel_section_id: "group_1", - panel_section_name: "Group 1", - }, - { - model_class: "Tool2", - id: "tool2", - name: "Tool 2", - link: "/tool_runner?tool_id=tool2", - panel_section_id: "group_1", - panel_section_name: "Group 1", - }, - ], - }, - { - model_class: "ToolSection", - id: "group_2", - name: "Group 2", - elems: [ - { - model_class: "Tool3", - id: "tool3", - name: "Tool 3", - link: "/tool_runner?tool_id=tool3", - panel_section_id: "group_2", - panel_section_name: "Group 2", - }, - ], - }, - ]; - const resultsMock = ["tool1", "tool3"]; - - let axiosMock; - - beforeEach(async () => { - axiosMock = new MockAdapter(axios); - }); - - it("test filter functions correctly matching: (1) Tools store array-of-objects with (2) Results array", async () => { - axiosMock - .onGet(`/api/tools`) - .replyOnce(200, toolsMock) - .onGet(/api\/tools?.*/) - .replyOnce(200, resultsMock); - const toolsResults = filterTools(toolsMock, resultsMock); - const toolsResultsSection = filterToolSections(toolsMock, resultsMock); - expect(toolsResults.length).toBe(2); - expect(toolsResultsSection.length).toBe(2); - }); -}); diff --git a/client/src/components/Panels/ToolBox.test.js b/client/src/components/Panels/ToolBox.test.js new file mode 100644 index 00000000000..c853d72568c --- /dev/null +++ b/client/src/components/Panels/ToolBox.test.js @@ -0,0 +1,86 @@ +import Vuex from "vuex"; +import { mount } from "@vue/test-utils"; +import { getLocalVue } from "jest/helpers"; +import MockAdapter from "axios-mock-adapter"; +import axios from "axios"; +import { filterToolSections, filterTools } from "./utilities"; +import ToolBox from "./ToolBox"; +import toolsList from "./testToolsList"; +import { userStore } from "store/userStore"; +import { useConfig } from "composables/config"; + +jest.mock("composables/config"); +useConfig.mockReturnValue({ + config: { + toolbox_auto_sort: true, + }, + isLoaded: true, +}); + +const localVue = getLocalVue(); + +describe("ToolBox", () => { + const toolsMock = toolsList; + const resultsMock = ["join1", "join_collections", "find1"]; + let axiosMock; + + const searches = { + "join": ["join1", "join_collections", "find1"], + "find": ["find1"], + "remove": ["remove1", "remove_duplicate"], + "fi": null, + }; + + let wrapper; + let store; + let state; + + beforeEach(async () => { + axiosMock = new MockAdapter(axios); + + store = new Vuex.Store({ + modules: { + user: { + state, + actions: { + loadUser: jest.fn(), + }, + getters: userStore.getters, + namespaced: true, + }, + }, + }); + + wrapper = mount(ToolBox, { + propsData: { + toolbox: toolsList, + currentPanelView: "default", + storedWorkflowMenuEntries: [], + }, + store, + localVue, + stubs: { + icon: { template: "
" }, + }, + }); + }); + + it("test filter functions correctly matching: (1) Tools store array-of-objects with (2) Results array", async () => { + axiosMock + .onGet(`/api/tools`) + .replyOnce(200, toolsMock) + .onGet(/api\/tools?.*/) + .replyOnce(200, resultsMock); + const toolsResults = filterTools(toolsMock, resultsMock); + const toolsResultsSection = filterToolSections(toolsMock, resultsMock); + expect(toolsResults.length).toBe(2); + expect(toolsResultsSection.length).toBe(2); + }); + + it("test toolbox client search", async() => { + for (const [query, results] of Object.entries(searches)){ + await wrapper.setData({ query: query }); + expect(wrapper.vm.results).toEqual(results); + } + }); +}); diff --git a/client/src/components/Panels/testToolsList.json b/client/src/components/Panels/testToolsList.json new file mode 100644 index 00000000000..c5a3f965240 --- /dev/null +++ b/client/src/components/Panels/testToolsList.json @@ -0,0 +1,72 @@ +[ + { + "model_class": "ToolSection", + "id": "group_1", + "name": "Group 1", + "elems": [ + { + "model_class": "Tool", + "name": "Join", + "id": "join1", + "description": "two files", + "panel_section_id": "group_1", + "panel_section_name": "Group 1" + }, + { + "model_class": "Tool", + "name": "Remove", + "id": "remove1", + "description": "lines", + "panel_section_id": "group_1", + "panel_section_name": "Group 1" + }, + { + "model_class": "Tool", + "name": "Cut", + "id": "cut1", + "description": "lines", + "panel_section_id": "group_1", + "panel_section_name": "Group 1" + }, + { + "model_class": "Tool", + "name": "Find", + "id": "find1", + "description": "joined dataset", + "panel_section_id": "group_1", + "panel_section_name": "Group 1" + } + ] + }, + { + "model_class": "ToolSection", + "id": "group_2", + "name": "Group 2", + "elems": [ + { + "model_class": "Tool", + "name": "Join Collections", + "id": "join_collections", + "description": "", + "panel_section_id": "group_2", + "panel_section_name": "Group 2" + }, + { + "model_class": "Tool", + "name": "Create Collections", + "id": "create_collections", + "description": "from datasets", + "panel_section_id": "group_2", + "panel_section_name": "Group 2" + } + ] + }, + { + "model_class": "Tool", + "name": "Remove duplicate", + "id": "remove_duplicate", + "description": "lines from dataset", + "panel_section_id": "remove_duplicate", + "panel_section_name": "Remove duplicate" + } +] diff --git a/client/src/components/Panels/utilities.js b/client/src/components/Panels/utilities.js index 53b77c3509d..673d5cabeb2 100644 --- a/client/src/components/Panels/utilities.js +++ b/client/src/components/Panels/utilities.js @@ -6,17 +6,13 @@ import { orderBy } from "lodash"; const TOOLS_RESULTS_SORT_LABEL = "apiSort"; const TOOLS_RESULTS_SECTIONS_HIDE = ["Expression Tools"]; -/* - createWhooshQuery() - - Takes filterSettings = {"name": "Tool Name", "section": "Collection", ...} - - Takes panelView (if not 'default', does ontology search at backend) - - Takes toolbox (to find ontology id if given ontology name) - - Returns parsed Whoosh query - - e.g. fn call: createWhooshQuery(filterSettings, 'ontology:edam_topics', toolbox) - can return: - query = "(name:(skew) name_exact:(skew) description:(skew)) AND (edam_topics:(topic_0797) AND )" -*/ +// - Takes filterSettings = {"name": "Tool Name", "section": "Collection", ...} +// - Takes panelView (if not 'default', does ontology search at backend) +// - Takes toolbox (to find ontology id if given ontology name) +// - Returns parsed Whoosh query +// e.g. fn call: createWhooshQuery(filterSettings, 'ontology:edam_topics', toolbox) +// can return: +// query = "(name:(skew) name_exact:(skew) description:(skew)) AND (edam_topics:(topic_0797) AND )" export function createWhooshQuery(filterSettings, panelView, toolbox) { let query = "("; // add description+name_exact fields = name, to do a combined OrGroup at backend @@ -52,10 +48,7 @@ export function createWhooshQuery(filterSettings, panelView, toolbox) { return query; } -/* - filterTools() - Given toolbox and search results, returns filtered tool results -*/ +// Given toolbox and search results, returns filtered tool results export function filterTools(tools, results) { let toolsResults = []; tools = normalizeTools(tools); @@ -65,10 +58,7 @@ export function filterTools(tools, results) { return toolsResults; } -/* - filterToolSections() - Given toolbox and search results, returns filtered tool results by sections -*/ +// Given toolbox and search results, returns filtered tool results by sections export function filterToolSections(tools, results) { let toolsResults = []; let toolsResultsSection = []; @@ -93,11 +83,8 @@ export function hasResults(results) { return Array.isArray(results) && results.length > 0; } -/* - searchToolsByKeys() - Given toolbox, keys to sort/search results by and a search query, - Returns tool ids sorted by order of keys that are being searched -*/ +// Given toolbox, keys to sort/search results by and a search query, +// Returns tool ids sorted by order of keys that are being searched export function searchToolsByKeys(tools, keys, query) { const returnedTools = []; for (const section of tools) { @@ -109,14 +96,12 @@ export function searchToolsByKeys(tools, keys, query) { } for (const tool of elems) { for (const key of Object.keys(keys)) { - const actualValue = tool[key]; - if (actualValue && actualValue.toLowerCase().match(query.toLowerCase())) { - // do we care for exact matches && is it an exact match - if (keys.exact && actualValue.toLowerCase() === query.toLowerCase()) { - returnedTools.push({ id: tool.id, order: keys.exact }); - } else { - returnedTools.push({ id: tool.id, order: keys[key] }); - } + const actualValue = tool[key] ? tool[key].toLowerCase() : ""; + const queryLowerCase = query.toLowerCase(); + if (actualValue.match(queryLowerCase)) { + // do we care for exact matches && is it an exact match ? + const order = keys.exact && actualValue === queryLowerCase ? keys.exact : keys[key]; + returnedTools.push({ id: tool.id, order }); break; } } From 7c41a1390b9ce239c71a7f7c396bddc553d1cfd6 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Wed, 23 Nov 2022 22:41:31 -0500 Subject: [PATCH 3/3] Changes to tool box search tests and utilities --- .../components/Panels/Common/ToolSearch.vue | 7 +++-- client/src/components/Panels/ToolBox.test.js | 16 +++++----- client/src/components/Panels/utilities.js | 30 +++++++------------ .../src/components/Panels/utilities.test.js | 25 +++++++++++++++- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/client/src/components/Panels/Common/ToolSearch.vue b/client/src/components/Panels/Common/ToolSearch.vue index 221bf5b3f62..dd2a5303020 100644 --- a/client/src/components/Panels/Common/ToolSearch.vue +++ b/client/src/components/Panels/Common/ToolSearch.vue @@ -43,7 +43,7 @@