Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ pass), it:
- finishes a plan: one under way whose issue closed, or all of whose issues
closed, with no PRs open, is done, or dropped when they were closed as not
planned. One past its hard end with none open, its status untouched
since, is done.
since, is done. Never an ongoing plan: upkeep has no end, so closed
issues only mean nothing is asked of it, and it parks once it's idle.
- plans a project: one with 3 or more PRs open or merged in the last 14
days that never had a plan gets one, in progress from its first open
PR's week (or its first PR's, when all have merged) with no end, named
Expand Down
11 changes: 11 additions & 0 deletions frontend-v2/src/model/autoCalls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,17 @@ describe('finished', () => {
expect(calls([plan], { p1: [] }, { rows: dropped })[0]).toMatchObject({ status: 'dropped' });
});

it('never finishes an ongoing plan, whose issues closing means nothing is asked of it', () => {
const plan = item(1, { start: '2026-09-07', end_kind: 'ongoing' });
const issues = new Map([['p1', { total: 3, open: 0, done: 3, dropped: 0, lastClosedAt: ago(2) }]]);
const finishes = (c: ReturnType<typeof calls>) =>
c.filter(x => x.kind === 'status' && (x.status === 'done' || x.status === 'dropped'));
const rows = rowsFor(plan, [project('p1')], { issues });
expect(finishes(calls([plan], { p1: [] }, { rows }))).toEqual([]);
const closed = rowsFor(plan, [project('p1')], { closed: closedOn('done', 3) });
expect(finishes(calls([plan], { p1: [] }, { rows: closed }))).toEqual([]);
});

it('finishes a plan past its hard end with none open, until someone reopens it', () => {
const plan = item(1, { end_kind: 'hard', start: '2026-08-03', weeks: 4, status_at: ago(60) });
const rows = rowsFor(plan, [project('p1')]);
Expand Down
6 changes: 5 additions & 1 deletion shared/model/autoCalls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { isUnderWay, mondayOf, planEnd, type RoadmapItem } from './roadmap';
* - finished: a plan under way whose issue closed, or all of whose issues
* closed, with none of its PRs open, is done (dropped when they were
* closed as not planned); one past its hard end with none open, its
* status untouched since, is done (model/decide.ts answeredOnItsOwn)
* status untouched since, is done (model/decide.ts answeredOnItsOwn).
* Never an ongoing plan: upkeep has no end, so it parks when idle
* - new: a project with DECIDE_MIN_PRS or more PRs open or merged in the
* last LIVE_DAYS, that never had a plan (not even one since removed),
* gets one in progress, with no end; put down parks it once it's idle.
Expand Down Expand Up @@ -239,6 +240,9 @@ export function autoCalls({
for (const r of row.reasons) {
const item = row.item;
if (!item || !isUnderWay(item.status) || !answeredOnItsOwn(r, item, timeZone)) continue;
// upkeep has no end: closed issues mean nothing is asked of it right
// now, not that it's finished; it parks once it's idle instead
if (item.end_kind === 'ongoing') continue;
if (r.kind === 'issue_closed') {
const as = r.as === 'dropped' ? 'not planned' : 'completed';
setStatus(item, r.as, `Finished: its issue was closed as ${as} on ${r.on}`);
Expand Down
Loading