Skip to content

Commit

Permalink
Merge pull request galaxyproject#16739 from ahmedhamidawan/toolbox_re…
Browse files Browse the repository at this point in the history
…structuring

Refactor Tool Panel views structures and combine ToolBox and ToolBoxWorkflow into one component
  • Loading branch information
dannon authored Oct 16, 2023
2 parents 9cb0dd8 + a418224 commit 7c013cc
Show file tree
Hide file tree
Showing 49 changed files with 2,079 additions and 1,696 deletions.
4 changes: 2 additions & 2 deletions client/src/components/ActivityBar/ActivityBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NotificationItem from "./Items/NotificationItem.vue";
import UploadItem from "./Items/UploadItem.vue";
import ContextMenu from "@/components/Common/ContextMenu.vue";
import FlexPanel from "@/components/Panels/FlexPanel.vue";
import ToolBox from "@/components/Panels/ProviderAwareToolBox.vue";
import ToolPanel from "@/components/Panels/ToolPanel.vue";
import WorkflowBox from "@/components/Panels/WorkflowBox.vue";
const { config, isConfigLoaded } = useConfig();
Expand Down Expand Up @@ -211,7 +211,7 @@ function toggleContextMenu(evt: MouseEvent) {
</b-nav>
</div>
<FlexPanel v-if="isActiveSideBar('tools')" key="tools" side="left" :collapsible="false">
<ToolBox />
<ToolPanel />
</FlexPanel>
<FlexPanel v-else-if="isActiveSideBar('workflows')" key="workflows" side="left" :collapsible="false">
<WorkflowBox />
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Common/PublishedItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { usePanels } from "@/composables/usePanels";
import ActivityBar from "@/components/ActivityBar/ActivityBar.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
import FlexPanel from "@/components/Panels/FlexPanel.vue";
import ToolBox from "@/components/Panels/ProviderAwareToolBox.vue";
import ToolPanel from "@/components/Panels/ToolPanel.vue";
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
interface Item {
Expand Down Expand Up @@ -54,7 +54,7 @@ const { showActivityBar, showToolbox } = usePanels();
<div id="columns" class="d-flex">
<ActivityBar v-if="showActivityBar" />
<FlexPanel v-if="showToolbox" side="left">
<ToolBox />
<ToolPanel />
</FlexPanel>
<div id="center" class="m-3 w-100 overflow-auto d-flex flex-column">
<slot />
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Panels/Buttons/FavoritesButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default {
props: {
query: {
type: String,
required: true,
},
},
data() {
Expand Down Expand Up @@ -56,7 +57,7 @@ export default {
if (this.toggle) {
this.$emit("onFavorites", this.searchKey);
} else {
this.$emit("onFavorites", null);
this.$emit("onFavorites", "");
}
},
},
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/Panels/Buttons/PanelViewMenuItem.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<b-dropdown-item :data-panel-id="panelView.id" :active="isSelected" @click="onClick">
<b-dropdown-item class="ml-1" :title="title" :data-panel-id="panelView.id" :active="isSelected" @click="onClick">
<span :class="['fa', `fa-${icon}`]" fixed-width />
<span v-localize class="ml-1" :title="title">{{ name }}</span>
<span v-localize>{{ name }}</span>
</b-dropdown-item>
</template>

Expand Down
17 changes: 8 additions & 9 deletions client/src/components/Panels/Common/ToolSearch.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "jest-location-mock";

import { mount } from "@vue/test-utils";
import { createPinia } from "pinia";
import { getLocalVue } from "tests/jest/helpers";
import VueRouter from "vue-router";

Expand All @@ -11,19 +12,22 @@ localVue.use(VueRouter);
const router = new VueRouter();

describe("ToolSearch", () => {
it("test tools advanced filter panel", async () => {
it("test tools advanced filter panel navigation", async () => {
const pinia = createPinia();
const wrapper = mount(ToolSearch, {
propsData: {
currentPanelView: "default",
enableAdvanced: false,
showAdvanced: false,
toolbox: [],
toolsList: [],
currentPanel: {},
},
localVue,
router,
stubs: {
icon: { template: "<div></div>" },
},
pinia,
});
const $router = wrapper.vm.$router;

Expand All @@ -33,13 +37,6 @@ describe("ToolSearch", () => {
expect(wrapper.find("[data-description='wide toggle advanced search']").exists()).toBe(true);
expect(wrapper.find("[data-description='advanced filters']").exists()).toBe(true);

// Test: changing panel view should change search by section field to search by ontology
expect(wrapper.find("[placeholder='any section']").exists()).toBe(true);
await wrapper.setProps({ currentPanelView: "ontology:edam_operations" });
expect(wrapper.find("[placeholder='any section']").exists()).toBe(false);
expect(wrapper.find("[placeholder='any ontology']").exists()).toBe(true);
await wrapper.setProps({ currentPanelView: "default" });

// Test: keyup.esc (should toggle the view out) --- doesn't work from name (DelayedInput) field
const sectionField = wrapper.find("[placeholder='any section']");
expect(wrapper.emitted()["update:show-advanced"]).toBeUndefined();
Expand All @@ -52,6 +49,7 @@ describe("ToolSearch", () => {
const filterInputs = {
"[placeholder='any name']": "name-filter",
"[placeholder='any section']": "section-filter",
"[placeholder='any EDAM ontology']": "ontology-filter",
"[placeholder='any id']": "id-filter",
"[placeholder='any repository owner']": "owner-filter",
"[placeholder='any help text']": "help-filter",
Expand All @@ -72,6 +70,7 @@ describe("ToolSearch", () => {
const filterSettings = {
name: "name-filter",
section: "section-filter",
ontology: "ontology-filter",
id: "id-filter",
owner: "owner-filter",
help: "help-filter",
Expand Down
76 changes: 44 additions & 32 deletions client/src/components/Panels/Common/ToolSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ import { computed, type ComputedRef, onMounted, onUnmounted, type PropType, type
import { useRouter } from "vue-router/composables";
import { getGalaxyInstance } from "@/app";
import { type Tool, type ToolSection, useToolStore } from "@/stores/toolStore";
import Filtering, { contains, type ValidFilter } from "@/utils/filtering";
import _l from "@/utils/localization";
import { flattenTools } from "../utilities.js";
import { type ToolSearchKeys } from "../utilities";
import DelayedInput from "@/components/Common/DelayedInput.vue";
import FilterMenu from "@/components/Common/FilterMenu.vue";
const router = useRouter();
const KEYS = { exact: 3, name: 2, description: 1, combined: 0 };
// Note: These are ordered by result priority (exact matches very top; words matches very bottom)
const KEYS: ToolSearchKeys = { exact: 5, startsWith: 4, name: 3, description: 2, combined: 1, wordMatch: 0 };
const FAVORITES = ["#favs", "#favorites", "#favourites"];
const MIN_QUERY_LENGTH = 3;
interface Tool {
name?: string;
}
const props = defineProps({
currentPanelView: {
type: String,
Expand All @@ -45,15 +45,24 @@ const props = defineProps({
type: Boolean,
default: false,
},
toolbox: {
toolsList: {
type: Array as PropType<Tool[]>,
required: true,
},
currentPanel: {
type: Object as PropType<Record<string, Tool | ToolSection>>,
required: true,
},
});
const emit = defineEmits<{
(e: "update:show-advanced", showAdvanced: boolean): void;
(e: "onResults", filtered: string[] | null, closestValue: string | null): void;
(
e: "onResults",
filtered: string[] | null,
sectioned: Record<string, Tool | ToolSection> | null,
closestValue: string | null
): void;
(e: "onQuery", query: string): void;
}>();
Expand All @@ -76,22 +85,21 @@ const propShowAdvanced = computed({
emit("update:show-advanced", val);
},
});
const sectionNames = computed(() => {
return props.toolbox.map((section) =>
section.name !== undefined && section.name !== "Uncategorized" ? section.name : ""
);
});
const toolsList = computed(() => {
return flattenTools(props.toolbox);
});
const validFilters: ComputedRef<Record<string, ValidFilter<string>>> = computed(() => {
return {
name: { placeholder: "name", type: String, handler: contains("name"), menuItem: true },
section: {
placeholder: props.currentPanelView === "default" ? "section" : "ontology",
placeholder: "section",
type: String,
handler: contains("section"),
datalist: sectionNames.value,
datalist: sectionNames,
menuItem: true,
},
ontology: {
placeholder: "EDAM ontology",
type: String,
handler: contains("ontology"),
datalist: ontologyList,
menuItem: true,
},
id: { placeholder: "id", type: String, handler: contains("id"), menuItem: true },
Expand All @@ -101,19 +109,26 @@ const validFilters: ComputedRef<Record<string, ValidFilter<string>>> = computed(
});
const ToolFilters: ComputedRef<Filtering<string>> = computed(() => new Filtering(validFilters.value));
const toolStore = useToolStore();
const sectionNames = toolStore.sectionDatalist("default").map((option: { value: string; text: string }) => option.text);
const ontologyList = toolStore
.sectionDatalist("ontology:edam_topics")
.concat(toolStore.sectionDatalist("ontology:edam_operations"));
onMounted(() => {
searchWorker.value = new Worker(new URL("../toolSearch.worker.js", import.meta.url));
const Galaxy = getGalaxyInstance();
const favoritesResults = Galaxy?.user.getFavorites().tools;
searchWorker.value.onmessage = ({ data }) => {
const { type, payload, query, closestTerm } = data;
const { type, payload, sectioned, query, closestTerm } = data;
if (type === "searchToolsByKeysResult" && query === props.query) {
emit("onResults", payload, closestTerm);
emit("onResults", payload, sectioned, closestTerm);
} else if (type === "clearFilterResult") {
emit("onResults", null, null);
emit("onResults", null, null, null);
} else if (type === "favoriteToolsResult") {
emit("onResults", favoritesResults, null);
emit("onResults", favoritesResults, null, null);
}
};
});
Expand All @@ -131,9 +146,11 @@ function checkQuery(q: string) {
post({
type: "searchToolsByKeys",
payload: {
tools: toolsList.value,
tools: props.toolsList,
keys: KEYS,
query: q,
panelView: props.currentPanelView,
currentPanel: props.currentPanel,
},
});
}
Expand Down Expand Up @@ -185,16 +202,11 @@ function onAdvancedSearch(filters: any) {
<dt><code>name</code></dt>
<dd>The tool name (stored as tool.name + tool.description in the XML)</dd>
<dt><code>section</code></dt>
<dd>The tool section is based on the default tool panel view</dd>
<dt><code>ontology</code></dt>
<dd>
The tool section is based on the current view you have selected for the panel. <br />
When this field is active, you will be able to see a datalist showing the available sections
you can filter from. <br />
By default, Galaxy tool panel sections are filterable if you are currently on the
<i>Full Tool Panel</i> view, and it will show EDAM ontologies or EDAM topics if you have
either of those options selected. <br />
Change panel views by clicking on the
<icon icon="caret-down" />
icon at the top right of the tool panel.
This is the EDAM ontology term that is associated with the tool. Example inputs:
<i>"topic_3174"</i> or <i>"operation_0324"</i>
</dd>
<dt><code>id</code></dt>
<dd>The tool id (taken from its XML)</dd>
Expand Down
27 changes: 18 additions & 9 deletions client/src/components/Panels/Common/ToolSection.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mount } from "@vue/test-utils";
import { useConfig } from "composables/config";
import { createPinia } from "pinia";
import { getLocalVue } from "tests/jest/helpers";

import ToolSection from "./ToolSection";
Expand All @@ -13,6 +14,11 @@ useConfig.mockReturnValue({
});

const localVue = getLocalVue();
const pinia = createPinia();

function sectionIsOpened(wrapper) {
return wrapper.find("[data-description='opened tool panel section']").exists();
}

describe("ToolSection", () => {
test("test tool section", () => {
Expand All @@ -23,6 +29,7 @@ describe("ToolSection", () => {
},
},
localVue,
pinia,
});
const nameElement = wrapper.findAll(".name");
expect(nameElement.at(0).text()).toBe("name");
Expand All @@ -46,8 +53,9 @@ describe("ToolSection", () => {
},
},
localVue,
pinia,
});
expect(wrapper.vm.opened).toBe(false);
expect(sectionIsOpened(wrapper)).toBe(false);
const $sectionName = wrapper.find(".name");
expect($sectionName.text()).toBe("tool_section");
await $sectionName.trigger("click");
Expand Down Expand Up @@ -76,22 +84,23 @@ describe("ToolSection", () => {
queryFilter: "test",
},
localVue,
pinia,
});
expect(wrapper.vm.opened).toBe(true);
expect(sectionIsOpened(wrapper)).toBe(true);
const $sectionName = wrapper.find(".name");
await $sectionName.trigger("click");
expect(wrapper.vm.opened).toBe(false);
expect(sectionIsOpened(wrapper)).toBe(false);
await wrapper.setProps({ queryFilter: "" });
expect(wrapper.vm.opened).toBe(false);
expect(sectionIsOpened(wrapper)).toBe(false);
await wrapper.setProps({ queryFilter: "test" });
expect(wrapper.vm.opened).toBe(true);
expect(sectionIsOpened(wrapper)).toBe(true);
await wrapper.setProps({ disableFilter: true });
expect(wrapper.vm.opened).toBe(true);
expect(sectionIsOpened(wrapper)).toBe(true);
await wrapper.setProps({ queryFilter: "" });
expect(wrapper.vm.opened).toBe(false);
expect(sectionIsOpened(wrapper)).toBe(false);
await $sectionName.trigger("click");
expect(wrapper.vm.opened).toBe(true);
expect(sectionIsOpened(wrapper)).toBe(true);
await wrapper.setProps({ queryFilter: "test" });
expect(wrapper.vm.opened).toBe(false);
expect(sectionIsOpened(wrapper)).toBe(false);
});
});
Loading

0 comments on commit 7c013cc

Please sign in to comment.