mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
feat: request a feature link for plugins and use the bug form for unverified plugins
This commit is contained in:
1 parent
8666533162
commit
6a855c0fa8
5 files changed
+70
-45
No files matched your search
@@ -2167,6 +2167,7 @@
|
|||||||
"unverifiedHint": "Installed from a file. Termix did not check or sign it.",
|
"unverifiedHint": "Installed from a file. Termix did not check or sign it.",
|
||||||
"reportIssue": "Report an issue",
|
"reportIssue": "Report an issue",
|
||||||
"reportTo": "Report to {{author}}",
|
"reportTo": "Report to {{author}}",
|
||||||
|
"requestFeature": "Request a feature",
|
||||||
"openSettings": "Open its settings"
|
"openSettings": "Open its settings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Database,
|
Database,
|
||||||
Download,
|
Download,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
Lightbulb,
|
||||||
Pin,
|
Pin,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -35,6 +36,7 @@ import {
|
|||||||
formatBytes,
|
formatBytes,
|
||||||
orderByRisk,
|
orderByRisk,
|
||||||
reportIssueUrl,
|
reportIssueUrl,
|
||||||
|
requestFeatureUrl,
|
||||||
type PluginEntry,
|
type PluginEntry,
|
||||||
} from "./plugin-model";
|
} from "./plugin-model";
|
||||||
import { openPluginSettings } from "./open-plugins";
|
import { openPluginSettings } from "./open-plugins";
|
||||||
@@ -77,6 +79,7 @@ export function PluginDetail({
|
|||||||
plugin,
|
plugin,
|
||||||
import.meta.env.VITE_APP_VERSION || undefined,
|
import.meta.env.VITE_APP_VERSION || undefined,
|
||||||
);
|
);
|
||||||
|
const featureUrl = requestFeatureUrl(plugin);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -398,6 +401,20 @@ export function PluginDetail({
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{featureUrl && (
|
||||||
|
<a
|
||||||
|
href={featureUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={LINK_ROW}
|
||||||
|
>
|
||||||
|
<Lightbulb className="size-3.5 shrink-0" />
|
||||||
|
<span className="truncate">
|
||||||
|
{t("plugins.manager.requestFeature")}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
{plugin.repository && (
|
{plugin.repository && (
|
||||||
<a
|
<a
|
||||||
href={plugin.repository}
|
href={plugin.repository}
|
||||||
|
|||||||
@@ -325,10 +325,9 @@ export function formatCount(count: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A new GitHub issue on the plugin's own repo with what a maintainer asks
|
* A new bug report on the plugin's own repo with what a maintainer asks first
|
||||||
* first already filled in. Official plugins use the org bug form; a third
|
* already filled in. Official plugins use the org bug form and the plugin
|
||||||
* party repo may not have it, so it gets a plain prefilled body. Null when
|
* template ships the same one. Null when the repository is not on GitHub.
|
||||||
* the repository is not on GitHub.
|
|
||||||
*/
|
*/
|
||||||
export function reportIssueUrl(
|
export function reportIssueUrl(
|
||||||
plugin: Pick<
|
plugin: Pick<
|
||||||
@@ -347,38 +346,25 @@ export function reportIssueUrl(
|
|||||||
if (!repo) return null;
|
if (!repo) return null;
|
||||||
const version = plugin.version ?? plugin.latestVersion ?? "not installed";
|
const version = plugin.version ?? plugin.latestVersion ?? "not installed";
|
||||||
const state = `Plugin ${plugin.id}, ${plugin.source}, ${plugin.status ?? "not installed"}`;
|
const state = `Plugin ${plugin.id}, ${plugin.source}, ${plugin.status ?? "not installed"}`;
|
||||||
if (plugin.source !== "unverified") {
|
return buildIssueUrl(repo, {
|
||||||
return buildIssueUrl(repo, {
|
template: "bug_report.yml",
|
||||||
template: "bug_report.yml",
|
fields: {
|
||||||
fields: {
|
"termix-version": termixVersion,
|
||||||
"termix-version": termixVersion,
|
"plugin-version": version,
|
||||||
"plugin-version": version,
|
environment: `${describeEnvironment()}
|
||||||
environment: `${describeEnvironment()}\n${state}`,
|
${state}`,
|
||||||
logs: plugin.lastError,
|
logs: plugin.lastError,
|
||||||
},
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
const lines = [
|
|
||||||
`**Plugin:** ${plugin.id}`,
|
|
||||||
`**Version:** ${version}`,
|
|
||||||
`**Source:** ${plugin.source}`,
|
|
||||||
`**State:** ${plugin.status ?? "not installed"}`,
|
|
||||||
...(termixVersion ? [`**Termix:** ${termixVersion}`] : []),
|
|
||||||
`**Environment:** ${describeEnvironment()}`,
|
|
||||||
...(plugin.lastError
|
|
||||||
? ["", "**Error:**", "```", plugin.lastError, "```"]
|
|
||||||
: []),
|
|
||||||
"",
|
|
||||||
"**What happened:**",
|
|
||||||
"",
|
|
||||||
"**What you expected:**",
|
|
||||||
"",
|
|
||||||
];
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
title: `[${plugin.id}] `,
|
|
||||||
body: lines.join("\n"),
|
|
||||||
});
|
});
|
||||||
return `${repo}/issues/new?${params.toString()}`;
|
}
|
||||||
|
|
||||||
|
/** A new feature request on the plugin's own repo, or null when not on GitHub. */
|
||||||
|
export function requestFeatureUrl(
|
||||||
|
plugin: Pick<PluginEntry, "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. */
|
/** One version in a plugin's release notes. */
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ describe("PluginsStep", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
await screen.findByText("TERM");
|
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 () => {
|
it("groups plugins by category and puts consent plugins in their own section", async () => {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
pluginSource,
|
pluginSource,
|
||||||
pluginStatus,
|
pluginStatus,
|
||||||
reportIssueUrl,
|
reportIssueUrl,
|
||||||
|
requestFeatureUrl,
|
||||||
sortPlugins,
|
sortPlugins,
|
||||||
uploadEntry,
|
uploadEntry,
|
||||||
youtubeEmbedUrl,
|
youtubeEmbedUrl,
|
||||||
@@ -289,19 +290,16 @@ describe("reportIssueUrl", () => {
|
|||||||
expect(parsed.searchParams.has("body")).toBe(false);
|
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(
|
const parsed = new URL(
|
||||||
reportIssueUrl({ ...base, source: "unverified" }, "26.10.0")!,
|
reportIssueUrl({ ...base, source: "unverified" }, "26.10.0")!,
|
||||||
);
|
);
|
||||||
expect(parsed.searchParams.has("template")).toBe(false);
|
expect(parsed.searchParams.get("template")).toBe("bug_report.yml");
|
||||||
expect(parsed.searchParams.get("title")).toBe("[docker] ");
|
expect(parsed.searchParams.get("plugin-version")).toBe("1.0.0");
|
||||||
const body = parsed.searchParams.get("body")!;
|
expect(parsed.searchParams.get("environment")).toContain(
|
||||||
expect(body).toContain("**Plugin:** docker");
|
"Plugin docker, unverified, failed",
|
||||||
expect(body).toContain("**Version:** 1.0.0");
|
);
|
||||||
expect(body).toContain("**Source:** unverified");
|
expect(parsed.searchParams.has("body")).toBe(false);
|
||||||
expect(body).toContain("**State:** failed");
|
|
||||||
expect(body).toContain("**Termix:** 26.10.0");
|
|
||||||
expect(body).toContain("boom");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gives nothing for a repo that is not on GitHub", () => {
|
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", () => {
|
describe("mergeReleaseNotes", () => {
|
||||||
const registryVersion = (version: string, extra = {}) => ({
|
const registryVersion = (version: string, extra = {}) => ({
|
||||||
version,
|
version,
|
||||||
|
|||||||
Reference in new issue
Block a user