feat: notifications in the app and, optionally, by e-mail
Derived from events and kept per person: whoever a ticket is assigned to, whoever reports or works on it when it moves, fails QA or gets a comment, and whoever joins a project. Never about what you did yourself, once per event. They arrive live through a subscription to a bell in the header, and are copied by e-mail to whoever turns it on when SMTP_URL is set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
Notification,
|
||||
NotificationSettings,
|
||||
NotificationsGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import {
|
||||
MarkNotificationsReadDocument,
|
||||
MyNotificationsDocument,
|
||||
NotificationSettingsDocument,
|
||||
UpdateNotificationSettingsDocument,
|
||||
} from '../graphql/generated';
|
||||
import { GraphqlOperations } from './graphql-operations';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlNotificationsGateway extends NotificationsGateway {
|
||||
private readonly gql = inject(GraphqlOperations);
|
||||
|
||||
mine(
|
||||
unreadOnly: boolean,
|
||||
limit: number,
|
||||
): Observable<{ items: Notification[]; unread: number }> {
|
||||
return this.gql.query(MyNotificationsDocument, { unreadOnly, limit }).pipe(
|
||||
map(({ myNotifications }) => ({
|
||||
unread: myNotifications.unread,
|
||||
items: myNotifications.items as Notification[],
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
markRead(ids?: string[]): Observable<void> {
|
||||
return this.gql
|
||||
.mutate(MarkNotificationsReadDocument, { ids: ids ?? null })
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
settings(): Observable<NotificationSettings> {
|
||||
return this.gql
|
||||
.query(NotificationSettingsDocument)
|
||||
.pipe(map((d) => ({ ...d.notificationSettings })));
|
||||
}
|
||||
|
||||
updateSettings(email: boolean): Observable<NotificationSettings> {
|
||||
return this.gql
|
||||
.mutate(UpdateNotificationSettingsDocument, { email })
|
||||
.pipe(map((d) => ({ ...d.updateNotificationSettings })));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Activity,
|
||||
CommentChanged,
|
||||
IssueChanged,
|
||||
Notification,
|
||||
ProjectChanged,
|
||||
Release,
|
||||
TrackerRealtimeGateway,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
CommentChangedDocument,
|
||||
IssueChangedDocument,
|
||||
MyProjectsChangedDocument,
|
||||
NotificationAddedDocument,
|
||||
ProjectChangedDocument,
|
||||
ReleaseChangedDocument,
|
||||
} from '../graphql/generated';
|
||||
@@ -98,6 +100,14 @@ export class GraphqlRealtimeGateway extends TrackerRealtimeGateway {
|
||||
.pipe(map((d) => toProjectChanged(d.myProjectsChanged))),
|
||||
);
|
||||
}
|
||||
|
||||
notifications(): Observable<Notification> {
|
||||
return this.signedIn(() =>
|
||||
this.gql
|
||||
.subscribe(NotificationAddedDocument)
|
||||
.pipe(map((d) => d.notificationAdded as Notification)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const toProjectChanged = (change: {
|
||||
|
||||
Reference in New Issue
Block a user