import type { BoardIssue, BoardSnapshot } from "../../../../api/types.ts"; import { EPICS, KEY_RE, NO_EPIC, NOT_IN_HUB } from "../constants.ts"; import type { BoardModel, BoardNode, Dicts, Link } from "../types.ts"; import { addLink, addStubs } from "./links.ts"; import { sortableDate } from "./util.ts"; // снимок хаба (GET /board) → модель доски. Эпики («Фича»/Epic) — имена дорожек, не карточки; соседи по связям — обычные карточки; // вид работы — первая метка kind-*, иначе тип задачи; срок — плановая дата окончания, иначе срок исполнения; связи — только известных // типов (dicts.linkTypes), между ключами вида AURORA-1234 (ключ идёт в разметку карточки) и не с эпиками; ключи из связей, которых нет в зеркале, — заглушки на дорожке «Нет в зеркале» export function fromHub(snap: BoardSnapshot, dicts: Dicts): BoardModel { const isEpic = (it: BoardIssue) => /^(Фича|Epic|Эпик)$/i.test(it.issue_type || ""); const epicNames: Record = { ...EPICS }, epics = new Set(); snap.issues.forEach((it) => { if (isEpic(it)) { epics.add(it.key); epicNames[it.key] = it.summary || it.key; } }); const nodes: BoardNode[] = [], links: Link[] = [], seen = new Set(), sprints: Record = {}; snap.issues.forEach((it) => { if (isEpic(it) || !KEY_RE.test(it.key)) return; const label = (it.labels || []).find((l) => dicts.kindLabels[l]), sprint = it.sprint || ""; if (sprint) sprints[sprint] = (sprints[sprint] || 0) + 1; nodes.push({ key: it.key, title: it.summary || "", kind: label ? dicts.kindLabels[label] : (it.issue_type || ""), prio: it.priority || "", due: sortableDate(it.planned_end || it.duedate || ""), status: it.status || "", epic: it.epic_key ? (epicNames[it.epic_key] || it.epic_key) : NO_EPIC, sprint, who: it.assignee || "", stub: false }); }); snap.links.forEach((l) => { const type = dicts.linkTypes[l.link_type]; if (type && KEY_RE.test(l.source) && KEY_RE.test(l.target) && !epics.has(l.source) && !epics.has(l.target)) addLink(links, seen, l.source, l.target, type); }); const missing = addStubs(nodes, links, NOT_IN_HUB); const problems = snap.last_sync ? [] : ["в хабе ещё не было полного обновления из Jira — нажмите «Обновить из Jira»"]; // фильтр спринтов — только по своим задачам: спринт соседа из другого проекта не делает канбан-проект «спринтовым» const hasSprint = snap.issues.some((it) => !it.neighbor && !!it.sprint); return { nodes, links, sprints, fileName: "Хаб", problems, hasSprint, missing, stubLane: NOT_IN_HUB }; }