feat: comments marked as decisions, on top of the ticket and searchable
Whoever can change the ticket flags a comment as a decision (new event
CommentDecisionMarkedEvent on the comment). The ticket page lists its
decisions above the description, with a link to the comment in the
conversation (`?comment=<id>` scrolls to it and points at it), and the
search palette finds them by their text across my projects.
API: markDecision / PUT-DELETE /api/comments/{id}/decision; Issue.decisions
and GET /api/issues/{issue}/decisions; decisions(projectId?, search) and
GET /api/projects/{project}/decisions, GET /api/me/decisions. MCP:
mark_decision, search_decisions, and get_issue includes them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,9 @@ const commentFromDoc = (doc: CommentDoc): CommentReadModel => ({
|
||||
replyTo: doc.replyTo ?? null,
|
||||
deleted: doc.deleted ?? false,
|
||||
reactions: doc.reactions ?? [],
|
||||
decision: doc.decision ?? false,
|
||||
decidedBy: doc.decidedBy ?? null,
|
||||
decidedAt: doc.decidedAt ?? null,
|
||||
});
|
||||
|
||||
const escapeRegex = (text: string) =>
|
||||
@@ -545,6 +548,28 @@ export class MongoFindCommentsRepository extends FindCommentsRepository {
|
||||
countReplies(commentId: string): Promise<number> {
|
||||
return this.comments.countDocuments({ replyTo: commentId });
|
||||
}
|
||||
|
||||
async findDecisions(filter: {
|
||||
issueId?: string;
|
||||
projectIds?: string[];
|
||||
search?: string;
|
||||
limit: number;
|
||||
}): Promise<CommentReadModel[]> {
|
||||
const query: Record<string, unknown> = {
|
||||
decision: true,
|
||||
deleted: { $ne: true },
|
||||
};
|
||||
if (filter.issueId) query['issueId'] = filter.issueId;
|
||||
if (filter.projectIds) query['projectId'] = { $in: filter.projectIds };
|
||||
if (filter.search)
|
||||
query['body'] = { $regex: escapeRegex(filter.search), $options: 'i' };
|
||||
const docs = await this.comments
|
||||
.find(query)
|
||||
.sort(filter.issueId ? { createdAt: 1 } : { decidedAt: -1 })
|
||||
.limit(filter.limit)
|
||||
.lean<CommentDoc[]>();
|
||||
return docs.map(commentFromDoc);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -239,6 +239,10 @@ class CommentDocument {
|
||||
}
|
||||
export const CommentSchema = SchemaFactory.createForClass(CommentDocument);
|
||||
CommentSchema.index({ issueId: 1, createdAt: 1 });
|
||||
CommentSchema.index(
|
||||
{ projectId: 1, decidedAt: -1 },
|
||||
{ partialFilterExpression: { decision: true } },
|
||||
);
|
||||
|
||||
@Schema({ ...LOOSE, collection: 'releases_view' })
|
||||
class ReleaseDocument {
|
||||
|
||||
Reference in New Issue
Block a user