From 9415c213092b1f52aac44b17b74c440497b99727 Mon Sep 17 00:00:00 2001 From: Abdou TOP Date: Wed, 28 Oct 2026 11:45:15 +0000 Subject: [PATCH] DT-163: Aggregate work items from all providers into tickets --- api/team-directory.ts | 13 +------- api/tickets.test.ts | 76 +++++++++++++++++++++++++++++++++++++++++++ api/tickets.ts | 15 +++++++-- 3 files changed, 89 insertions(+), 15 deletions(-) diff --git a/api/team-directory.ts b/api/team-directory.ts index 91d2ef4..7454907 100644 --- a/api/team-directory.ts +++ b/api/team-directory.ts @@ -1,16 +1,5 @@ import { get } from './lmdb-store.ts' - -// Matches the `Person` shape from tickets.ts (issue 1, a separate branch as -// of this issue) — duplicated rather than imported since this issue has no -// dependency on it. -export type Person = { - id: string - name: string - emails: string[] - githubLogin?: string - discordId?: string - jiraAccountId?: string -} +import type { Person } from './tickets.ts' export type PersonLink = { githubLogin?: string diff --git a/api/tickets.test.ts b/api/tickets.test.ts index 5865537..bccafb6 100644 --- a/api/tickets.test.ts +++ b/api/tickets.test.ts @@ -117,6 +117,41 @@ describe('deriveStatus', () => { it('defaults to todo with no recognizable signal', () => { assertEquals(deriveStatus([]), 'todo') }) + + it('is in_progress while any PR is open, even next to a merged one', () => { + assertEquals( + deriveStatus([ + jiraItem({ status: 'Done' }), + githubItem({ status: 'merged' }), + githubItem({ externalId: 'gh-2', status: 'open' }), + ]), + 'in_progress', + ) + }) + + it('is done when a PR is merged after another was closed unmerged', () => { + assertEquals( + deriveStatus([ + jiraItem({ status: 'In Progress' }), + githubItem({ status: 'closed' }), + githubItem({ externalId: 'gh-2', status: 'merged' }), + ]), + 'done', + ) + }) + + it('does not count an open github issue as work in progress', () => { + assertEquals( + deriveStatus([ + jiraItem({ status: 'To Do' }), + githubItem({ + externalUrl: 'https://github.com/01edu/license-hub/issues/2', + status: 'open', + }), + ]), + 'todo', + ) + }) }) describe('resolvePerson', () => { @@ -173,6 +208,47 @@ describe('groupIntoTickets', () => { assertEquals(tickets[0].assignees.map((p) => p.id), ['p1']) }) + it('merges the items of all three providers sharing a key', () => { + const tickets = groupIntoTickets( + [jiraItem(), githubItem({ status: 'open' }), discordItem()], + directory, + ) + + assertEquals(tickets.length, 1) + assertEquals(tickets[0].items.map((item) => item.source), [ + 'jira', + 'github', + 'discord', + ]) + }) + + const statusCases: [string, string, string][] = [ + ['To Do', 'open', 'in_progress'], + ['In Progress', 'open', 'in_progress'], + ['In Progress', 'merged', 'done'], + ] + for (const [jiraStatus, prStatus, expected] of statusCases) { + it(`deduces ${expected} from jira ${jiraStatus} + PR ${prStatus}`, () => { + const [ticket] = groupIntoTickets( + [jiraItem({ status: jiraStatus }), githubItem({ status: prStatus })], + directory, + ) + assertEquals(ticket.status, expected) + }) + } + + it('resolves reviewers to deduped people and drops unknown refs', () => { + const tickets = groupIntoTickets( + [ + githubItem({ reviewerRefs: [{ login: 'ada' }, { login: 'stranger' }] }), + githubItem({ externalId: 'gh-2', reviewerRefs: [{ login: 'ada' }] }), + ], + directory, + ) + + assertEquals(tickets[0].reviewers, [directory[0]]) + }) + it('keeps an unkeyed item as its own single-item ticket', () => { const unkeyed = githubItem({ title: 'Fix login bug' }) const tickets = groupIntoTickets([unkeyed], []) diff --git a/api/tickets.ts b/api/tickets.ts index 8613b93..422bd2a 100644 --- a/api/tickets.ts +++ b/api/tickets.ts @@ -101,10 +101,19 @@ const JIRA_STATUS_MAP: Record = { const mapJiraStatus = (status?: string): TicketStatus | undefined => status ? JIRA_STATUS_MAP[status.toLowerCase()] : undefined +// a github issue is only a placeholder, a PR is the evidence of real work +const isPullRequest = (item: WorkItem): boolean => + item.source === 'github' && item.externalUrl.includes('/pull/') + +const isOpen = (item: WorkItem): boolean => item.status === 'open' +const isMerged = (item: WorkItem): boolean => item.status === 'merged' + export const deriveStatus = (items: WorkItem[]): TicketStatus => { - const pr = items.find((item) => item.source === 'github') - if (pr?.status === 'merged') return 'done' - if (pr?.status === 'open') return 'in_progress' + // one key can carry several PRs (a follow-up, an abandoned first try): + // any still open means work is ongoing, otherwise any merged one is done + const prs = items.filter(isPullRequest) + if (prs.some(isOpen)) return 'in_progress' + if (prs.some(isMerged)) return 'done' const jira = items.find((item) => item.source === 'jira') return mapJiraStatus(jira?.status) ?? 'todo' }