Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/app/src/hooks/queries/plugin-settings-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface PluginListItem {
source: string;
isOrphanedBuiltin: boolean;
catalogEntryId: string | null;
catalogMarketplaceName: string | null;
publisherLabel: string | null;
sourceDisplay: string;
updateState: PluginUpdateState;
Expand Down Expand Up @@ -108,6 +109,7 @@ export function toPluginListItem(plugin: InstalledPlugin): PluginListItem {
source: plugin.source,
isOrphanedBuiltin: plugin.isOrphanedBuiltin,
catalogEntryId: plugin.catalogEntryId ?? null,
catalogMarketplaceName: plugin.catalogMarketplaceName ?? null,
publisherLabel: plugin.publisherLabel,
sourceDisplay: plugin.sourceDisplay,
updateState: {
Expand Down
1 change: 1 addition & 0 deletions apps/app/src/test/fixtures/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function makePluginListItem(
provenance: "direct",
isOrphanedBuiltin: false,
catalogEntryId: null,
catalogMarketplaceName: null,
publisherLabel: null,
sourceDisplay: "path · /plugins/plugin-test",
updateState: {
Expand Down
163 changes: 163 additions & 0 deletions apps/app/src/views/ToolsView.plugin-detail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,168 @@ describe("PluginDetail official catalog lifecycle", () => {
});

describe("BB Official plugin detail routing", () => {
it("uses the installed catalog identity when plugin ids collide", async () => {
const firstCatalogEntry = {
...GITHUB_CATALOG_ENTRY,
marketplace: "bb-community",
marketplaceDisplayName: "BB Community",
publisherKey: "bb-community:example",
publisherLabel: "BB Community",
description: "Description from the first catalog.",
repositoryUrl: "https://github.com/example/first-catalog-plugin",
author: {
name: "First publisher",
github: "first-publisher",
url: "https://github.com/first-publisher",
},
};
const installedCatalogEntry = {
...GITHUB_CATALOG_ENTRY,
marketplace: "partner-catalog",
marketplaceDisplayName: "Partner Catalog",
publisherKey: "partner-catalog:example",
publisherLabel: "Partner Catalog",
description: "Description from the installed catalog.",
repositoryUrl: "https://github.com/example/installed-catalog-plugin",
author: {
name: "Installed publisher",
github: "installed-publisher",
url: "https://github.com/installed-publisher",
},
installed: true,
};
vi.stubGlobal(
"fetch",
vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/v1/plugins") {
return new Response(
JSON.stringify({
enabled: true,
plugins: [
{
...GITHUB_PLUGIN,
catalogMarketplaceName: "partner-catalog",
publisherLabel: "Partner Catalog",
iconUrl: null,
screenshots: [],
collections: [],
providerIds: [],
icons: {},
updateState: {},
},
],
}),
{ headers: { "content-type": "application/json" } },
);
}
if (url.startsWith("/api/v1/plugin-catalog/search")) {
return new Response(
JSON.stringify({
results: [firstCatalogEntry, installedCatalogEntry],
collections: [],
}),
{ headers: { "content-type": "application/json" } },
);
}
return new Response(JSON.stringify({ error: "not found" }), {
status: 404,
headers: { "content-type": "application/json" },
});
}),
);

const { wrapper: QueryClientWrapper } = createQueryClientTestHarness();
render(
<MemoryRouter initialEntries={["/extensions/plugins/github"]}>
<Routes>
<Route path="/extensions/plugins/*" element={<RoutedToolsView />} />
</Routes>
</MemoryRouter>,
{ wrapper: QueryClientWrapper },
);

await waitFor(() => {
expect(
document.querySelector("[data-plugin-summary]")?.textContent,
).toBe("Description from the installed catalog.");
});
});

it("uses installed metadata when its catalog entry is unavailable", async () => {
const unrelatedCatalogEntry = {
...GITHUB_CATALOG_ENTRY,
marketplace: "bb-community",
marketplaceDisplayName: "BB Community",
publisherKey: "bb-community:example",
publisherLabel: "BB Community",
description: "Description from an unrelated catalog entry.",
repositoryUrl: "https://github.com/example/unrelated-catalog-plugin",
author: {
name: "Unrelated publisher",
github: "unrelated-publisher",
url: "https://github.com/unrelated-publisher",
},
};
vi.stubGlobal(
"fetch",
vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url === "/api/v1/plugins") {
return new Response(
JSON.stringify({
enabled: true,
plugins: [
{
...GITHUB_PLUGIN,
description: "Description from installed metadata.",
catalogMarketplaceName: "partner-catalog",
publisherLabel: "Partner Catalog",
iconUrl: null,
screenshots: [],
collections: [],
providerIds: [],
icons: {},
updateState: {},
},
],
}),
{ headers: { "content-type": "application/json" } },
);
}
if (url.startsWith("/api/v1/plugin-catalog/search")) {
return new Response(
JSON.stringify({
results: [unrelatedCatalogEntry],
collections: [],
}),
{ headers: { "content-type": "application/json" } },
);
}
return new Response(JSON.stringify({ error: "not found" }), {
status: 404,
headers: { "content-type": "application/json" },
});
}),
);

const { wrapper: QueryClientWrapper } = createQueryClientTestHarness();
render(
<MemoryRouter initialEntries={["/extensions/plugins/github"]}>
<Routes>
<Route path="/extensions/plugins/*" element={<RoutedToolsView />} />
</Routes>
</MemoryRouter>,
{ wrapper: QueryClientWrapper },
);

await waitFor(() => {
expect(
document.querySelector("[data-plugin-summary]")?.textContent,
).toBe("Description from installed metadata.");
});
});

it("resolves an uninstalled catalog plugin and opens its install confirmation", async () => {
vi.stubGlobal(
"fetch",
Expand Down Expand Up @@ -763,6 +925,7 @@ describe("BB Official plugin detail routing", () => {
plugins: [
{
...GITHUB_PLUGIN,
catalogMarketplaceName: "bb-official",
iconUrl: null,
screenshots: [],
collections: [],
Expand Down
18 changes: 16 additions & 2 deletions apps/app/src/views/ToolsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,23 @@ function PluginDetailToolView({ pluginId }: { pluginId: string }) {
const isLoading = listQuery.isFetching && listQuery.data === undefined;
const selectedPlugin =
plugins.find((plugin) => plugin.id === pluginId) ?? null;
const selectedCatalogEntryId = selectedPlugin?.catalogEntryId ?? null;
const selectedCatalogMarketplaceName =
selectedPlugin?.catalogMarketplaceName ?? null;
const selectedCatalogEntry =
catalogQuery.data?.entries.find((entry) => entry.pluginId === pluginId) ??
null;
selectedPlugin === null
? (catalogQuery.data?.entries.find(
(entry) => entry.pluginId === pluginId,
) ?? null)
: selectedCatalogEntryId === null ||
selectedCatalogMarketplaceName === null
? null
: (catalogQuery.data?.entries.find(
(entry) =>
entry.pluginId === selectedPlugin.id &&
entry.entryId === selectedCatalogEntryId &&
entry.marketplace === selectedCatalogMarketplaceName,
) ?? null);
useResourceRouteLabel(
selectedPlugin?.name ??
selectedPlugin?.id ??
Expand Down
Loading