diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx index 92f76d8304f..1dd29d1af58 100644 --- a/ui/src/components/IssueProperties.test.tsx +++ b/ui/src/components/IssueProperties.test.tsx @@ -416,19 +416,122 @@ describe("IssueProperties", () => { act(() => root.unmount()); }); - it("shows referenced task ids when the issue points at other tasks", async () => { + it("shows related task references below sub-issues", async () => { const root = renderProperties(container, { issue: createIssue({ - referencedIssueIdentifiers: ["PAP-22", "PAP-44"], + relatedWork: { + outbound: [ + { + issue: { + id: "issue-22", + identifier: "PAP-22", + title: "Related task", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + mentionCount: 1, + sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-22" }], + }, + ], + inbound: [], + }, }), childIssues: [], onUpdate: vi.fn(), }); await flush(); - expect(container.textContent).toContain("Task ids"); + expect(container.textContent).not.toContain("Task ids"); + expect(container.textContent).toContain("Related Tasks"); expect(container.textContent).toContain("PAP-22"); - expect(container.textContent).toContain("PAP-44"); + + act(() => root.unmount()); + }); + + it("hides related task references already covered by blockers, blocking, and sub-issues", async () => { + const root = renderProperties(container, { + issue: createIssue({ + blockedBy: [ + { + id: "issue-22", + identifier: "PAP-22", + title: "Blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + blocks: [ + { + id: "issue-33", + identifier: "PAP-33", + title: "Blocked issue", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + ], + relatedWork: { + outbound: [ + { + issue: { + id: "issue-22", + identifier: "PAP-22", + title: "Blocker", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + mentionCount: 1, + sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-22" }], + }, + { + issue: { + id: "issue-33", + identifier: "PAP-33", + title: "Blocked issue", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + mentionCount: 1, + sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-33" }], + }, + { + issue: { + id: "child-44", + identifier: "PAP-44", + title: "Child issue", + status: "todo", + priority: "medium", + assigneeAgentId: null, + assigneeUserId: null, + }, + mentionCount: 1, + sources: [{ kind: "description", sourceRecordId: null, label: "description", matchedText: "PAP-44" }], + }, + ], + inbound: [], + }, + }), + childIssues: [ + createIssue({ + id: "child-44", + identifier: "PAP-44", + title: "Child issue", + }), + ], + onUpdate: vi.fn(), + }); + await flush(); + + expect(container.textContent).not.toContain("Related Tasks"); act(() => root.unmount()); }); diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx index e43f39d68ab..dcb3abd3800 100644 --- a/ui/src/components/IssueProperties.tsx +++ b/ui/src/components/IssueProperties.tsx @@ -297,6 +297,29 @@ export function IssueProperties({ return runningRuntimeServiceWithUrl(issue.currentExecutionWorkspace?.runtimeServices); }, [issue, issueProject]); const referencedIssueIdentifiers = issue.referencedIssueIdentifiers ?? []; + const relatedTasks = useMemo(() => { + const excluded = new Set(); + const addExcluded = (candidate: { id: string; identifier?: string | null }) => { + excluded.add(candidate.id); + if (candidate.identifier) excluded.add(candidate.identifier); + }; + + for (const blocker of issue.blockedBy ?? []) addExcluded(blocker); + for (const blocked of issue.blocks ?? []) addExcluded(blocked); + for (const child of childIssues) addExcluded(child); + + const referencedIssues = issue.relatedWork?.outbound.map((item) => item.issue) ?? []; + if (referencedIssues.length > 0) { + return referencedIssues.filter((referenced) => { + const label = referenced.identifier ?? referenced.id; + return !excluded.has(referenced.id) && !excluded.has(label); + }); + } + + return referencedIssueIdentifiers + .filter((identifier) => !excluded.has(identifier)) + .map((identifier) => ({ id: identifier, identifier, title: identifier })); + }, [childIssues, issue.blockedBy, issue.blocks, issue.relatedWork?.outbound, referencedIssueIdentifiers]); const projectLink = (id: string | null) => { if (!id) return null; const project = projects?.find((p) => p.id === id) ?? null; @@ -1095,21 +1118,6 @@ export function IssueProperties({ ) : null} - - {referencedIssueIdentifiers.length > 0 ? ( -
- {referencedIssueIdentifiers.map((identifier) => ( - - ))} -
- ) : ( - None - )} -
-
{childIssues.length > 0 @@ -1130,12 +1138,22 @@ export function IssueProperties({ onClick={onAddSubIssue} > - Add sub-issue + Add sub-issue ) : null}
+ {relatedTasks.length > 0 ? ( + +
+ {relatedTasks.map((related) => ( + + ))} +
+
+ ) : null} + - {issue.status ? ( - - ) : ( - - )} + {issue.status ? : null} {issue.identifier ?? issue.title} ); diff --git a/ui/src/components/MarkdownBody.test.tsx b/ui/src/components/MarkdownBody.test.tsx index a9845cfb5e7..204be8024ed 100644 --- a/ui/src/components/MarkdownBody.test.tsx +++ b/ui/src/components/MarkdownBody.test.tsx @@ -156,6 +156,7 @@ describe("MarkdownBody", () => { expect(html).toContain('href="/issues/PAP-1271"'); expect(html).toContain("text-green-600"); expect(html).toContain(">PAP-1271<"); + expect(html).not.toContain("paperclip-mention-chip--issue"); }); it("rewrites full issue URLs to internal issue links", () => { @@ -166,6 +167,7 @@ describe("MarkdownBody", () => { expect(html).toContain('href="/issues/PAP-1179"'); expect(html).toContain("text-red-600"); expect(html).toContain(">http://localhost:3100/PAP/issues/PAP-1179<"); + expect(html).not.toContain("paperclip-mention-chip--issue"); }); it("rewrites issue scheme links to internal issue links", () => { diff --git a/ui/src/components/MarkdownBody.tsx b/ui/src/components/MarkdownBody.tsx index 2aa5e6b6697..1c269c02fc2 100644 --- a/ui/src/components/MarkdownBody.tsx +++ b/ui/src/components/MarkdownBody.tsx @@ -44,7 +44,7 @@ function MarkdownIssueLink({ return ( {data ? : null} diff --git a/ui/src/index.css b/ui/src/index.css index 2a0793a40fc..95643466a50 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -482,15 +482,6 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { mask-size: contain; } -.paperclip-mdxeditor-content a.paperclip-mention-chip[data-mention-kind="issue"]::before, -a.paperclip-mention-chip[data-mention-kind="issue"]::before { - width: 0.45rem; - height: 0.45rem; - border-radius: 0.14rem; - background-color: currentColor; - opacity: 0.75; -} - .paperclip-mdxeditor-content ul, .paperclip-mdxeditor-content ol { margin: 1.1em 0;