import { IssueReadModel, ProjectWorkflow, ReleaseReadModel, StatusCategory, } from '@task-stream/api/tracker/domain'; /** How the default types are told in release notes; others use their name. */ const HEADING: Record = { FEATURE: 'Novedades', UI_CHANGE: 'Cambios en la interfaz', BUG: 'Correcciones', TASK: 'Mejoras internas', }; /** Verification work is not news for whoever reads the notes. */ const LEFT_OUT = new Set(['QA']); /** * A first draft of a release's notes, in Markdown: its finished tickets * grouped by type (in the project's order of types), newest version header * first. Someone edits it before publishing it; it is never saved by itself. */ export function draftReleaseNotes(input: { release: ReleaseReadModel; issues: IssueReadModel[]; workflow: ProjectWorkflow; }): string { const { release, issues, workflow } = input; const done = issues.filter( (i) => i.statusCategory === StatusCategory.DONE && !LEFT_OUT.has(i.type), ); const day = release.releasedAt ?? release.targetDate; const date = day ? ` — ${new Date(day).toLocaleDateString('es', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC', })}` : ''; const lines = [`## ${release.name}${date}`]; if (release.description) lines.push('', release.description); const order = workflow.settings.issueTypes.map((t) => t.id); const types = [...new Set(done.map((i) => i.type))].sort( (a, b) => (order.indexOf(a) + 1 || order.length + 1) - (order.indexOf(b) + 1 || order.length + 1), ); for (const type of types) { lines.push('', `### ${HEADING[type] ?? workflow.typeName(type)}`, ''); for (const issue of done .filter((i) => i.type === type) .sort((a, b) => a.number - b.number)) lines.push(`- ${issue.title} (${issue.key})`); } if (!done.length) lines.push('', '_Aún no hay tickets terminados en esta release._'); return lines.join('\n') + '\n'; }