import { inject, Injectable } from '@angular/core'; import { BoardColumn, BoardFilter, BulkChange, BulkResult, ChangeStatusInput, CommandResult, CreateIssueInput, Epic, Issue, IssuePage, IssuesGateway, IssueSummary, LinkIssuesInput, MyIssuesFilter, UpdateIssueInput, } from '@task-stream/web/tracker/domain'; import { map, Observable } from 'rxjs'; import { AddChecklistItemDocument, AssignIssueDocument, BacklogDocument, BulkUpdateIssuesDocument, BoardDocument, ChangeIssueStatusDocument, CreateIssueDocument, EpicsDocument, IssueChildrenDocument, IssueDocument, LinkIssuesDocument, MyIssuesDocument, RankIssueDocument, RemoveChecklistItemDocument, SearchIssuesDocument, SetIssueParentDocument, SetIssueReleaseDocument, UnlinkIssuesDocument, UnwatchIssueDocument, WatchIssueDocument, UpdateChecklistItemDocument, UpdateIssueDocument, } from '../graphql/generated'; import { GraphqlOperations } from './graphql-operations'; import { toCommandResult, toIssue, toIssueSummary } from './mappers'; @Injectable() export class GraphqlIssuesGateway extends IssuesGateway { private readonly gql = inject(GraphqlOperations); board(filter: BoardFilter): Observable { return this.gql .query(BoardDocument, { idOrKey: filter.projectId, releaseId: filter.releaseId ?? null, assigneeId: filter.assigneeId ?? null, area: filter.area ?? null, types: filter.types?.length ? filter.types : null, priorities: filter.priorities?.length ? (filter.priorities as never) : null, labels: filter.labels?.length ? filter.labels : null, search: filter.search?.trim() || null, unassigned: filter.unassigned ?? null, sprintId: filter.sprintId ?? null, }) .pipe( map((d) => d.project.board.map((column) => ({ status: column.status, total: column.total, issues: column.issues.map(toIssueSummary), })), ), ); } issue(idOrKey: string): Observable { return this.gql .query(IssueDocument, { idOrKey }) .pipe(map((d) => toIssue(d.issue))); } mine(filter: MyIssuesFilter): Observable { return this.gql .query(MyIssuesDocument, { assignedToMe: filter.assignedToMe, categories: filter.categories as never, limit: filter.limit, search: filter.search?.trim() || null, }) .pipe( map((d) => ({ items: d.myIssues.items.map(toIssueSummary), total: d.myIssues.total, })), ); } search(projectId: string, text: string): Observable { return this.gql .query(SearchIssuesDocument, { filter: { projectId, search: text } }) .pipe(map((d) => d.issues.items.map(toIssueSummary))); } create(input: CreateIssueInput): Observable { return this.gql .mutate(CreateIssueDocument, { input: input as never }) .pipe(map((d) => toCommandResult(d.createIssue))); } update(input: UpdateIssueInput): Observable { return this.gql .mutate(UpdateIssueDocument, { input: input as never }) .pipe(map((d) => toCommandResult(d.updateIssue))); } changeStatus(input: ChangeStatusInput): Observable { return this.gql .mutate(ChangeIssueStatusDocument, { input: input as never }) .pipe(map((d) => toCommandResult(d.changeIssueStatus))); } assign( issueId: string, assigneeId: string | null, ): Observable { return this.gql .mutate(AssignIssueDocument, { issueId, assigneeId }) .pipe(map((d) => toCommandResult(d.assignIssue))); } link(input: LinkIssuesInput): Observable { return this.gql .mutate(LinkIssuesDocument, { input: input as never }) .pipe(map((d) => toCommandResult(d.linkIssues))); } unlink(input: LinkIssuesInput): Observable { return this.gql .mutate(UnlinkIssuesDocument, { input: input as never }) .pipe(map((d) => toCommandResult(d.unlinkIssues))); } watch(issueId: string, watching: boolean): Observable { return watching ? this.gql .mutate(WatchIssueDocument, { issueId }) .pipe(map((d) => toCommandResult(d.watchIssue))) : this.gql .mutate(UnwatchIssueDocument, { issueId }) .pipe(map((d) => toCommandResult(d.unwatchIssue))); } children(projectId: string, issueId: string): Observable { return this.gql .query(IssueChildrenDocument, { filter: { projectId, parentId: issueId }, }) .pipe(map((d) => d.issues.items.map(toIssueSummary))); } setParent( issueId: string, parentId: string | null, ): Observable { return this.gql .mutate(SetIssueParentDocument, { issueId, parentId }) .pipe(map((d) => toCommandResult(d.setIssueParent))); } epics(projectId: string): Observable { return this.gql.query(EpicsDocument, { projectId }).pipe( map((d) => d.epics.map((epic) => ({ issue: toIssueSummary(epic.issue), progress: { ...epic.progress }, })), ), ); } bulkUpdate(issueIds: string[], change: BulkChange): Observable { return this.gql .mutate(BulkUpdateIssuesDocument, { input: { issueIds, status: change.status ?? null, unassign: change.assigneeId === null ? true : null, assigneeId: change.assigneeId ?? null, noSprint: change.sprintId === null ? true : null, sprintId: change.sprintId ?? null, priority: (change.priority ?? null) as never, addLabels: change.addLabels ?? null, }, }) .pipe(map((d) => d.bulkUpdateIssues)); } backlog(projectId: string): Observable { return this.gql .query(BacklogDocument, { filter: { projectId, categories: ['TODO'], sort: 'RANK' }, }) .pipe( map((d) => ({ items: d.issues.items.map(toIssueSummary), total: d.issues.total, })), ); } rank( issueId: string, aboveIssueId: string | null, belowIssueId: string | null, ): Observable { return this.gql .mutate(RankIssueDocument, { input: { issueId, aboveIssueId, belowIssueId }, }) .pipe(map((d) => toCommandResult(d.rankIssue))); } addChecklistItem(issueId: string, text: string): Observable { return this.gql .mutate(AddChecklistItemDocument, { input: { issueId, text } }) .pipe(map((d) => toCommandResult(d.addChecklistItem))); } updateChecklistItem( issueId: string, itemId: string, changes: { text?: string; done?: boolean }, ): Observable { return this.gql .mutate(UpdateChecklistItemDocument, { input: { issueId, itemId, ...changes }, }) .pipe(map((d) => toCommandResult(d.updateChecklistItem))); } removeChecklistItem( issueId: string, itemId: string, ): Observable { return this.gql .mutate(RemoveChecklistItemDocument, { issueId, itemId }) .pipe(map((d) => toCommandResult(d.removeChecklistItem))); } setRelease( issueId: string, releaseId: string | null, ): Observable { return this.gql .mutate(SetIssueReleaseDocument, { issueId, releaseId }) .pipe(map((d) => toCommandResult(d.setIssueRelease))); } }