feat(FN-4538): complete Step 5 — show overlap blockers in dashboard
Fusion-Task-Id: FN-4538 Fusion-Task-Lineage: 332b4c78-b67f-45d0-9181-0bb539b662c1
This commit is contained in:
@@ -452,6 +452,7 @@ function areTaskCardPropsEqual(previous: TaskCardProps, next: TaskCardProps): bo
|
||||
previousTask.error === nextTask.error &&
|
||||
previousTask.size === nextTask.size &&
|
||||
previousTask.blockedBy === nextTask.blockedBy &&
|
||||
previousTask.overlapBlockedBy === nextTask.overlapBlockedBy &&
|
||||
previousTask.worktree === nextTask.worktree &&
|
||||
previousTask.branch === nextTask.branch &&
|
||||
previousTask.baseBranch === nextTask.baseBranch &&
|
||||
@@ -1760,7 +1761,7 @@ function TaskCardComponent({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{(((task.retrySummary?.total ?? 0) > 0) || (task.dependencies && task.dependencies.length > 0) || queued || task.status === "queued" || task.blockedBy || (fanout && fanout.totalCount > 0)) && (
|
||||
{(((task.retrySummary?.total ?? 0) > 0) || (task.dependencies && task.dependencies.length > 0) || queued || task.status === "queued" || task.blockedBy || task.overlapBlockedBy || (fanout && fanout.totalCount > 0)) && (
|
||||
<div className="card-meta">
|
||||
{task.dependencies && task.dependencies.length > 0 && (
|
||||
<div className="card-dep-list">
|
||||
@@ -1776,9 +1777,9 @@ function TaskCardComponent({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{task.blockedBy && (
|
||||
<span className="card-scope-badge" data-tooltip={`Blocked by ${task.blockedBy} (file overlap)`}>
|
||||
<Layers size={12} style={{ verticalAlign: "middle" }} /> {task.blockedBy}
|
||||
{(task.overlapBlockedBy || task.blockedBy) && (
|
||||
<span className="card-scope-badge" data-tooltip={`Blocked by ${task.overlapBlockedBy || task.blockedBy} (file overlap)`}>
|
||||
<Layers size={12} style={{ verticalAlign: "middle" }} /> {task.overlapBlockedBy || task.blockedBy}
|
||||
</span>
|
||||
)}
|
||||
{fanout && fanout.totalCount > 0 && (
|
||||
|
||||
@@ -1899,6 +1899,12 @@ export function TaskDetailContent({
|
||||
const overlapBlockingSummary = blockingEntry
|
||||
? `${task.id} is blocking ${blockingEntry.overlapBlockedTodoCount} todo task(s) via blockedBy overlap`
|
||||
: null;
|
||||
const overlapBlockerTask = workingTask.overlapBlockedBy
|
||||
? tasks.find((candidate) => candidate.id === workingTask.overlapBlockedBy)
|
||||
: undefined;
|
||||
const overlapBlockerActive = Boolean(
|
||||
overlapBlockerTask && (overlapBlockerTask.column === "in-progress" || overlapBlockerTask.column === "in-review"),
|
||||
);
|
||||
|
||||
const assignedAgentLabel = assignedAgent?.name ?? task.assignedAgentId ?? null;
|
||||
const detailProviders = useMemo(() => {
|
||||
@@ -3047,6 +3053,12 @@ export function TaskDetailContent({
|
||||
) : (
|
||||
<div className="detail-empty-inline">(no dependencies)</div>
|
||||
)}
|
||||
{workingTask.overlapBlockedBy && (
|
||||
<div className="detail-empty-inline">
|
||||
File scope overlap blocker: {workingTask.overlapBlockedBy}
|
||||
{!overlapBlockerActive && " (stale)"}
|
||||
</div>
|
||||
)}
|
||||
<div className="dep-trigger-wrap">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -661,6 +661,30 @@ describe("TaskCard", () => {
|
||||
expect(container.querySelector(".card-fanout-badge")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders overlap scope badge when overlapBlockedBy is set without blockedBy", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({ column: "todo", blockedBy: undefined, overlapBlockedBy: "FN-OVER" })}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("FN-OVER")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render overlap scope badge when blockedBy and overlapBlockedBy are absent", () => {
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
task={makeTask({ column: "todo", blockedBy: undefined, overlapBlockedBy: undefined })}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.querySelector(".card-scope-badge")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders fan-out badge with downstream count and tooltip", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
|
||||
@@ -312,6 +312,27 @@ describe("TaskDetailModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows active file scope overlap blocker in Dependencies section", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "FN-T", column: "todo", overlapBlockedBy: "FN-OVER" })}
|
||||
tasks={[
|
||||
makeTask({ id: "FN-T", column: "todo", overlapBlockedBy: "FN-OVER" }),
|
||||
makeTask({ id: "FN-OVER", column: "in-progress" }),
|
||||
]}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("File scope overlap blocker: FN-OVER")).toBeInTheDocument();
|
||||
expect(screen.queryByText("File scope overlap blocker: FN-OVER (stale)")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows overlap blockedBy summary in Blocking section", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
|
||||
Reference in New Issue
Block a user