Files
task-stream/libs/web/tracker/infrastructure/src/lib/adapters/graphql-collaboration.gateways.ts
Josep Piguillem 285bd5c454 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>
2026-10-21 21:05:44 +02:00

226 lines
6.3 KiB
TypeScript

import { inject, Injectable } from '@angular/core';
import {
Activity,
ActivityGateway,
CommandResult,
Comment,
CommentsGateway,
CreateReleaseInput,
Release,
ReleaseBurnup,
ReleasesGateway,
UserDirectoryEntry,
UsersGateway,
} from '@task-stream/web/tracker/domain';
import { map, Observable } from 'rxjs';
import {
ActivityDocument,
ActivityOfDayDocument,
AddCommentDocument,
ArchiveReleaseDocument,
CreateReleaseDocument,
DeleteCommentDocument,
EditCommentDocument,
IssueCommentsDocument,
ReactToCommentDocument,
MarkDecisionDocument,
ResolveThreadDocument,
TurnCommentIntoIssueDocument,
VotePollDocument,
ClosePollDocument,
PublishReleaseDocument,
ReleaseBurnupDocument,
ReleaseNotesDraftDocument,
WriteReleaseNotesDocument,
ReleasesDocument,
UpcomingReleasesDocument,
SearchUsersDocument,
} from '../graphql/generated';
import { GraphqlOperations } from './graphql-operations';
import { toActivity, toCommandResult, toComment, toRelease } from './mappers';
@Injectable()
export class GraphqlCommentsGateway extends CommentsGateway {
private readonly gql = inject(GraphqlOperations);
list(issueId: string): Observable<Comment[]> {
return this.gql
.query(IssueCommentsDocument, { idOrKey: issueId })
.pipe(map((d) => d.issue.comments.items.map(toComment)));
}
add(
issueId: string,
body: string,
replyTo: string | null = null,
poll: string[] | null = null,
): Observable<CommandResult> {
return this.gql
.mutate(AddCommentDocument, {
issueId,
body,
replyTo,
poll: poll?.length ? poll : null,
})
.pipe(map((d) => toCommandResult(d.addComment)));
}
votePoll(
commentId: string,
option: number | null,
): Observable<CommandResult> {
return this.gql
.mutate(VotePollDocument, { commentId, option })
.pipe(map((d) => toCommandResult(d.votePoll)));
}
closePoll(commentId: string, decide: boolean): Observable<CommandResult> {
return this.gql
.mutate(ClosePollDocument, { commentId, decide })
.pipe(map((d) => toCommandResult(d.closePoll)));
}
react(
commentId: string,
emoji: string,
on: boolean,
): Observable<CommandResult> {
return this.gql
.mutate(ReactToCommentDocument, { commentId, emoji, on })
.pipe(map((d) => toCommandResult(d.reactToComment)));
}
edit(commentId: string, body: string): Observable<CommandResult> {
return this.gql
.mutate(EditCommentDocument, { commentId, body })
.pipe(map((d) => toCommandResult(d.editComment)));
}
remove(commentId: string): Observable<CommandResult> {
return this.gql
.mutate(DeleteCommentDocument, { commentId })
.pipe(map((d) => toCommandResult(d.deleteComment)));
}
resolveThread(
commentId: string,
resolved: boolean,
conclusion: string | null,
): Observable<CommandResult> {
return this.gql
.mutate(ResolveThreadDocument, { commentId, resolved, conclusion })
.pipe(map((d) => toCommandResult(d.resolveThread)));
}
turnIntoIssue(
commentId: string,
title: string,
type: string,
asSubtask: boolean,
): Observable<CommandResult> {
return this.gql
.mutate(TurnCommentIntoIssueDocument, {
commentId,
title,
type,
asSubtask,
})
.pipe(map((d) => toCommandResult(d.turnCommentIntoIssue)));
}
markDecision(commentId: string, on: boolean): Observable<CommandResult> {
return this.gql
.mutate(MarkDecisionDocument, { commentId, on })
.pipe(map((d) => toCommandResult(d.markDecision)));
}
}
@Injectable()
export class GraphqlReleasesGateway extends ReleasesGateway {
private readonly gql = inject(GraphqlOperations);
list(projectId: string): Observable<Release[]> {
return this.gql
.query(ReleasesDocument, { idOrKey: projectId })
.pipe(map((d) => d.project.releases.map(toRelease)));
}
upcoming(limit: number): Observable<Release[]> {
return this.gql
.query(UpcomingReleasesDocument, { limit })
.pipe(map((d) => d.upcomingReleases.map(toRelease)));
}
create(input: CreateReleaseInput): Observable<CommandResult> {
return this.gql
.mutate(CreateReleaseDocument, { input })
.pipe(map((d) => toCommandResult(d.createRelease)));
}
publish(releaseId: string, force: boolean): Observable<CommandResult> {
return this.gql
.mutate(PublishReleaseDocument, { releaseId, force })
.pipe(map((d) => toCommandResult(d.publishRelease)));
}
archive(
releaseId: string,
moveTo: string | null = null,
): Observable<CommandResult> {
return this.gql
.mutate(ArchiveReleaseDocument, { releaseId, moveTo })
.pipe(map((d) => toCommandResult(d.archiveRelease)));
}
burnup(releaseId: string): Observable<ReleaseBurnup> {
return this.gql
.query(ReleaseBurnupDocument, { id: releaseId })
.pipe(map((d) => d.release.burnup));
}
notesDraft(releaseId: string): Observable<string> {
return this.gql
.query(ReleaseNotesDraftDocument, { id: releaseId })
.pipe(map((d) => d.release.notesDraft));
}
writeNotes(
releaseId: string,
notes: string | null,
): Observable<CommandResult> {
return this.gql
.mutate(WriteReleaseNotesDocument, { releaseId, notes })
.pipe(map((d) => toCommandResult(d.writeReleaseNotes)));
}
}
@Injectable()
export class GraphqlActivityGateway extends ActivityGateway {
private readonly gql = inject(GraphqlOperations);
list(projectId: string, issueId?: string): Observable<Activity[]> {
return this.gql
.query(ActivityDocument, { projectId, issueId: issueId ?? null })
.pipe(map((d) => d.activity.map(toActivity)));
}
onDay(projectId: string, day: string): Observable<Activity[]> {
const after = `${day}T00:00:00.000Z`;
const before = new Date(Date.parse(after) + 86_400_000).toISOString();
return this.gql
.query(ActivityOfDayDocument, { projectId, after, before })
.pipe(map((d) => d.activity.map(toActivity)));
}
}
@Injectable()
export class GraphqlUsersGateway extends UsersGateway {
private readonly gql = inject(GraphqlOperations);
search(term: string): Observable<UserDirectoryEntry[]> {
return this.gql
.query(SearchUsersDocument, { search: term })
.pipe(map((d) => d.users));
}
}