mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-30 18:13:23 +08:00
Merge pull request #23075 from itisAliRH/fix/my-tools-recent-tools-default-panel
[26.1] Fix recent tools for My Tools default panel
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
buildToolEntries,
|
||||
buildToolLabel,
|
||||
buildToolSection,
|
||||
countUniqueToolsInList,
|
||||
countUniqueToolsInPanel,
|
||||
FAVORITES_KEYS,
|
||||
filterPanelByToolIds,
|
||||
@@ -180,6 +181,8 @@ const defaultSectionsById = computed<Record<string, ToolPanelItem> | null>(() =>
|
||||
return Object.keys(validSections).length > 0 ? validSections : null;
|
||||
});
|
||||
|
||||
const myToolsDefaultSectionsById = computed(() => defaultSectionsById.value || localSectionsById.value);
|
||||
|
||||
// Use composable for favorites and recent tools — we only need the bits that
|
||||
// drive the search-results split (favorites get their own section in mixed
|
||||
// results). The full My-Tools landing state lives inside `MyToolsLanding.vue`.
|
||||
@@ -189,7 +192,10 @@ const { favoritesCollapsed, favoriteToolIdSet, recentToolIdsToShowSet } = useToo
|
||||
const { favoriteResults, nonFavoriteResults, hasMixedResults } = useFavoriteSearchResults(results, favoriteToolIdSet);
|
||||
|
||||
const toolsCount = computed(() =>
|
||||
countUniqueToolsInPanel(defaultSectionsById.value || localSectionsById.value, toolsList.value.length),
|
||||
countUniqueToolsInPanel(
|
||||
defaultSectionsById.value || localSectionsById.value,
|
||||
countUniqueToolsInList(toolsList.value),
|
||||
),
|
||||
);
|
||||
|
||||
const resultsSet = computed(() => new Set(results.value));
|
||||
@@ -436,7 +442,7 @@ function onLabelToggle(labelId: string) {
|
||||
<MyToolsLanding
|
||||
v-if="showMyToolsLanding"
|
||||
:local-tools-by-id="localToolsById"
|
||||
:default-sections-by-id="defaultSectionsById"
|
||||
:default-sections-by-id="myToolsDefaultSectionsById"
|
||||
:local-sections-by-id="localSectionsById"
|
||||
:tools-count="toolsCount"
|
||||
@onClick="onToolClick"
|
||||
|
||||
@@ -38,7 +38,7 @@ const DEFAULT_VIEW_ID = "default";
|
||||
const PANEL_VIEW_ERR_MSG = "Error loading panel view";
|
||||
|
||||
const firstTool = toolsList[0];
|
||||
const toolsListWithExtraVersion = [...toolsList, { ...firstTool, id: `${firstTool?.id}/older-version` }];
|
||||
const toolsListWithExtraVersion = [...toolsList, { ...firstTool, id: `${firstTool?.id}/0.9`, version: "0.9" }];
|
||||
|
||||
vi.mock("@/composables/config");
|
||||
|
||||
@@ -91,6 +91,8 @@ describe("ToolPanel", () => {
|
||||
failDefault: boolean = false,
|
||||
captureToolsRequest?: (url: URL) => void,
|
||||
panelResponses: Record<string, unknown> = {},
|
||||
defaultPanelView: string = DEFAULT_VIEW_ID,
|
||||
capturePanelRequest?: (panelView: string) => void,
|
||||
) {
|
||||
server.use(
|
||||
http.untyped.get("/api/tools", ({ request }) => {
|
||||
@@ -102,7 +104,7 @@ describe("ToolPanel", () => {
|
||||
return HttpResponse.json([]);
|
||||
}),
|
||||
http.untyped.get(TEST_PANELS_URI, () => {
|
||||
return HttpResponse.json({ default_panel_view: DEFAULT_VIEW_ID, views: viewsList });
|
||||
return HttpResponse.json({ default_panel_view: defaultPanelView, views: viewsList });
|
||||
}),
|
||||
http.get("/api/users/{user_id}", ({ response }) => {
|
||||
return response(200).json(getFakeRegisteredUser());
|
||||
@@ -133,6 +135,7 @@ describe("ToolPanel", () => {
|
||||
server.use(
|
||||
http.untyped.get(/\/api\/tool_panels\/.*/, ({ request }) => {
|
||||
const panelView = new URL(request.url).pathname.split("/").pop() || "";
|
||||
capturePanelRequest?.(panelView);
|
||||
return HttpResponse.json(panelResponses[panelView] ?? toolsListInPanel);
|
||||
}),
|
||||
);
|
||||
@@ -274,6 +277,85 @@ describe("ToolPanel", () => {
|
||||
expect(discoverButton.text()).toBe(`Discover ${formatted} Tools`);
|
||||
});
|
||||
|
||||
it("uses the loaded default panel count when My Tools is the backend default view", async () => {
|
||||
storePanelView("");
|
||||
const wrapper = await createWrapper(
|
||||
"",
|
||||
false,
|
||||
undefined,
|
||||
{
|
||||
my_panel: {
|
||||
favorites: {
|
||||
model_class: "ToolSection",
|
||||
id: "favorites",
|
||||
name: "Favorites",
|
||||
tools: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"my_panel",
|
||||
);
|
||||
const count = getUniqueToolIdsInPanel(toolsListInPanel).size;
|
||||
const formatted = count < 1000 ? `${count}` : `${Math.floor(count / 1000)}k+`;
|
||||
const discoverButton = wrapper.find('[data-description="toolbox discover tools"]');
|
||||
expect(discoverButton.text()).toBe(`Discover ${formatted} Tools`);
|
||||
expect(discoverButton.text()).not.toBe(`Discover ${toolsListWithExtraVersion.length} Tools`);
|
||||
});
|
||||
|
||||
it("ignores My Tools panel contents for the header count when My Tools is the backend default view", async () => {
|
||||
storePanelView("");
|
||||
const firstToolId = firstTool?.id;
|
||||
if (!firstToolId) {
|
||||
throw new Error("Expected at least one tool fixture");
|
||||
}
|
||||
const wrapper = await createWrapper(
|
||||
"",
|
||||
false,
|
||||
undefined,
|
||||
{
|
||||
my_panel: {
|
||||
favorites: {
|
||||
model_class: "ToolSection",
|
||||
id: "favorites",
|
||||
name: "Favorites",
|
||||
tools: [firstToolId],
|
||||
},
|
||||
},
|
||||
},
|
||||
"my_panel",
|
||||
);
|
||||
const count = getUniqueToolIdsInPanel(toolsListInPanel).size;
|
||||
const formatted = count < 1000 ? `${count}` : `${Math.floor(count / 1000)}k+`;
|
||||
const discoverButton = wrapper.find('[data-description="toolbox discover tools"]');
|
||||
expect(discoverButton.text()).toBe(`Discover ${formatted} Tools`);
|
||||
expect(discoverButton.text()).not.toBe("Discover 1 Tools");
|
||||
});
|
||||
|
||||
it("loads default panel sections when My Tools is the backend default view", async () => {
|
||||
storePanelView("");
|
||||
const requestedPanels: string[] = [];
|
||||
await createWrapper(
|
||||
"",
|
||||
false,
|
||||
undefined,
|
||||
{
|
||||
my_panel: {
|
||||
favorites: {
|
||||
model_class: "ToolSection",
|
||||
id: "favorites",
|
||||
name: "Favorites",
|
||||
tools: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"my_panel",
|
||||
(panelView) => requestedPanels.push(panelView),
|
||||
);
|
||||
|
||||
expect(requestedPanels).toContain(DEFAULT_VIEW_ID);
|
||||
expect(requestedPanels).toContain("my_panel");
|
||||
});
|
||||
|
||||
it("does not request tool tags during default tool panel startup", async () => {
|
||||
let toolsRequestUrl: URL | undefined;
|
||||
let toolTagsRequested = false;
|
||||
|
||||
@@ -9,7 +9,7 @@ import localize from "@/utils/localization";
|
||||
import { errorMessageAsString } from "@/utils/simple-error";
|
||||
|
||||
import { MY_PANEL_VIEW_ID } from "./panelViews";
|
||||
import { countUniqueToolsInPanel } from "./utilities";
|
||||
import { countUniqueToolsInList, countUniqueToolsInPanel } from "./utilities";
|
||||
|
||||
import LoadingSpan from "../LoadingSpan.vue";
|
||||
import ActivityPanel from "./ActivityPanel.vue";
|
||||
@@ -36,14 +36,22 @@ const isMyPanel = computed(() => currentPanelView.value === MY_PANEL_VIEW_ID);
|
||||
const errorMessage = ref("");
|
||||
const panelsFetched = ref(false);
|
||||
const showFavorites = ref(false);
|
||||
const defaultToolSections = computed(() => {
|
||||
return (
|
||||
toolSections.value.default ||
|
||||
(defaultPanelView.value && defaultPanelView.value !== MY_PANEL_VIEW_ID
|
||||
? toolSections.value[defaultPanelView.value]
|
||||
: null)
|
||||
);
|
||||
});
|
||||
const headerToolSections = computed(() => {
|
||||
if (isMyPanel.value) {
|
||||
return toolSections.value[defaultPanelView.value] || toolSections.value.default || currentToolSections.value;
|
||||
return defaultToolSections.value || currentToolSections.value;
|
||||
}
|
||||
return currentToolSections.value;
|
||||
});
|
||||
const toolsCount = computed(() =>
|
||||
countUniqueToolsInPanel(headerToolSections.value, Object.keys(toolsById.value).length),
|
||||
countUniqueToolsInPanel(headerToolSections.value, countUniqueToolsInList(toolsById.value)),
|
||||
);
|
||||
|
||||
function formatToolsCount(count: number) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import toolsListInPanelUntyped from "@/components/ToolsView/testData/toolsListIn
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
countUniqueToolsInList,
|
||||
createSortedResultPanel,
|
||||
createWhooshQuery,
|
||||
determineWidth,
|
||||
@@ -98,6 +99,12 @@ describe("test helpers in tool searching utilities", () => {
|
||||
expect(createWhooshQuery({ section: '"Get Data"' })).toEqual('(section:("Get Data"))');
|
||||
});
|
||||
|
||||
it("counts versioned tools once when falling back to the full tools list", async () => {
|
||||
const firstTool = toolsList[0]!;
|
||||
const toolsWithOlderVersion = [...toolsList, { ...firstTool, id: `${firstTool.id}/0.9`, version: "0.9" }];
|
||||
expect(countUniqueToolsInList(toolsWithOlderVersion)).toBe(toolsList.length);
|
||||
});
|
||||
|
||||
it("lowercases tag clauses to match the Whoosh field analyzer", async () => {
|
||||
// Whoosh indexes tool_tags lowercased; lowercase the search clause too.
|
||||
expect(createWhooshQuery({ tag: ["Get Data"] })).toEqual('(tool_tags:("get data"))');
|
||||
|
||||
@@ -67,6 +67,24 @@ export function getUniqueToolIdsInPanel(panel: Record<string, ToolPanelItem> | n
|
||||
return toolIds;
|
||||
}
|
||||
|
||||
type CountableTool = Pick<Tool, "id"> & Partial<Pick<Tool, "version">>;
|
||||
|
||||
export function getVersionlessToolId(toolId: string, tool: CountableTool | undefined) {
|
||||
if (tool?.version && toolId.endsWith(`/${tool.version}`)) {
|
||||
return toolId.slice(0, -tool.version.length - 1);
|
||||
}
|
||||
return toolId;
|
||||
}
|
||||
|
||||
export function countUniqueToolsInList<T extends CountableTool>(tools: Record<string, T> | T[]) {
|
||||
const toolEntries = Array.isArray(tools) ? tools.map((tool) => [tool.id, tool] as const) : Object.entries(tools);
|
||||
const toolIds = new Set<string>();
|
||||
for (const [toolId, tool] of toolEntries) {
|
||||
toolIds.add(getVersionlessToolId(toolId, tool));
|
||||
}
|
||||
return toolIds.size;
|
||||
}
|
||||
|
||||
export function countUniqueToolsInPanel(panel: Record<string, ToolPanelItem> | null | undefined, fallbackCount = 0) {
|
||||
return getUniqueToolIdsInPanel(panel).size || fallbackCount;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ const MY_PANEL_VIEW: Panel = {
|
||||
searchable: true,
|
||||
};
|
||||
|
||||
const DEFAULT_PANEL_VIEW_ID = "default";
|
||||
|
||||
export const useToolStore = defineStore("toolStore", () => {
|
||||
const currentPanelView: Ref<string> = useUserLocalStorage("tool-store-view", "");
|
||||
const defaultPanelView: Ref<string> = ref("");
|
||||
@@ -369,8 +371,16 @@ export const useToolStore = defineStore("toolStore", () => {
|
||||
|
||||
async function setPanel(panelView: string) {
|
||||
try {
|
||||
if (panelView === MY_PANEL_VIEW_ID && defaultPanelView.value && panelView !== defaultPanelView.value) {
|
||||
await fetchToolSections(defaultPanelView.value);
|
||||
if (panelView === MY_PANEL_VIEW_ID) {
|
||||
const sectionedPanelView =
|
||||
defaultPanelView.value && defaultPanelView.value !== MY_PANEL_VIEW_ID
|
||||
? defaultPanelView.value
|
||||
: panels.value[DEFAULT_PANEL_VIEW_ID]
|
||||
? DEFAULT_PANEL_VIEW_ID
|
||||
: null;
|
||||
if (sectionedPanelView) {
|
||||
await fetchToolSections(sectionedPanelView);
|
||||
}
|
||||
}
|
||||
await fetchToolSections(panelView);
|
||||
currentPanelView.value = panelView;
|
||||
|
||||
Reference in New Issue
Block a user