FN-7454: show Linear Import in Plugin Manager

Register Linear Import as a built-in plugin so users can install and manage it from Plugin Manager.

- Add fusion-plugin-linear-import to the built-in plugin catalog.
- Extend Plugin Manager registry and rendering coverage for the Linear Import entry.
- Add a patch changeset documenting the operator-visible plugin visibility fix.

Files changed:
 .changeset/fn-7454-linear-plugin-visibility.md     |  7 ++
 .../dashboard/app/components/PluginManager.tsx     | 11 +++
 .../__tests__/PluginManager.registry.test.tsx      | 72 ++++++++++++++++++-
 .../components/__tests__/PluginManager.test.tsx    | 80 ++++++++++++++++++++++
 4 files changed, 168 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7454

Fusion-Task-Lineage: 1b54adb4-78cc-47cc-ad7a-d675de64fd49

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-02 18:06:00 -07:00
parent 765218f80f
commit 78ebeafcec
4 changed files with 168 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Show the bundled Linear Import plugin in Plugin Manager and dashboard plugin surfaces.
category: fix
dev: Keeps fusion-plugin-linear-import registered across the built-in Plugin Manager catalog while reusing existing registry, dashboard view, and bundled packaging paths.

View File

@@ -176,6 +176,17 @@ export const BUILTIN_PLUGINS: BuiltinPlugin[] = [
category: "integration",
path: "./plugins/fusion-plugin-compound-engineering",
},
/*
* FNXC:PluginManager 2026-07-02-17:56:
* FN-7454 keeps Linear Import in the built-in catalog because FN-7443 shipped the plugin package, registry entry, and dashboard view, but users still could not install or manage it from Plugin Manager without this bundled-plugin registration.
*/
{
id: "fusion-plugin-linear-import",
name: "Linear Import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
category: "integration",
path: "./plugins/fusion-plugin-linear-import",
},
{
id: BUILTIN_AGENT_BROWSER_PLUGIN_ID,
name: "Agent Browser",

View File

@@ -97,8 +97,8 @@ function stubEventSource() {
vi.stubGlobal("EventSource", MockEventSource);
}
async function renderRegistry(entries: RegistryPluginEntry[] = registryEntries) {
vi.mocked(fetchPlugins).mockResolvedValue([installedPlugin]);
async function renderRegistry(entries: RegistryPluginEntry[] = registryEntries, installed: PluginInstallation[] = [installedPlugin]) {
vi.mocked(fetchPlugins).mockResolvedValue(installed);
vi.mocked(fetchPluginRegistry).mockResolvedValue(entries);
render(<PluginManager addToast={addToast} />);
await act(async () => {
@@ -151,6 +151,74 @@ describe("PluginManager registry browsing", () => {
expect(within(comingSoon).getByText("Coming Soon")).toBeInTheDocument();
});
it.each([
{
state: "not-installed" as const,
entry: {
id: "fusion-plugin-linear-import",
name: "Linear Import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
version: "0.1.0",
author: "Fusion",
category: "integration" as const,
path: "./plugins/fusion-plugin-linear-import",
tags: ["linear", "import", "issues", "dashboard"],
installed: false,
canInstall: true,
},
installed: [] as PluginInstallation[],
action: "Install",
},
{
state: "installed-error" as const,
entry: {
id: "fusion-plugin-linear-import",
name: "Linear Import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
version: "0.1.0",
author: "Fusion",
category: "integration" as const,
path: "./plugins/fusion-plugin-linear-import",
tags: ["linear", "import", "issues", "dashboard"],
installed: true,
installedVersion: "0.1.0",
state: "error" as const,
canInstall: true,
},
installed: [{ ...installedPlugin, id: "fusion-plugin-linear-import", name: "Linear Import", state: "error" as const, enabled: true, error: "Linear plugin failed" }],
action: "Manage",
},
{
state: "installed-started" as const,
entry: {
id: "fusion-plugin-linear-import",
name: "Linear Import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
version: "0.1.0",
author: "Fusion",
category: "integration" as const,
path: "./plugins/fusion-plugin-linear-import",
tags: ["linear", "import", "issues", "dashboard"],
installed: true,
installedVersion: "0.1.0",
state: "started" as const,
canInstall: true,
},
installed: [{ ...installedPlugin, id: "fusion-plugin-linear-import", name: "Linear Import", state: "started" as const, enabled: true }],
action: "Manage",
},
])("shows Linear Import registry action for $state", async ({ entry, installed, action }) => {
await renderRegistry([entry], installed);
const section = screen.getByRole("region", { name: "Browse Registry" });
const linear = within(section).getByText("Linear Import").closest(".plugin-registry-item") as HTMLElement;
expect(linear).toBeInTheDocument();
expect(within(linear).getByText("Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.")).toBeInTheDocument();
expect(within(linear).getByText("integration")).toBeInTheDocument();
expect(within(linear).getByRole("button", { name: action })).toBeInTheDocument();
expect(within(section).getAllByText("Linear Import")).toHaveLength(1);
});
it("installs registry plugins with their manifest path and refreshes installed plugins", async () => {
await renderRegistry();
expect(fetchPlugins).toHaveBeenCalledTimes(1);

View File

@@ -113,6 +113,7 @@ vi.mock("../../hooks/useConfirm", () => ({
import {
AGENT_BROWSER_SETTINGS_SCHEMA,
BUILTIN_AGENT_BROWSER_PLUGIN_ID,
BUILTIN_PLUGINS,
PluginManager,
STATE_COLORS,
} from "../PluginManager";
@@ -131,6 +132,15 @@ import {
} from "../../api";
const addToast = vi.fn();
const LINEAR_PLUGIN_ID = "fusion-plugin-linear-import";
function getBuiltInPluginCard(name: string): HTMLElement {
const card = screen.getAllByText(name)
.map((node) => node.closest(".plugin-builtins-item"))
.find((node): node is HTMLElement => node instanceof HTMLElement);
expect(card).toBeTruthy();
return card;
}
function expectEventsUrl(url: string, projectId?: string) {
const parsed = new URL(url, "http://localhost");
@@ -271,9 +281,79 @@ describe("PluginManager", () => {
expect(screen.getByText("Reports")).toBeTruthy();
expect(screen.getByText("WhatsApp Chat")).toBeTruthy();
expect(screen.getByText("CLI Printing Press")).toBeTruthy();
expect(screen.getByText("Linear Import")).toBeTruthy();
expect(screen.getByText(/Pairs to WhatsApp Web \(multi-device\) with QR or pairing code/i)).toBeTruthy();
});
it("keeps Linear Import in the bundled plugin catalog", () => {
expect(BUILTIN_PLUGINS.find((plugin) => plugin.id === LINEAR_PLUGIN_ID)).toMatchObject({
id: LINEAR_PLUGIN_ID,
name: "Linear Import",
category: "integration",
path: "./plugins/fusion-plugin-linear-import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
});
});
it("installs Linear Import from the built-in section when not installed", async () => {
vi.mocked(fetchPlugins).mockResolvedValue([]);
render(<PluginManager addToast={addToast} />);
await waitFor(() => {
expect(fetchPlugins).toHaveBeenCalled();
});
const linearCard = getBuiltInPluginCard("Linear Import");
expect(within(linearCard).getByText("Not installed")).toBeTruthy();
const installButton = within(linearCard).getByRole("button", { name: /Install Linear Import/i });
await userEvent.click(installButton);
await waitFor(() => {
expect(installPlugin).toHaveBeenCalledWith({ path: "./plugins/fusion-plugin-linear-import" }, undefined);
expect(addToast).toHaveBeenCalledWith("Linear Import installed globally", "success");
});
});
it.each([
{ state: "installed" as const, enabled: false, statusLabel: "installed" },
{ state: "started" as const, enabled: true, statusLabel: "started" },
{ state: "error" as const, enabled: true, statusLabel: "error" },
])("shows Manage for installed Linear Import in $state state", async ({ state, enabled, statusLabel }) => {
vi.mocked(fetchPlugins).mockResolvedValue([
{
...mockPlugins[0],
id: LINEAR_PLUGIN_ID,
name: "Linear Import",
description: "Browse Linear issues and import selected issues as Fusion triage tasks through plugin-owned settings.",
state,
enabled,
error: state === "error" ? "Linear plugin failed to start" : undefined,
settingsSchema: {},
},
]);
render(<PluginManager addToast={addToast} />);
await waitFor(() => {
expect(screen.getAllByText("Linear Import").length).toBeGreaterThanOrEqual(2);
});
const linearCard = getBuiltInPluginCard("Linear Import");
expect(within(linearCard).getByText("Installed")).toBeTruthy();
expect(within(linearCard).queryByText("Built-in metadata only")).toBeNull();
expect(within(linearCard).getAllByText("integration").length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText(statusLabel).length).toBeGreaterThanOrEqual(1);
const manageButton = within(linearCard).getByRole("button", { name: /^Manage$/i });
expect(manageButton).not.toBeDisabled();
await userEvent.click(manageButton);
await waitFor(() => {
expect(fetchPluginSettings).toHaveBeenCalledWith(LINEAR_PLUGIN_ID, undefined);
});
expect(screen.getByTestId("plugin-manager-detail")).toBeTruthy();
});
it("renders built-in agent browser metadata-only entry when uninstalled", async () => {
render(<PluginManager addToast={addToast} />);