From 6a855c0fa80f5fb089de8fa3fb4215f4af826e87 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 7 Oct 2026 10:39:52 -0500 Subject: [PATCH] feat: request a feature link for plugins and use the bug form for unverified plugins --- src/ui/locales/en.json | 1 + src/ui/plugins/PluginDetail.tsx | 17 ++++++ src/ui/plugins/plugin-model.ts | 56 +++++++------------ .../onboarding/steps/PluginsStep.test.tsx | 3 + src/ui/tests/plugins/plugin-model.test.ts | 38 +++++++++---- 5 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 50c3e0f3a..374b130f4 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -2167,6 +2167,7 @@ "unverifiedHint": "Installed from a file. Termix did not check or sign it.", "reportIssue": "Report an issue", "reportTo": "Report to {{author}}", + "requestFeature": "Request a feature", "openSettings": "Open its settings" } }, diff --git a/src/ui/plugins/PluginDetail.tsx b/src/ui/plugins/PluginDetail.tsx index 3634bb9a0..b3361de6d 100644 --- a/src/ui/plugins/PluginDetail.tsx +++ b/src/ui/plugins/PluginDetail.tsx @@ -6,6 +6,7 @@ import { Database, Download, ExternalLink, + Lightbulb, Pin, RotateCcw, Settings, @@ -35,6 +36,7 @@ import { formatBytes, orderByRisk, reportIssueUrl, + requestFeatureUrl, type PluginEntry, } from "./plugin-model"; import { openPluginSettings } from "./open-plugins"; @@ -77,6 +79,7 @@ export function PluginDetail({ plugin, import.meta.env.VITE_APP_VERSION || undefined, ); + const featureUrl = requestFeatureUrl(plugin); return (
)} + {featureUrl && ( + + + + {t("plugins.manager.requestFeature")} + + + )} + {plugin.repository && ( , +): string | null { + const repo = normalizeGitHubRepo(plugin.repository); + if (!repo) return null; + return buildIssueUrl(repo, { template: "feature_request.yml" }); } /** One version in a plugin's release notes. */ diff --git a/src/ui/tests/onboarding/steps/PluginsStep.test.tsx b/src/ui/tests/onboarding/steps/PluginsStep.test.tsx index d4836123e..3958011ad 100644 --- a/src/ui/tests/onboarding/steps/PluginsStep.test.tsx +++ b/src/ui/tests/onboarding/steps/PluginsStep.test.tsx @@ -99,6 +99,9 @@ describe("PluginsStep", () => { />, ); await screen.findByText("TERM"); + // findByText resolves on the DOM change, before the effect that hands + // over the loaded apply has run. Flush it so beforeNext is never stale. + await act(async () => {}); } it("groups plugins by category and puts consent plugins in their own section", async () => { diff --git a/src/ui/tests/plugins/plugin-model.test.ts b/src/ui/tests/plugins/plugin-model.test.ts index 7d2ec35d2..7ae35b1f5 100644 --- a/src/ui/tests/plugins/plugin-model.test.ts +++ b/src/ui/tests/plugins/plugin-model.test.ts @@ -13,6 +13,7 @@ import { pluginSource, pluginStatus, reportIssueUrl, + requestFeatureUrl, sortPlugins, uploadEntry, youtubeEmbedUrl, @@ -289,19 +290,16 @@ describe("reportIssueUrl", () => { expect(parsed.searchParams.has("body")).toBe(false); }); - it("uses a plain body for an unverified plugin", () => { + it("fills the same bug form on an unverified plugin repo", () => { const parsed = new URL( reportIssueUrl({ ...base, source: "unverified" }, "26.10.0")!, ); - expect(parsed.searchParams.has("template")).toBe(false); - expect(parsed.searchParams.get("title")).toBe("[docker] "); - const body = parsed.searchParams.get("body")!; - expect(body).toContain("**Plugin:** docker"); - expect(body).toContain("**Version:** 1.0.0"); - expect(body).toContain("**Source:** unverified"); - expect(body).toContain("**State:** failed"); - expect(body).toContain("**Termix:** 26.10.0"); - expect(body).toContain("boom"); + expect(parsed.searchParams.get("template")).toBe("bug_report.yml"); + expect(parsed.searchParams.get("plugin-version")).toBe("1.0.0"); + expect(parsed.searchParams.get("environment")).toContain( + "Plugin docker, unverified, failed", + ); + expect(parsed.searchParams.has("body")).toBe(false); }); it("gives nothing for a repo that is not on GitHub", () => { @@ -314,6 +312,26 @@ describe("reportIssueUrl", () => { }); }); +describe("requestFeatureUrl", () => { + it("opens the feature form on the plugin repo", () => { + const parsed = new URL( + requestFeatureUrl({ + repository: "https://github.com/Termix-SSH/Plugin-Docker/", + })!, + ); + expect(parsed.origin + parsed.pathname).toBe( + "https://github.com/Termix-SSH/Plugin-Docker/issues/new", + ); + expect(parsed.searchParams.get("template")).toBe("feature_request.yml"); + }); + + it("gives nothing for a repo that is not on GitHub", () => { + expect( + requestFeatureUrl({ repository: "https://gitlab.com/a/b" }), + ).toBeNull(); + }); +}); + describe("mergeReleaseNotes", () => { const registryVersion = (version: string, extra = {}) => ({ version,