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>
226 lines
6.3 KiB
TypeScript
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));
|
|
}
|
|
}
|