Powiadomienia (w aplikacji i mailowe)

View on GitHub

Powiadomienia (w aplikacji i mailowe)

Orbit informuje użytkowników o aktywności na dwa sposoby: powiadomieniem w aplikacji (dropdown z dzwonkiem) oraz mailem, w podziale na App\Enums\Notifications\NotificationType — per użytkownik, per kanał, niezależnie przełączalne w ustawieniach konta. Ta kategoria dokumentuje ten system: jak event domenowy staje się powiadomieniem, jak preferencje per-użytkownik bramkują dostarczanie, oraz jeden krok, o którym łatwo zapomnieć przy dodawaniu nowego rodzaju powiadomienia.

Przewodniki, w kolejności, w jakiej faktycznie będą potrzebne

  1. Dodaj nowy typ powiadomieniaPowiadomienia (w aplikacji i mailowe)Dodaj nowy typ powiadomieniaPrzećwiczony przykład: powiadamianie członka, gdy zmieni się jego rola w projekcie — NotificationType::MemberRoleChanged. Dziś ProjectMemberService::update… — przećwiczony przykład dodania MemberRoleChanged (powiadamianie członka, gdy zmieni się jego rola w projekcie) od początku do końca: przypadek enuma, wystrzelenie i obsłużenie eventu oraz — krok, o którym łatwo zapomnieć — wiersz w ustawieniach na froncie, który w ogóle sprawia, że nowy typ jest widoczny i przełączalny.

  2. Wyślij powiadomienie z własnego koduPowiadomienia (w aplikacji i mailowe)Wyślij powiadomienie z własnego koduPrzewodnik 1 pokrywa podłączenie eventu domenowego przez SendNotificationListener do NotificationService::notify(). Nie zawsze potrzebujesz eventu pomiędzy… — pełny kontrakt NotificationService::notify() (odbiorca, typ, ważność wizualna, wiadomość, URL akcji, co dokładnie robi każdy kanał), na wypadek gdy chcesz wysłać istniejący typ powiadomienia bezpośrednio, bez wprowadzania nowego eventu domenowego.

  3. Przegląd połączenia frontend ↔ backendPowiadomienia (w aplikacji i mailowe)Przegląd połączenia frontend ↔ backendMapa całej funkcji: jak powiadomienia docierają do przeglądarki, jak stan read/unread wraca w obie strony i jak zakładka ustawień zapisuje preferencje. War… — jak powiadomienie faktycznie dociera do przeglądarki (to współdzielony prop Inertii, nie fetch), jak przebiega mark-as-read/mark-all-as-read/usuwanie oraz jak zakładka ustawień zapisuje przełącznik — przydatne jako mapa przed zagłębieniem się w którykolwiek z powyższych przewodników.

  4. Dodaj dedykowany mail transakcyjnyPowiadomienia (w aplikacji i mailowe)Dodaj dedykowany mail transakcyjnyPrzewodnik 2 pokrywa ponowne użycie generycznego szablonu NotificationMail (jeden współdzielony układ tematu/treści/przycisku akcji dla każdego Notificatio… — przećwiczony przykład (OwnershipTransferredMail) dla innego kształtu maila: takiego, który musi zostać wysłany bezwarunkowo, z własnym tematem i layoutem, całkowicie z pominięciem NotificationType/NotificationSetting — ten sam wzorzec, którego już używa ProjectInvitationMail.

  5. Dodaj obsługę @mention do pola tekstowegoPowiadomienia (w aplikacji i mailowe)Dodaj obsługę @mention do pola tekstowegoPrzećwiczony przykład: system @mention już zbudowany dla komentarzy do zadań — wpisz @ w polu komentarza, wybierz członka projektu z pływającej listy podpo… — przećwiczony przykład: system @mention w komentarzach (IssueMentioned), od początku do końca, od wpisania @ w zwykłym <textarea> aż po powiadomienie odbiorcy. Pokrywa wielokrotnego użytku technikę śledzenia zakresów (MentionRange/applyRangeEdit), która odróżnia dwie wzmianki po pozycji znaków zamiast po nazwie, oraz kontrolę po stronie backendu, która nie pozwala klientowi zgłosić mentioned_user_ids dla osoby, której treść komentarza nigdy faktycznie nie wymienia.

  6. Sprzątanie starych powiadomień i historii aktywnościPowiadomienia (w aplikacji i mailowe)Sprzątanie starych powiadomień i historii aktywnościPowiadomienia i wpisy dziennika aktywności gromadzą się w nieskończoność, jeśli nic ich nie usuwa. Orbit daje każdemu użytkownikowi dwa sposoby, oba w Usta… — harmonogram per użytkownik (codziennie / co tydzień / co miesiąc, notifications:prune) oraz jednorazowe akcje "usuń starsze niż N dni" w Ustawienia → Powiadomienia: co jest usuwane, logika "należy do uruchomienia", cache oraz jak dodać wartość wieku, częstotliwość albo kolejny rodzaj danych.

Podłączenie eventu domenowego do pipeline'u powiadomień jest samo w sobie udokumentowane w ramach ../integrations/03-add-a-new-event-type.mdIntegracjeDodaj nowy typ eventuPrzećwiczony przykład: dodanie IssueCreated — rodzaju aktywności, który jeszcze nie istnieje. Dziś stworzenie issue odpala tylko IssueAssigned, i to tylko… (na przykładzie IssueCreated) — przewodnik 1 nie powtarza tego mechanizmu, tylko pokrywa wszystko, co jest specyficzne dla typów powiadomień ponad nim: wiersz w ustawieniach, domyślne wartości kanałów oraz podział frontend/backend, którego tamten przewodnik nie dotyka.

Architektura w jednym akapicie

Każdy fakt wart powiadomienia to jeden przypadek NotificationType (np. IssueAssigned, ProjectInvited). Każde powiadomienie może pójść jednym lub obydwoma z dwóch NotificationChannel — InApp (domyślnie włączony) i Email (domyślnie wyłączony, zobacz NotificationChannel::enabledByDefault()) — a użytkownik może nadpisać dowolny kanał dla dowolnego typu poprzez wiersz NotificationSetting (user_id + type + channel + enabled; brak wiersza oznacza "użyj domyślnej wartości kanału"). NotificationService::notify() to jedyny lejek, przez który wszystko przechodzi: zawsze prosi NotificationMailService o (ewentualne) wysłanie maila, a następnie sprawdza preferencję odbiorcy dla powiadomień w aplikacji, zanim w ogóle zapisze wiersz Notification — pominięcie tego sprawdzenia zapisywałoby wiersze dla kanałów, które użytkownik jawnie wyłączył. Eventy domenowe nie wywołują notify() bezpośrednio; SendNotificationListener::handle() to jedyne miejsce, które zamienia fakt "to się wydarzyło" (IssueAssigned, CommentAdded, ProjectInvited, …) w "powiadom tę konkretną osobę, z tym tytułem/wiadomością" — zobacz ../integrations/03-add-a-new-event-type.mdIntegracjeDodaj nowy typ eventuPrzećwiczony przykład: dodanie IssueCreated — rodzaju aktywności, który jeszcze nie istnieje. Dziś stworzenie issue odpala tylko IssueAssigned, i to tylko… po to, jak sam event jest tworzony i rejestrowany. Na froncie tablica defaultNotificationTypes w AccountSettingsNotificationsTab.tsx to ręcznie utrzymywane lustro przypadków NotificationType z backendu (ikona, tytuł, opis per typ) — nic nie generuje jej z enuma, więc nowy typ z backendu jest niewidoczny w Ustawieniach, dopóki nie dodasz tam też jego wiersza.

Popup z dzwonkiem nie renderuje całej skrzynki: współdzielony prop niesie tylko 20 najnowszych powiadomień oraz prawdziwą liczbę nieprzeczytanych, a starsze popup dociąga stronami z GET /notifications (kursor keyset, filtry nieprzeczytanych i ważności), z trybem zaznaczania do hurtowego oznaczania/usuwania — zobacz przewodnik 3.