From 567d0728888cf6605557c50378eda621963afe0a Mon Sep 17 00:00:00 2001 From: mbeaulne Date: Mon, 29 Jun 2026 13:04:49 -0400 Subject: [PATCH] Add source to component search v1 and v2 --- .../FlowSidebar/components/ComponentItem.tsx | 11 ++- .../components/SearchResults.test.tsx | 99 +++++++++++++++++++ .../DashboardComponentsV2View.test.tsx | 39 +++++++- .../Dashboard/DashboardComponentsV2View.tsx | 11 ++- .../ComponentSearchResults.test.tsx | 22 +++++ .../components/ComponentSearchResults.tsx | 1 + 6 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx index efdadbd83..3c7598a0a 100644 --- a/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx +++ b/src/components/shared/ReactFlow/FlowSidebar/components/ComponentItem.tsx @@ -120,6 +120,11 @@ const ComponentMarkup = ({ owned, published_by: publishedBy, } = component; + const componentIdentifier = publishedBy + ? `Published by ${publishedBy}` + : digest + ? `Digest ${digest}` + : undefined; const displayName = useMemo( () => name ?? getComponentName({ spec, url }), @@ -267,14 +272,14 @@ const ComponentMarkup = ({ > {displayName} - {publishedBy && ( + {componentIdentifier && ( - Published by {publishedBy} + {componentIdentifier} )} {matchExplanation && ( diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx new file mode 100644 index 000000000..cfc1e00f1 --- /dev/null +++ b/src/components/shared/ReactFlow/FlowSidebar/components/SearchResults.test.tsx @@ -0,0 +1,99 @@ +import { render, screen } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { describe, expect, it, vi } from "vitest"; + +import type { SearchResult } from "@/types/componentLibrary"; +import { ComponentSearchFilter } from "@/utils/constants"; + +import SearchResults from "./SearchResults"; + +vi.mock("@/components/shared/Dialogs", () => ({ + ComponentDetailsDialog: () => , +})); + +vi.mock("@/components/shared/FavoriteComponentToggle", () => ({ + ComponentFavoriteToggle: () => , +})); + +vi.mock( + "@/components/shared/ManageComponent/hooks/useOutdatedComponents", + () => ({ + useOutdatedComponents: () => ({ data: [] }), + }), +); + +vi.mock("@/components/shared/Settings/useFlags", () => ({ + useFlagValue: () => false, +})); + +vi.mock("@/providers/ComponentLibraryProvider/ForcedSearchProvider", () => ({ + useForcedSearchContext: () => ({ + currentSearchFilter: { + searchTerm: "scrape", + filters: [ComponentSearchFilter.NAME], + }, + }), +})); + +vi.mock("../../NodesOverlay/NodesOverlayProvider", () => ({ + useNodesOverlay: () => ({ + notifyNode: vi.fn(), + getNodeIdsByDigest: vi.fn(() => []), + fitNodeIntoView: vi.fn(), + }), +})); + +vi.mock("./ComponentHoverPopover", () => ({ + ComponentHoverPopover: ({ children }: { children: ReactNode }) => ( + <>{children} + ), +})); + +describe("SearchResults", () => { + it("shows publisher metadata for v1 component search results", () => { + const searchResult: SearchResult = { + components: { + standard: [ + { + digest: "published-digest", + name: "Scrape V2", + published_by: "pipeline-components@shopify.com", + }, + ], + user: [], + used: [], + }, + }; + + render( + , + ); + + expect(screen.getByText("Scrape V2")).toBeInTheDocument(); + expect( + screen.getByText("Published by pipeline-components@shopify.com"), + ).toBeInTheDocument(); + }); + + it("shows digest metadata when a v1 component search result has no publisher", () => { + const searchResult: SearchResult = { + components: { + standard: [ + { + digest: "sha256:abc123", + name: "Upload to GCS", + }, + ], + user: [], + used: [], + }, + }; + + render( + , + ); + + expect(screen.getByText("Upload to GCS")).toBeInTheDocument(); + expect(screen.getByText("Digest sha256:abc123")).toBeInTheDocument(); + }); +}); diff --git a/src/routes/Dashboard/DashboardComponentsV2View.test.tsx b/src/routes/Dashboard/DashboardComponentsV2View.test.tsx index 72bfa0ac4..b9fed3f39 100644 --- a/src/routes/Dashboard/DashboardComponentsV2View.test.tsx +++ b/src/routes/Dashboard/DashboardComponentsV2View.test.tsx @@ -35,6 +35,10 @@ const routeMocks = vi.hoisted(() => { return { standard: makeComponent("standard-digest", "Standard component"), registered: makeComponent("registered-digest", "Registered component"), + published: { + ...makeComponent("published-digest", "Published component"), + published_by: "pipeline-components@shopify.com", + }, user: makeComponent("user-digest", "User component"), extraStandardComponents: [] as ComponentReference[], navigate: vi.fn(), @@ -81,7 +85,7 @@ vi.mock("@tanstack/react-query", () => ({ } if (key === "component-search-v2" && queryKey[1] === "published") { - return { data: [], isLoading: false }; + return { data: [routeMocks.published], isLoading: false }; } if ( @@ -108,6 +112,7 @@ vi.mock("@tanstack/react-query", () => ({ data: [ routeMocks.standard, routeMocks.registered, + routeMocks.published, routeMocks.user, ...routeMocks.extraStandardComponents, ], @@ -536,13 +541,13 @@ describe("DashboardComponentsV2View", () => { expect( screen.getByText( - "Showing 100 of 108 components in selected sources. Start typing to search.", + "Showing 100 of 109 components in selected sources. Start typing to search.", ), ).toBeInTheDocument(); expect(screen.getByText("Browse component 099")).toBeInTheDocument(); expect(screen.queryByText("Browse component 100")).not.toBeInTheDocument(); - fireEvent.click(screen.getByRole("button", { name: "Show 8 more" })); + fireEvent.click(screen.getByRole("button", { name: "Show 9 more" })); await waitFor(() => { expect(screen.getByText("Browse component 104")).toBeInTheDocument(); @@ -635,6 +640,34 @@ describe("DashboardComponentsV2View", () => { }); }); + it("shows publisher on published component result cards", async () => { + render(); + + fireEvent.change(screen.getByLabelText("Search components"), { + target: { value: "published" }, + }); + + await waitFor(() => { + expect(screen.getByText("Published component")).toBeInTheDocument(); + }); + expect( + screen.getByText("Published by pipeline-components@shopify.com"), + ).toBeInTheDocument(); + }); + + it("keeps publisher visible in compact selected-result rows", async () => { + routeMocks.search = { component: "published-digest", q: "published" }; + + render(); + + await waitFor(() => { + expect(screen.getByText("Published component")).toBeInTheDocument(); + }); + expect( + screen.getByText("Published by pipeline-components@shopify.com"), + ).toBeInTheDocument(); + }); + it("explains why lexical component results matched", async () => { render(); diff --git a/src/routes/Dashboard/DashboardComponentsV2View.tsx b/src/routes/Dashboard/DashboardComponentsV2View.tsx index 9d945748c..ef7184589 100644 --- a/src/routes/Dashboard/DashboardComponentsV2View.tsx +++ b/src/routes/Dashboard/DashboardComponentsV2View.tsx @@ -372,9 +372,14 @@ const ComponentCard = ({ )} - {publishedBy && !isDetailOpen && ( - - by {publishedBy} + {publishedBy && ( + + Published by {publishedBy} )} {matchSummary && ( diff --git a/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx b/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx index 69cd6dfa0..3548e05ec 100644 --- a/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx +++ b/src/routes/v2/pages/Editor/components/ComponentSearchResults.test.tsx @@ -12,13 +12,16 @@ vi.mock( ComponentMarkup: ({ component, matchedFields, + source, }: { component: { name?: string }; matchedFields?: string[]; + source?: { label: string }; }) => (
  • {component.name} {matchedFields && matched {matchedFields.join(",")}} + {source && source {source.label}}
  • ), IONodeSidebarItem: () =>
  • IO node
  • , @@ -109,6 +112,25 @@ describe("ComponentSearchResults", () => { expect(onSuggestedSearch).toHaveBeenCalledWith("csv"); }); + it("passes source metadata through for result display", () => { + const results: ComponentSearchV2Result[] = [ + { + reference: { + digest: "digest", + name: "Load CSV", + published_by: "pipeline-components@shopify.com", + }, + source: { kind: "published", id: "published", label: "Published" }, + }, + ]; + + render( + , + ); + + expect(screen.getByText("source Published")).toBeInTheDocument(); + }); + it("passes matched fields through for result explanations", () => { const results: ComponentSearchV2Result[] = [ { diff --git a/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx b/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx index d7c40761e..f6ccf1ab8 100644 --- a/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx +++ b/src/routes/v2/pages/Editor/components/ComponentSearchResults.tsx @@ -154,6 +154,7 @@ export function ComponentSearchResults({ matchedFields={result.matchedFields} rerankScore={result.rerankScore} rerankReason={result.rerankReason} + source={result.source} /> ))}