Show external object display key in issue properties

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-06-23 00:35:05 +00:00
1 parent 95acb60120
commit 4394fcd00d
6 files changed
+95 -20

No files matched your search

@@ -182,7 +182,7 @@ describe("GitHub external object provider", () => {
providerKey: "github",
objectType: "pull_request",
externalId: "acme/app#pull/42",
displayKey: "GitHub PR",
displayKey: "Github Pull Request",
iconKey: "github",
displayTitle: "Acme/App#42",
}),
@@ -190,7 +190,7 @@ describe("GitHub external object provider", () => {
providerKey: "github",
objectType: "issue",
externalId: "acme/app#issues/7",
displayKey: "GitHub Issue",
displayKey: "Github Issue",
iconKey: "github",
displayTitle: "Acme/App#7",
}),
@@ -239,7 +239,7 @@ describe("GitHub external object provider", () => {
ok: true,
snapshot: expect.objectContaining({
...expected,
displayKey: "GitHub PR",
displayKey: "Github Pull Request",
iconKey: "github",
displayTitle: expect.stringContaining(String(body.title)),
remoteVersion: "2026-04-24T01:02:03Z",
@@ -281,7 +281,7 @@ describe("GitHub external object provider", () => {
ok: true,
snapshot: expect.objectContaining({
...expected,
displayKey: "GitHub Issue",
displayKey: "Github Issue",
iconKey: "github",
data: expect.objectContaining({
provider: "github",
@@ -330,7 +330,7 @@ describe("GitHub external object provider", () => {
[
"not-found",
new Response("", { status: 404, headers: { etag: '"missing"' } }),
{ ok: true, snapshot: expect.objectContaining({ displayKey: "GitHub PR", iconKey: "github", statusKey: "not_found", statusIconKey: "archive", statusCategory: "archived", statusTone: "muted" }) },
{ ok: true, snapshot: expect.objectContaining({ displayKey: "Github Pull Request", iconKey: "github", statusKey: "not_found", statusIconKey: "archive", statusCategory: "archived", statusTone: "muted" }) },
],
])("maps %s responses to provider-safe results", async (_name, githubResponse, expected) => {
const provider = createGitHubExternalObjectProvider({} as any, {
@@ -106,7 +106,7 @@ function displayTitleFor(identity: GitHubObjectIdentity) {
}
function displayKeyFor(identity: Pick<GitHubObjectIdentity, "objectType">) {
return identity.objectType === "pull_request" ? "GitHub PR" : "GitHub Issue";
return identity.objectType === "pull_request" ? "Github Pull Request" : "Github Issue";
}
function retryAfterSeconds(response: Response) {
@@ -97,7 +97,7 @@ describe("ExternalObjectPill", () => {
object={{
providerKey: "github",
objectType: "pull_request",
displayKey: "GitHub PR",
displayKey: "Github Pull Request",
iconKey: "github",
statusCategory: "succeeded",
statusIconKey: "git-merge",
@@ -108,7 +108,7 @@ describe("ExternalObjectPill", () => {
}}
/>,
);
expect(html).toContain("GitHub PR");
expect(html).toContain("Github Pull Request");
expect(html).toContain("Merged");
expect(html).toContain("acme/web#241: Add rich object presentation metadata");
expect(html).toContain('aria-label="GitHub pull request — Merged: acme/web#241: Add rich object presentation metadata"');
@@ -1797,4 +1797,47 @@ describe("IssueProperties", () => {
act(() => root.unmount());
});
it("uses external object display metadata for the properties row label", async () => {
const root = renderProperties(container, {
issue: createIssue(),
childIssues: [],
onUpdate: vi.fn(),
inline: true,
externalObjects: [
{
mentionCount: 1,
sourceLabels: ["Description"],
pill: {
providerKey: "github",
objectType: "pull_request",
displayKey: "Github Pull Request",
iconKey: "github",
statusCategory: "succeeded",
statusIconKey: "git-merge",
statusLabel: "Merged",
liveness: "fresh",
displayTitle: "acme/web#241: Add rich object presentation metadata",
url: "https://github.com/acme/web/pull/241",
},
group: {
object: null,
mentions: [],
mentionCount: 1,
sourceLabels: ["Description"],
},
},
],
});
await flush();
expect(container.textContent).toContain("Github Pull Request");
expect(container.textContent).toContain("Merged");
expect(container.textContent).not.toContain("External objects");
const label = Array.from(container.querySelectorAll("span"))
.find((span) => span.textContent === "Github Pull Request");
expect(label?.querySelector("svg")).toBeTruthy();
act(() => root.unmount());
});
});
+42 -10
View File
@@ -37,7 +37,7 @@ import { IssueReferencePill } from "./IssueReferencePill";
import { formatDate, formatDateTime, cn, projectUrl } from "../lib/utils";
import { ExternalObjectPill } from "./ExternalObjectPill";
import type { IssueExternalObjectGroup } from "../hooks/useIssueExternalObjects";
import { externalObjectToneSeverity } from "../lib/external-objects";
import { externalObjectIconForKey, externalObjectToneSeverity } from "../lib/external-objects";
import { timeAgo } from "../lib/timeAgo";
import { Button } from "@/components/ui/button";
import { ToggleSwitch } from "@/components/ui/toggle-switch";
@@ -165,15 +165,52 @@ interface IssuePropertiesProps {
const ISSUE_BLOCKER_SEARCH_LIMIT = 50;
const ISSUE_PROPERTY_RELATION_PREVIEW_COUNT = 5;
function PropertyRow({ label, children }: { label: string; children: React.ReactNode }) {
function PropertyRow({
label,
children,
labelClassName,
}: {
label: React.ReactNode;
children: React.ReactNode;
labelClassName?: string;
}) {
return (
<div className="flex items-start gap-3 py-1.5">
<span className="text-xs text-muted-foreground shrink-0 w-20 mt-0.5">{label}</span>
<span className={cn("text-xs text-muted-foreground shrink-0 w-20 mt-0.5", labelClassName)}>{label}</span>
<div className="flex items-center gap-1.5 min-w-0 flex-1 flex-wrap">{children}</div>
</div>
);
}
function sortExternalObjectGroups(groups: IssueExternalObjectGroup[]) {
return [...groups].sort((a, b) => {
const aTone = externalObjectToneSeverity(a.group.object?.statusTone);
const bTone = externalObjectToneSeverity(b.group.object?.statusTone);
return bTone - aTone;
});
}
function externalObjectRowLabel(groups: IssueExternalObjectGroup[] | undefined): React.ReactNode {
if (!groups || groups.length === 0) return "External objects";
const sorted = sortExternalObjectGroups(groups);
const primary = sorted[0]?.pill;
const displayKey = primary?.displayKey?.trim();
if (!primary || !displayKey) return "External objects";
const iconKey = primary.iconKey ?? null;
const allSameDisplay = sorted.every(({ pill }) =>
(pill.displayKey?.trim() ?? "") === displayKey && (pill.iconKey ?? null) === iconKey,
);
if (!allSameDisplay) return "External objects";
const Icon = externalObjectIconForKey(iconKey);
return (
<span className="inline-flex min-w-0 items-center gap-1">
{Icon ? <Icon aria-hidden="true" className="h-3 w-3 shrink-0" /> : null}
<span className="truncate">{displayKey}</span>
</span>
);
}
const ISSUE_THINKING_EFFORT_OPTIONS = {
claude_local: [
{ value: "", label: "Default" },
@@ -2202,14 +2239,9 @@ export function IssueProperties({
<span className="h-4 w-24 animate-pulse rounded bg-muted/40" />
</PropertyRow>
) : externalObjects && externalObjects.length > 0 ? (
<PropertyRow label="External objects">
<PropertyRow label={externalObjectRowLabel(externalObjects)} labelClassName="w-32">
<div className="flex flex-wrap items-center gap-1.5">
{[...externalObjects]
.sort((a, b) => {
const aTone = externalObjectToneSeverity(a.group.object?.statusTone);
const bTone = externalObjectToneSeverity(b.group.object?.statusTone);
return bTone - aTone;
})
{sortExternalObjectGroups(externalObjects)
.map(({ pill, mentionCount, sourceLabels, group }) => (
<ExternalObjectPill
key={group.object?.id ?? `${pill.providerKey}:${pill.objectType}:${pill.url ?? "anon"}`}
@@ -37,7 +37,7 @@ describe("MarkdownBody external object decoration", () => {
"https://github.com/acme/web/pull/241": {
providerKey: "github",
objectType: "pull_request",
displayKey: "GitHub PR",
displayKey: "Github Pull Request",
iconKey: "github",
statusCategory: "succeeded",
statusIconKey: "git-merge",
@@ -53,7 +53,7 @@ describe("MarkdownBody external object decoration", () => {
expect(html).toContain('data-external-link="resolved"');
expect(html).toContain('data-external-status="succeeded"');
expect(html).toContain('data-external-liveness="fresh"');
expect(html).toContain('aria-label="GitHub PR Merged: Add external refs"');
expect(html).toContain('aria-label="Github Pull Request Merged: Add external refs"');
expect(html).toContain("github.com/acme/web/pull/241");
});