beadboard/src/lib/kanban.ts

99 lines
2.5 KiB
TypeScript

import type { BeadIssue } from './types';
export const KANBAN_STATUSES = ['open', 'in_progress', 'blocked', 'deferred', 'closed'] as const;
export type KanbanStatus = (typeof KANBAN_STATUSES)[number];
export type KanbanColumns = Record<KanbanStatus, BeadIssue[]>;
export interface KanbanFilterOptions {
query?: string;
type?: string;
priority?: string;
showClosed?: boolean;
}
export interface KanbanStats {
total: number;
open: number;
active: number;
blocked: number;
done: number;
p0: number;
}
function isKanbanStatus(status: string): status is KanbanStatus {
return KANBAN_STATUSES.includes(status as KanbanStatus);
}
function issueSort(a: BeadIssue, b: BeadIssue): number {
const priorityDiff = a.priority - b.priority;
if (priorityDiff !== 0) {
return priorityDiff;
}
return b.updated_at.localeCompare(a.updated_at);
}
export function filterKanbanIssues(issues: BeadIssue[], filters: KanbanFilterOptions): BeadIssue[] {
const query = (filters.query ?? '').trim().toLowerCase();
const type = (filters.type ?? '').trim().toLowerCase();
const priority = (filters.priority ?? '').trim();
const showClosed = filters.showClosed ?? false;
return issues.filter((issue) => {
if (!showClosed && issue.status === 'closed') {
return false;
}
if (query) {
const haystack = [issue.id, issue.title, issue.description ?? '', issue.assignee ?? '', issue.labels.join(' ')].join(' ').toLowerCase();
if (!haystack.includes(query)) {
return false;
}
}
if (type && issue.issue_type.toLowerCase() !== type) {
return false;
}
if (priority && String(issue.priority) !== priority) {
return false;
}
return true;
});
}
export function buildKanbanColumns(issues: BeadIssue[]): KanbanColumns {
const columns = {
open: [],
in_progress: [],
blocked: [],
deferred: [],
closed: [],
} as KanbanColumns;
for (const issue of issues) {
if (isKanbanStatus(issue.status)) {
columns[issue.status].push(issue);
}
}
for (const status of KANBAN_STATUSES) {
columns[status].sort(issueSort);
}
return columns;
}
export function buildKanbanStats(issues: BeadIssue[]): KanbanStats {
return {
total: issues.length,
open: issues.filter((x) => x.status === 'open').length,
active: issues.filter((x) => x.status === 'in_progress').length,
blocked: issues.filter((x) => x.status === 'blocked').length,
done: issues.filter((x) => x.status === 'closed').length,
p0: issues.filter((x) => x.priority === 0).length,
};
}