Plan sprints with dates and a goal, run one at a time per project, and put tickets in them from the ticket page. The Sprints tab follows the one under way with its progress and a burndown in tickets or points against the ideal line. Closing a sprint sends what was left to the next one or back to the backlog; the board can be filtered by sprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
|
|
import {
|
|
SprintClosedEvent,
|
|
SprintCreatedEvent,
|
|
SprintDeletedEvent,
|
|
SprintStartedEvent,
|
|
SprintStatus,
|
|
SprintUpdatedEvent,
|
|
} from '@task-stream/api/tracker/domain';
|
|
import { UpsertMaterializedIssueRepository } from '../ports/upsert-materialized-issue.repository';
|
|
import { UpsertMaterializedSprintRepository } from '../ports/upsert-materialized-sprint.repository';
|
|
import { ReadModelNotifier, versionOf } from './read-model-notifier';
|
|
|
|
type SprintEvent =
|
|
| SprintCreatedEvent
|
|
| SprintUpdatedEvent
|
|
| SprintStartedEvent
|
|
| SprintClosedEvent
|
|
| SprintDeletedEvent;
|
|
|
|
/** The sprints view (`sprints_view`), one document per sprint. */
|
|
@EventsHandler(
|
|
SprintCreatedEvent,
|
|
SprintUpdatedEvent,
|
|
SprintStartedEvent,
|
|
SprintClosedEvent,
|
|
SprintDeletedEvent,
|
|
)
|
|
export class SprintEventHandler implements IEventHandler<SprintEvent> {
|
|
constructor(
|
|
private readonly sprints: UpsertMaterializedSprintRepository,
|
|
private readonly issues: UpsertMaterializedIssueRepository,
|
|
private readonly notifier: ReadModelNotifier,
|
|
) {}
|
|
|
|
async handle(event: SprintEvent) {
|
|
const version = versionOf(event);
|
|
const id = event.aggregateId;
|
|
if (event instanceof SprintCreatedEvent) {
|
|
await this.sprints.upsert({ ...event.sprint, version });
|
|
} else if (event instanceof SprintUpdatedEvent) {
|
|
await this.sprints.upsert({ id, ...event.changes, version });
|
|
// Tickets show its name.
|
|
if (event.changes.name)
|
|
await this.issues.renameSprint(id, event.changes.name);
|
|
} else if (event instanceof SprintStartedEvent) {
|
|
await this.sprints.upsert({
|
|
id,
|
|
status: SprintStatus.ACTIVE,
|
|
startedAt: event.occurredAt,
|
|
version,
|
|
});
|
|
} else if (event instanceof SprintClosedEvent) {
|
|
await this.sprints.upsert({
|
|
id,
|
|
status: SprintStatus.CLOSED,
|
|
closedAt: event.occurredAt,
|
|
version,
|
|
});
|
|
} else {
|
|
await this.sprints.remove(id);
|
|
}
|
|
await this.notifier.sprint(id, event);
|
|
}
|
|
}
|