feat(FN-4144): add GitHub tracking indicator to task cards
Adds a GitHub link indicator to task cards with styling and hover behavior, accompanied by tests to cover the new icon display logic. Fusion-Task-Id: FN-4144
This commit is contained in:
@@ -615,6 +615,30 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-github-tracking-link {
|
||||
min-width: calc(var(--space-xl) + var(--space-md));
|
||||
min-height: calc(var(--space-xl) + var(--space-md));
|
||||
padding: var(--space-xs);
|
||||
border-radius: var(--radius-md);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background-color var(--transition-fast),
|
||||
color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.card-github-tracking-link:hover {
|
||||
background: var(--card-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.card-github-tracking-link:focus-visible {
|
||||
background: var(--card-hover);
|
||||
color: var(--text);
|
||||
box-shadow: var(--focus-ring-strong);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.card-time-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1139,6 +1163,11 @@
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
|
||||
.card-github-tracking-link {
|
||||
min-width: calc(var(--space-xl) + var(--space-xl));
|
||||
min-height: calc(var(--space-xl) + var(--space-xl));
|
||||
}
|
||||
|
||||
/* Card: smaller status badges for 280px width */
|
||||
.card-status-badge {
|
||||
font-size: 0.5625rem;
|
||||
|
||||
@@ -736,8 +736,13 @@ function TaskCardComponent({
|
||||
|
||||
// Check if this card can be edited inline
|
||||
const canEdit = EDITABLE_COLUMNS.has(task.column) && !isAgentActive && !isPaused && !queued && onUpdateTask;
|
||||
const githubTrackedIssue = task.githubTracking?.issue;
|
||||
const hasGithubTrackingLink = Boolean(githubTrackedIssue);
|
||||
const hasGitHubBadge = Boolean(task.prInfo || task.issueInfo);
|
||||
const isGitHubImportedTask = task.sourceType === "github_import";
|
||||
const showTrackingIndicator = hasGithubTrackingLink
|
||||
&& !isGitHubImportedTask
|
||||
&& !(task.issueInfo && task.issueInfo.number === githubTrackedIssue?.number);
|
||||
const branchMetadata = useMemo(() => getVisibleTaskCardBranches(task), [task.id, task.branch, task.baseBranch]);
|
||||
const hasBranchMetadata = Boolean(branchMetadata.branch || branchMetadata.baseBranch);
|
||||
const sourceIssueUrl = getIssueUrlFromMetadata(task.sourceMetadata);
|
||||
@@ -1602,7 +1607,7 @@ function TaskCardComponent({
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{(filesChangedButton || timeIndicator || isGitHubImportedTask) && (
|
||||
{(filesChangedButton || timeIndicator || isGitHubImportedTask || showTrackingIndicator) && (
|
||||
<div className="card-footer-row">
|
||||
{filesChangedButton}
|
||||
{isGitHubImportedTask && (
|
||||
@@ -1614,6 +1619,19 @@ function TaskCardComponent({
|
||||
<ProviderIcon provider="github" size="sm" />
|
||||
</span>
|
||||
)}
|
||||
{showTrackingIndicator && githubTrackedIssue && (
|
||||
<a
|
||||
className="card-source-provenance card-github-tracking-link"
|
||||
href={githubTrackedIssue.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={`Linked GitHub issue: ${githubTrackedIssue.owner}/${githubTrackedIssue.repo}#${githubTrackedIssue.number}`}
|
||||
aria-label={`Linked GitHub issue #${githubTrackedIssue.number}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ProviderIcon provider="github" size="sm" />
|
||||
</a>
|
||||
)}
|
||||
{timeIndicator && (
|
||||
<span
|
||||
className="card-time-indicator"
|
||||
|
||||
@@ -1019,6 +1019,131 @@ describe("TaskCard", () => {
|
||||
expect(screen.queryByTestId("provider-icon-github")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders a GitHub tracking link for tracked issues on non-imported tasks", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "todo",
|
||||
sourceType: "dashboard_ui",
|
||||
githubTracking: {
|
||||
issue: {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
number: 42,
|
||||
url: "https://github.com/owner/repo/issues/42",
|
||||
createdAt: "2026-05-12T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
})}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Linked GitHub issue #42" });
|
||||
expect(link.getAttribute("href")).toBe("https://github.com/owner/repo/issues/42");
|
||||
expect(link.getAttribute("title")).toBe("Linked GitHub issue: owner/repo#42");
|
||||
expect(link).toHaveClass("card-github-tracking-link");
|
||||
expect(screen.getByTestId("provider-icon-github")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render a GitHub tracking link when githubTracking is absent", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "todo",
|
||||
sourceType: "dashboard_ui",
|
||||
})}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("link", { name: /Linked GitHub issue/i })).toBeNull();
|
||||
});
|
||||
|
||||
it("does not render a GitHub tracking link for github_import tasks", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "todo",
|
||||
sourceType: "github_import",
|
||||
sourceMetadata: { issueUrl: "https://github.com/owner/repo/issues/42" },
|
||||
githubTracking: {
|
||||
issue: {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
number: 42,
|
||||
url: "https://github.com/owner/repo/issues/42",
|
||||
createdAt: "2026-05-12T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
})}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("link", { name: /Linked GitHub issue/i })).toBeNull();
|
||||
expect(screen.getByLabelText("Imported from GitHub")).toBeDefined();
|
||||
});
|
||||
|
||||
it("does not render a GitHub tracking link when a matching issue badge is already shown", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "todo",
|
||||
sourceType: "dashboard_ui",
|
||||
githubTracking: {
|
||||
issue: {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
number: 42,
|
||||
url: "https://github.com/owner/repo/issues/42",
|
||||
createdAt: "2026-05-12T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
issueInfo: {
|
||||
url: "https://github.com/owner/repo/issues/42",
|
||||
number: 42,
|
||||
state: "open",
|
||||
title: "Issue",
|
||||
},
|
||||
})}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("link", { name: /Linked GitHub issue/i })).toBeNull();
|
||||
});
|
||||
|
||||
it("clicking the GitHub tracking link does not open the task detail modal", () => {
|
||||
const onOpenDetail = vi.fn();
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
column: "todo",
|
||||
sourceType: "dashboard_ui",
|
||||
githubTracking: {
|
||||
issue: {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
number: 42,
|
||||
url: "https://github.com/owner/repo/issues/42",
|
||||
createdAt: "2026-05-12T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
})}
|
||||
onOpenDetail={onOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("link", { name: "Linked GitHub issue #42" }));
|
||||
expect(onOpenDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders agent-created provenance badge for automation tasks and prefers sourceMetadata.agentName", () => {
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
|
||||
Reference in New Issue
Block a user