feat: whoever opens or gets a ticket follows it
Like a commenter, whoever opens a ticket and whoever it is assigned to (also on reassigning) follow it from then on, so they hear of its priority, due date and sprint changes and find it under "Que sigo". Existing tickets are left as they are. Docs, Word and PDF updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
IssueAssignedEvent,
|
||||
IssueCreatedEvent,
|
||||
IssuePriority,
|
||||
} from '@task-stream/api/tracker/domain';
|
||||
import { lastValueFrom, of, toArray } from 'rxjs';
|
||||
import { WatchIssueCommand } from '../commands/watch-issue.command';
|
||||
import { PeopleFollowSaga } from './people-follow.saga';
|
||||
|
||||
describe('PeopleFollowSaga', () => {
|
||||
const saga = new PeopleFollowSaga();
|
||||
const created = (assigneeId: string | null) =>
|
||||
new IssueCreatedEvent('ana', {
|
||||
id: 'i-1',
|
||||
projectId: 'p-1',
|
||||
reporterId: 'ana',
|
||||
assigneeId,
|
||||
priority: IssuePriority.MEDIUM,
|
||||
} as never);
|
||||
|
||||
it('makes whoever opens a ticket, and whoever gets it, follow it', async () => {
|
||||
expect(
|
||||
await lastValueFrom(saga.created(of(created('bob'))).pipe(toArray())),
|
||||
).toEqual([
|
||||
new WatchIssueCommand('ana', 'i-1', 'ana'),
|
||||
new WatchIssueCommand('bob', 'i-1', 'bob'),
|
||||
]);
|
||||
// Opening a ticket for oneself: once.
|
||||
expect(
|
||||
await lastValueFrom(saga.created(of(created('ana'))).pipe(toArray())),
|
||||
).toEqual([new WatchIssueCommand('ana', 'i-1', 'ana')]);
|
||||
});
|
||||
|
||||
it('makes whoever a ticket is assigned to follow it, not when unassigned', async () => {
|
||||
const commands = await lastValueFrom(
|
||||
saga
|
||||
.assigned(
|
||||
of(
|
||||
new IssueAssignedEvent('ana', 'i-1', 'p-1', 'bob', null),
|
||||
new IssueAssignedEvent('ana', 'i-1', 'p-1', null, 'bob'),
|
||||
),
|
||||
)
|
||||
.pipe(toArray()),
|
||||
);
|
||||
expect(commands).toEqual([new WatchIssueCommand('bob', 'i-1', 'bob')]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ICommand, IEvent, ofType, Saga } from '@nestjs/cqrs';
|
||||
import {
|
||||
IssueAssignedEvent,
|
||||
IssueCreatedEvent,
|
||||
} from '@task-stream/api/tracker/domain';
|
||||
import { filter, mergeMap, Observable } from 'rxjs';
|
||||
import { WatchIssueCommand } from '../commands/watch-issue.command';
|
||||
|
||||
/** Follows a ticket as `userId` would by pressing "Seguir". */
|
||||
const follow = (userId: string, issueId: string) =>
|
||||
new WatchIssueCommand(userId, issueId, userId);
|
||||
|
||||
/**
|
||||
* Whoever opens a ticket and whoever it is assigned to follow it from then
|
||||
* on, as a commenter does: its changes concern them. Unfollowing stays
|
||||
* theirs to do. (Idempotent: following twice changes nothing; someone who
|
||||
* is not a member, like the system, is refused and skipped.)
|
||||
*/
|
||||
@Injectable()
|
||||
export class PeopleFollowSaga {
|
||||
@Saga()
|
||||
created = (events$: Observable<IEvent>): Observable<ICommand> =>
|
||||
events$.pipe(
|
||||
ofType(IssueCreatedEvent),
|
||||
mergeMap(({ issue }) =>
|
||||
[...new Set([issue.reporterId, issue.assigneeId])]
|
||||
.filter((userId): userId is string => !!userId)
|
||||
.map((userId) => follow(userId, issue.id)),
|
||||
),
|
||||
);
|
||||
|
||||
@Saga()
|
||||
assigned = (events$: Observable<IEvent>): Observable<ICommand> =>
|
||||
events$.pipe(
|
||||
ofType(IssueAssignedEvent),
|
||||
filter((event) => !!event.assigneeId),
|
||||
mergeMap((event) => [
|
||||
follow(event.assigneeId as string, event.aggregateId),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ProjectDeletionSaga } from './project-deletion.saga';
|
||||
import { ReleaseArchivedSaga } from './release-archived.saga';
|
||||
import { UserDeactivatedSaga } from './user-deactivated.saga';
|
||||
import { CommentersFollowSaga } from './commenters-follow.saga';
|
||||
import { PeopleFollowSaga } from './people-follow.saga';
|
||||
import { SprintCarryOverSaga } from './sprint-carry-over.saga';
|
||||
|
||||
/** Reactions to events that change other aggregates (run by the events bridge). */
|
||||
@@ -10,5 +11,6 @@ export const SAGAS = [
|
||||
ReleaseArchivedSaga,
|
||||
UserDeactivatedSaga,
|
||||
CommentersFollowSaga,
|
||||
PeopleFollowSaga,
|
||||
SprintCarryOverSaga,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user