feat: what is new since my last visit, and turning a comment into a ticket
Opening a ticket notes the visit and answers the one before (issue_visits), so the page marks others' comments and changes since. A comment can become a ticket of its own, as a subtask or linked; it remembers where it went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
import { CommentAddedEvent } from '../events/comment-added.event';
|
||||
import { CommentDecisionMarkedEvent } from '../events/comment-decision-marked.event';
|
||||
import { CommentThreadResolvedEvent } from '../events/comment-thread-resolved.event';
|
||||
import { CommentTurnedIntoIssueEvent } from '../events/comment-turned-into-issue.event';
|
||||
import {
|
||||
CommentPollClosedEvent,
|
||||
CommentPollVotedEvent,
|
||||
@@ -82,6 +83,8 @@ export class Comment extends VersionedAggregateRoot implements CommentProps {
|
||||
public pollVotes: Record<string, number> = {};
|
||||
public pollClosed = false;
|
||||
public pollWinner: number | null = null;
|
||||
/** Tickets made from it, oldest first. */
|
||||
public spawned: { id: string; key: string }[] = [];
|
||||
public deleted = false;
|
||||
public createdAt!: string;
|
||||
public editedAt: string | null = null;
|
||||
@@ -277,10 +280,32 @@ export class Comment extends VersionedAggregateRoot implements CommentProps {
|
||||
this.resolved = event.resolved;
|
||||
}
|
||||
|
||||
onCommentTurnedIntoIssueEvent(event: CommentTurnedIntoIssueEvent): void {
|
||||
this.spawned = event.spawned;
|
||||
}
|
||||
|
||||
onCommentDeletedEvent(): void {
|
||||
this.deleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notes a ticket made from it (its text became the ticket's description).
|
||||
* Who may create it is the caller's check.
|
||||
*/
|
||||
turnedInto(actorId: string, issue: { id: string; key: string }): void {
|
||||
this.assertAlive();
|
||||
if (this.spawned.some((s) => s.id === issue.id)) return;
|
||||
this.apply(
|
||||
new CommentTurnedIntoIssueEvent(
|
||||
actorId,
|
||||
this.id,
|
||||
this.projectId,
|
||||
this.issueId,
|
||||
[...this.spawned, issue],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private assertAlive(): void {
|
||||
if (this.deleted) throw new BusinessRuleError('Comment was deleted');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AutowiredEvent } from '@task-stream/api/shared/domain';
|
||||
import { TrackerEvent } from './tracker.event';
|
||||
|
||||
/** A ticket of its own was made from a comment («→ WEB-45» on it). */
|
||||
@AutowiredEvent
|
||||
export class CommentTurnedIntoIssueEvent extends TrackerEvent {
|
||||
constructor(
|
||||
actorId: string,
|
||||
commentId: string,
|
||||
projectId: string,
|
||||
readonly issueId: string,
|
||||
/** Every ticket made from it so far, this one last. */
|
||||
readonly spawned: { id: string; key: string }[],
|
||||
) {
|
||||
super(actorId, commentId, projectId);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export * from './comment-edited.event';
|
||||
export * from './comment-reacted.event';
|
||||
export * from './comment-decision-marked.event';
|
||||
export * from './comment-thread-resolved.event';
|
||||
export * from './comment-turned-into-issue.event';
|
||||
export * from './issue-review.events';
|
||||
export * from './comment-poll.events';
|
||||
export * from './comment-deleted.event';
|
||||
|
||||
@@ -41,6 +41,8 @@ export class CommentReadModel {
|
||||
/** Who settled it and when (null while open). */
|
||||
resolvedBy!: UserRefReadModel | null;
|
||||
resolvedAt!: string | null;
|
||||
/** Tickets made from it («Convertir en ticket»), oldest first. */
|
||||
spawned!: { id: string; key: string }[];
|
||||
createdAt!: string;
|
||||
editedAt!: string | null;
|
||||
version!: number;
|
||||
|
||||
Reference in New Issue
Block a user