Files
kandev/apps/web/lib/kanban/map-task.ts
T

83 lines
3.1 KiB
TypeScript

import {
isPRReviewFromMetadata,
isIssueWatchFromMetadata,
issueFieldsFromMetadata,
} from "@/lib/metadata-utils";
import type { KanbanState } from "@/lib/state/slices/kanban/types";
import type { TaskState, TaskSessionState } from "@/lib/types/http";
type KanbanTask = KanbanState["tasks"][number];
/**
* Shape accepted by {@link toKanbanTask}. Satisfied by both the HTTP Task DTO
* (which nests repositories) and the backend's `task.updated` WebSocket
* payload (which flattens `repository_id` and uses `task_id`).
*
* The single publisher / single mapper design relies on this shape being the
* contract; anyone changing what the backend emits needs to keep it in sync,
* and the parity test in `map-task.test.ts` will catch drift.
*/
export type TaskLike = {
id?: string;
task_id?: string;
workflow_step_id?: string;
title?: string;
description?: string | null;
position?: number;
state?: TaskState;
repositories?: Array<{ repository_id: string }>;
repository_id?: string;
primary_session_id?: string | null;
primary_session_state?: TaskSessionState | string | null;
session_count?: number | null;
review_status?: "pending" | "approved" | "changes_requested" | "rejected" | null;
primary_executor_id?: string | null;
primary_executor_type?: string | null;
primary_executor_name?: string | null;
is_remote_executor?: boolean;
parent_id?: string | null;
updated_at?: string;
created_at?: string;
metadata?: Record<string, unknown> | null;
};
function pickRepositoryId(source: TaskLike): string | undefined {
return source.repository_id ?? source.repositories?.[0]?.repository_id ?? undefined;
}
function pickId(source: TaskLike): string {
return (source.id ?? source.task_id ?? "") as string;
}
/**
* Build a canonical {@link KanbanTask} from either an HTTP DTO or a WebSocket
* payload. Both paths share this helper so a single publisher change can never
* leave them out of sync again (cf. sidebar filter regressions where the HTTP
* snapshot derived `isPRReview` but the WS handler didn't).
*/
export function toKanbanTask(source: TaskLike): KanbanTask {
return {
id: pickId(source),
workflowStepId: source.workflow_step_id ?? "",
title: source.title ?? "",
description: source.description ?? undefined,
position: source.position ?? 0,
state: source.state,
repositoryId: pickRepositoryId(source),
primarySessionId: source.primary_session_id ?? undefined,
primarySessionState: source.primary_session_state ?? undefined,
sessionCount: source.session_count ?? undefined,
reviewStatus: source.review_status ?? undefined,
primaryExecutorId: source.primary_executor_id ?? undefined,
primaryExecutorType: source.primary_executor_type ?? undefined,
primaryExecutorName: source.primary_executor_name ?? undefined,
isRemoteExecutor: source.is_remote_executor ?? false,
parentTaskId: source.parent_id ?? undefined,
updatedAt: source.updated_at,
createdAt: source.created_at,
isPRReview: isPRReviewFromMetadata(source.metadata),
isIssueWatch: isIssueWatchFromMetadata(source.metadata),
...issueFieldsFromMetadata(source.metadata),
} as KanbanTask;
}