Skip to content
Draft
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
30 changes: 30 additions & 0 deletions apps/app/src/components/plugin/PluginsOverview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,36 @@ describe("PluginsOverview", () => {
expect(builtInPills[0]?.parentElement?.className).toContain("py-0");
});

it("defaults to all plugins and can filter to built-in bb plugins", async () => {
installFetch(true, [
AUTOMATIONS_PLUGIN,
{
...AUTOMATIONS_PLUGIN,
id: "local-plugin",
name: "Local plugin",
source: "path:/plugins/local-plugin",
provenance: "direct",
},
]);
const { wrapper: QueryClientWrapper } = createQueryClientTestHarness();
render(
<MemoryRouter initialEntries={["/tools/plugins"]}>
<QueryClientWrapper>
<PluginsOverview />
</QueryClientWrapper>
</MemoryRouter>,
);

expect(await screen.findByText("Automations")).toBeTruthy();
expect(screen.getByText("Local plugin")).toBeTruthy();

fireEvent.pointerDown(screen.getByRole("button", { name: "Source" }));
fireEvent.click(screen.getByRole("menuitem", { name: "Built-in bb" }));

expect(screen.getByText("Automations")).toBeTruthy();
expect(screen.queryByText("Local plugin")).toBeNull();
});

it("uses the same passive provenance tag for built-in and BB Official plugins", async () => {
installFetch(true, [
AUTOMATIONS_PLUGIN,
Expand Down
54 changes: 41 additions & 13 deletions apps/app/src/components/plugin/PluginsOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ResourceCollectionPage,
ResourceCollectionViewport,
ResourceListState,
ResourceOptionMenu,
ResourceSortMenu,
ResourceToolbar,
type ResourceCollectionMode,
Expand All @@ -31,6 +32,7 @@ import {
} from "@/lib/route-paths";

type PluginsCollectionMode = "installed" | "browse";
type PluginSourceFilter = "all" | "builtin";

function modeFromSearchParams(
value: string | null,
Expand Down Expand Up @@ -69,6 +71,7 @@ export function PluginsOverview() {
const [installedSortDirection, setInstalledSortDirection] = useState<
"asc" | "desc"
>("asc");
const [sourceFilter, setSourceFilter] = useState<PluginSourceFilter>("all");
const [addDialog, setAddDialog] = useState<{
open: boolean;
initial: AddPluginInitial | null;
Expand All @@ -92,6 +95,9 @@ export function PluginsOverview() {
() =>
plugins
.filter((plugin) => {
if (sourceFilter === "builtin" && plugin.provenance !== "builtin") {
return false;
}
if (normalizedInstalledQuery.length === 0) return true;
return [
plugin.id,
Expand All @@ -114,11 +120,15 @@ export function PluginsOverview() {
);
return installedSortDirection === "asc" ? result : -result;
}),
[installedSortDirection, normalizedInstalledQuery, plugins],
[installedSortDirection, normalizedInstalledQuery, plugins, sourceFilter],
);
const installedPagination = useResourcePagination(visiblePlugins, {
pageSize: installedPageSize,
resetKey: [normalizedInstalledQuery, installedSortDirection].join("\u0000"),
resetKey: [
normalizedInstalledQuery,
sourceFilter,
installedSortDirection,
].join("\u0000"),
});
const hasInstalledPagination =
!listQuery.isError &&
Expand Down Expand Up @@ -188,16 +198,30 @@ export function PluginsOverview() {
onSearchChange={setInstalledQuery}
containedControls
controls={
<ResourceSortMenu
value="alpha"
direction={installedSortDirection}
options={[{ id: "alpha", label: "Plugin name" }]}
onChange={() =>
setInstalledSortDirection((current) =>
current === "asc" ? "desc" : "asc",
)
}
/>
<>
<ResourceOptionMenu
label="Source"
icon="PackageReceive"
value={sourceFilter}
options={[
{ id: "all", label: "All plugins" },
{ id: "builtin", label: "Built-in bb" },
]}
onChange={(value) =>
setSourceFilter(value as PluginSourceFilter)
}
/>
<ResourceSortMenu
value="alpha"
direction={installedSortDirection}
options={[{ id: "alpha", label: "Plugin name" }]}
onChange={() =>
setInstalledSortDirection((current) =>
current === "asc" ? "desc" : "asc",
)
}
/>
</>
}
/>
}
Expand Down Expand Up @@ -226,7 +250,11 @@ export function PluginsOverview() {
) : plugins.length > 0 && visiblePlugins.length === 0 ? (
<ResourceListState
state="empty"
message={`No plugins match "${installedQuery}"`}
message={
normalizedInstalledQuery.length > 0
? `No plugins match "${installedQuery}"`
: "No plugins match this filter."
}
/>
) : (
<InstalledPluginsTab plugins={installedPagination.items} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,18 @@ describe("PluginSettingsCompatibilityRoute", () => {
expect(screen.queryByText("Tools plugin detail")).toBeNull();
});

it("redirects legacy Settings plugin routes to Tools Hub when enabled", () => {
it("keeps plugin configuration in Settings while Tools Hub is enabled", () => {
renderRoute("/settings/plugins/example", true);

expect(screen.getByText("Tools plugin detail")).toBeTruthy();
expect(screen.queryByText("Settings plugin detail")).toBeNull();
expect(screen.getByText("Settings plugin detail")).toBeTruthy();
expect(screen.queryByText("Tools plugin detail")).toBeNull();
});

it("redirects the legacy Settings plugin manager to Tools Hub when enabled", () => {
renderRoute("/settings/plugins", true);

expect(screen.getByText("Tools plugins")).toBeTruthy();
expect(screen.queryByText("Settings plugin manager")).toBeNull();
});

it("renders neither management surface while configuration is loading", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { ReactNode } from "react";
import { Navigate, useParams } from "react-router-dom";
import { useSystemConfig } from "@/hooks/queries/system-queries";
import {
getPluginDetailRoutePath,
getPluginsRoutePath,
} from "@/lib/route-paths";
import { getPluginsRoutePath } from "@/lib/route-paths";

/** Keeps the existing Settings manager available while Tools Hub is off. */
/**
* Replaces the legacy plugin manager with Tools Hub while preserving each
* plugin's Settings page.
*/
export function PluginSettingsCompatibilityRoute({
children,
}: {
Expand All @@ -18,15 +18,7 @@ export function PluginSettingsCompatibilityRoute({

if (toolsHubEnabled === undefined) return null;
if (!toolsHubEnabled) return children;
if (pluginId !== undefined) return children;

return (
<Navigate
to={
pluginId
? getPluginDetailRoutePath({ pluginId })
: getPluginsRoutePath()
}
replace
/>
);
return <Navigate to={getPluginsRoutePath()} replace />;
}
19 changes: 17 additions & 2 deletions apps/app/src/components/settings/settings-nav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createQueryClientTestHarness } from "@/test/queryClientTestHarness";
import { useSettingsNavState } from "./settings-nav";

const mocks = vi.hoisted(() => ({
usePluginList: vi.fn(),
useSystemConfig: vi.fn(),
}));

Expand All @@ -18,7 +19,7 @@ vi.mock("@/hooks/queries/system-queries", () => ({
}));

vi.mock("@/hooks/queries/plugin-settings-queries", () => ({
usePluginList: () => ({ data: { plugins: [] } }),
usePluginList: mocks.usePluginList,
}));

vi.mock("@/hooks/useHostDaemon", () => ({
Expand All @@ -43,6 +44,7 @@ afterEach(() => {
});

beforeEach(() => {
mocks.usePluginList.mockReturnValue({ data: { plugins: [] } });
mocks.useSystemConfig.mockReset();
mocks.useSystemConfig.mockReturnValue({
data: {
Expand Down Expand Up @@ -109,6 +111,17 @@ describe("useSettingsNavState", () => {
});

it("removes plugin management from Settings while Tools Hub is enabled", () => {
mocks.usePluginList.mockReturnValue({
data: {
plugins: [
{
id: "connect",
enabled: true,
hasSettings: true,
},
],
},
});
mocks.useSystemConfig.mockReturnValue({
data: {
experiments: {
Expand All @@ -125,6 +138,8 @@ describe("useSettingsNavState", () => {
expect(result.current.sections.map((section) => section.id)).not.toContain(
"plugins",
);
expect(result.current.pluginEntries).toEqual([]);
expect(result.current.pluginEntries.map((plugin) => plugin.id)).toEqual([
"connect",
]);
});
});
15 changes: 6 additions & 9 deletions apps/app/src/components/settings/settings-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ export function useSettingsNavState(): SettingsNavState {
settingsSections.map((section) => section.pluginId),
);
const pluginListQuery = usePluginList({
enabled:
!toolsHubEnabled && (pluginsEnabled || settingsSectionPluginIds.size > 0),
enabled: pluginsEnabled || settingsSectionPluginIds.size > 0,
});

const pluginMatch = matchPath(SETTINGS_PLUGIN_ROUTE_PATH, location.pathname);
Expand Down Expand Up @@ -130,13 +129,11 @@ export function useSettingsNavState(): SettingsNavState {
}
return true;
});
const pluginEntries = toolsHubEnabled
? []
: (pluginListQuery.data?.plugins ?? []).filter(
(plugin) =>
plugin.enabled &&
(plugin.hasSettings || settingsSectionPluginIds.has(plugin.id)),
);
const pluginEntries = (pluginListQuery.data?.plugins ?? []).filter(
(plugin) =>
plugin.enabled &&
(plugin.hasSettings || settingsSectionPluginIds.has(plugin.id)),
);
return {
activePluginId,
activeProviderId,
Expand Down
Loading
Loading