mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Add a Report a bug button to plugin detail pages (#6721)
Links to a new issue on the plugin's GitHub repository, from the registry's repository URL. Monorepo folder URLs resolve to the repository; non-GitHub sources show no button. Co-authored-by: paseo-bot[bot] <paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
paseo-bot[bot]
parent
e1b01df905
commit
8bc5f47b1c
@@ -98,6 +98,11 @@ test("browses from the directory into a category, a plugin, and its author", asy
|
||||
"href",
|
||||
"/plugins/category/git",
|
||||
);
|
||||
// The source is a folder in a monorepo; issues are filed on the repository.
|
||||
await expect(page.getByRole("link", { name: "Report a bug" })).toHaveAttribute(
|
||||
"href",
|
||||
"https://github.com/omercnet/paseo-plugins/issues/new",
|
||||
);
|
||||
|
||||
await page.getByRole("link", { name: "Omer Cohen" }).click();
|
||||
await expect(page).toHaveURL(/\/plugins\/omercnet$/);
|
||||
@@ -764,6 +769,7 @@ async function expectPluginOverviewSafe(page: Page) {
|
||||
});
|
||||
expect(violations).toEqual([]);
|
||||
await expect(page.getByRole("link", { name: "Source", exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "Report a bug" })).toHaveCount(0);
|
||||
await expect(page.getByRole("img", { name: /screenshot/ })).toHaveCount(0);
|
||||
await expect(page.getByRole("heading", { level: 1 })).toHaveText(securityPlugin.name);
|
||||
await expect(
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type Plugin,
|
||||
readmeBody,
|
||||
recentlyUpdated,
|
||||
reportBugUrl,
|
||||
searchPlugins,
|
||||
updatedAgo,
|
||||
} from "./registry";
|
||||
@@ -292,6 +293,20 @@ describe("plugin registry", () => {
|
||||
expect(installCommand(plugin)).toBe("paseo plugin add acme/example");
|
||||
expect(formatInstalls(1250)).toBe("1.3k");
|
||||
});
|
||||
it("reports bugs on the source repository's GitHub issue tracker", () => {
|
||||
const at = (url: string) => reportBugUrl({ ...plugin, repository: { url } });
|
||||
expect(at("https://github.com/acme/example")).toBe(
|
||||
"https://github.com/acme/example/issues/new",
|
||||
);
|
||||
expect(at("https://github.com/acme/plugins.git")).toBe(
|
||||
"https://github.com/acme/plugins/issues/new",
|
||||
);
|
||||
expect(at("https://github.com/acme/plugins/tree/HEAD/packages/example")).toBe(
|
||||
"https://github.com/acme/plugins/issues/new",
|
||||
);
|
||||
expect(at("https://gitlab.com/acme/example")).toBeUndefined();
|
||||
expect(at("javascript:alert(1)")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
/** In-memory stand-in for the website KV namespace, covering the calls the counter makes. */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
isGitHubHost,
|
||||
parseGitHubRemoteIdentity,
|
||||
parseGitRemoteLocation,
|
||||
} from "@getpaseo/protocol/git-remote";
|
||||
import { pluginOverviewUrl } from "@getpaseo/protocol/plugin-overview";
|
||||
import { pluginMediaKind, type PublishedPluginSchema } from "@getpaseo/protocol/plugin-registry";
|
||||
import { scoreTextFields } from "@getpaseo/protocol/search/text-match";
|
||||
@@ -53,6 +58,14 @@ export function npmUrl(plugin: Plugin): string | undefined {
|
||||
? `https://www.npmjs.com/package/${plugin.artifact.package}`
|
||||
: undefined;
|
||||
}
|
||||
/** A new issue on the plugin's GitHub repository; absent when the source is hosted elsewhere. */
|
||||
export function reportBugUrl(plugin: Plugin): string | undefined {
|
||||
const location = parseGitRemoteLocation(plugin.repository.url);
|
||||
if (!location || !isGitHubHost(location.host)) return undefined;
|
||||
// A monorepo source points at the plugin's folder; issues belong to the repository.
|
||||
const repository = parseGitHubRemoteIdentity(location.path.split("/").slice(0, 2).join("/"));
|
||||
return repository ? `https://github.com/${repository.repo}/issues/new` : undefined;
|
||||
}
|
||||
export function authorNpmUrl(author: Author): string | undefined {
|
||||
return author.npm ? `https://www.npmjs.com/~${author.npm}` : undefined;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createFileRoute, notFound } from "@tanstack/react-router";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { GitHubIcon } from "~/components/brand-icons";
|
||||
import { CodeBlock } from "~/components/code-block";
|
||||
import { PluginOverview, PluginContentLink } from "~/plugins/overview";
|
||||
import { SiteShell } from "~/components/site-shell";
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
npmUrl,
|
||||
pluginVersion,
|
||||
readmeBody,
|
||||
reportBugUrl,
|
||||
} from "~/plugins";
|
||||
import { AuthorAvatar, AuthorLink } from "~/plugins/author-link";
|
||||
import { InstallCount } from "~/plugins/install-count";
|
||||
@@ -48,6 +50,8 @@ export const Route = createFileRoute("/plugins/$owner_/$slug")({
|
||||
|
||||
const META_LINK_CLASS =
|
||||
"inline-flex items-center gap-1 text-xs text-extra-muted-foreground transition-colors hover:text-muted-foreground";
|
||||
const REPORT_BUG_CLASS =
|
||||
"inline-flex items-center gap-2 rounded-lg border border-white/12 px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-white/10 hover:text-foreground";
|
||||
const AUTHOR_LINK_CLASS =
|
||||
"inline-flex items-center gap-1.5 text-xs text-extra-muted-foreground transition-colors hover:text-muted-foreground";
|
||||
|
||||
@@ -56,6 +60,7 @@ function PluginPage() {
|
||||
const category = getCategory(plugin.categories[0]);
|
||||
const author = getAuthor(plugin);
|
||||
const npm = npmUrl(plugin);
|
||||
const reportBug = reportBugUrl(plugin);
|
||||
|
||||
return (
|
||||
<SiteShell width="default">
|
||||
@@ -64,8 +69,16 @@ function PluginPage() {
|
||||
</a>
|
||||
<h1 className="mt-4 text-3xl font-medium tracking-tight">{plugin.name}</h1>
|
||||
<p className="mt-3 text-lg leading-relaxed text-white/70">{plugin.description}</p>
|
||||
<div className="mt-6 w-fit max-w-full">
|
||||
<CodeBlock size="sm">{installCommand(plugin)}</CodeBlock>
|
||||
<div className="mt-6 flex flex-wrap items-center gap-3">
|
||||
<div className="w-fit max-w-full">
|
||||
<CodeBlock size="sm">{installCommand(plugin)}</CodeBlock>
|
||||
</div>
|
||||
{reportBug && (
|
||||
<PluginContentLink href={reportBug} className={REPORT_BUG_CLASS}>
|
||||
<GitHubIcon className="h-4 w-4" />
|
||||
Report a bug
|
||||
</PluginContentLink>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-x-2 gap-y-2 text-xs text-extra-muted-foreground [&>*+*]:before:mr-2 [&>*+*]:before:content-['·'_/_'']">
|
||||
<AuthorLink author={author} className={AUTHOR_LINK_CLASS}>
|
||||
|
||||
Reference in New Issue
Block a user