|
| 1 | +--- |
| 2 | +name: Create In-App Notification |
| 3 | +description: Create or update a Baserow in-app notification for an event. Use when adding a backend `NotificationType`, wiring frontend notification rendering and routing, defining the notification target, or preventing duplicate notifications for the same event object. |
| 4 | +--- |
| 5 | + |
| 6 | +# Create Baserow In-App Notifications |
| 7 | + |
| 8 | +Use this skill when a task is to add or update an in-app notification shown in Baserow's notification center. |
| 9 | + |
| 10 | +Do not invent a new notification architecture. This repo already has established backend and frontend patterns. Start from the closest existing notification type in the same product area: core, database, builder, automation, integration, premium, or enterprise. |
| 11 | + |
| 12 | +## First Step |
| 13 | + |
| 14 | +Before editing, identify which shape best matches the event: |
| 15 | + |
| 16 | +1. One event sends one notification to one or more explicit users. |
| 17 | +2. One event fans out to many users and should be grouped or queued efficiently. |
| 18 | +3. One event is instance-wide and should be a broadcast notification. |
| 19 | +4. The event should update or reuse an existing notification instead of creating another one. |
| 20 | + |
| 21 | +Then inspect the closest example before editing. |
| 22 | + |
| 23 | +Useful starting points: |
| 24 | + |
| 25 | +- Core notification types: `backend/src/baserow/core/notification_types.py` |
| 26 | +- Database notification types: `backend/src/baserow/contrib/database/fields/notification_types.py` |
| 27 | +- Premium notification types: `premium/backend/src/baserow_premium/row_comments/notification_types.py` |
| 28 | +- Enterprise notification types: `enterprise/backend/src/baserow_enterprise/data_scanner/notification_types.py` |
| 29 | +- Backend notification APIs: `backend/src/baserow/core/notifications/handler.py` |
| 30 | +- Backend notification base classes: `backend/src/baserow/core/notifications/registries.py` |
| 31 | +- Frontend base notification type: `web-frontend/modules/core/notificationTypes.js` |
| 32 | + |
| 33 | +## What A Complete Notification Usually Needs |
| 34 | + |
| 35 | +Most new notifications touch both sides: |
| 36 | + |
| 37 | +1. Backend `NotificationType` subclass and event hook. |
| 38 | +2. Backend registration in the relevant app `ready()` method. |
| 39 | +3. Frontend `NotificationType` class. |
| 40 | +4. Frontend content component used in the notification list. |
| 41 | +5. Frontend registration in the relevant `plugin.js`. |
| 42 | +6. Targeted backend tests, and frontend tests if the route or rendering logic is non-trivial. |
| 43 | + |
| 44 | +## Backend Pattern |
| 45 | + |
| 46 | +Follow the existing backend shape: |
| 47 | + |
| 48 | +1. Add a typed payload container, usually a dataclass, with the minimal stable fields needed by the UI and routing. |
| 49 | +2. Implement a `NotificationType` subclass. |
| 50 | +3. Add a helper like `create_notification`, `notify_*`, or `construct_notification`. |
| 51 | +4. Call `NotificationHandler.create_direct_notification_for_users(...)` for direct notifications. |
| 52 | +5. Use `NotificationHandler.construct_notification(...)` plus `UserNotificationsGrouper` when batching many notifications. |
| 53 | +6. Use `NotificationHandler.create_broadcast_notification(...)` only for true broadcast events. |
| 54 | +7. Register the notification type in the matching backend app. |
| 55 | + |
| 56 | +Common backend registration points: |
| 57 | + |
| 58 | +- `backend/src/baserow/core/apps.py` |
| 59 | +- `backend/src/baserow/contrib/database/apps.py` |
| 60 | +- `premium/backend/src/baserow_premium/apps.py` |
| 61 | +- `enterprise/backend/src/baserow_enterprise/apps.py` |
| 62 | + |
| 63 | +## Frontend Pattern |
| 64 | + |
| 65 | +If the notification must render inside the app, add the frontend type too: |
| 66 | + |
| 67 | +1. Create a frontend `NotificationType` subclass with the same `type` string. |
| 68 | +2. Return the appropriate icon component. |
| 69 | +3. Return a content component that renders the notification text. |
| 70 | +4. Implement `getRoute(notificationData)` when the notification should be clickable. |
| 71 | +5. Register the type in the relevant frontend `plugin.js`. |
| 72 | + |
| 73 | +Common frontend registration points: |
| 74 | + |
| 75 | +- `web-frontend/modules/core/plugin.js` |
| 76 | +- `web-frontend/modules/database/plugin.js` |
| 77 | +- `premium/web-frontend/modules/baserow_premium/plugin.js` |
| 78 | +- `enterprise/web-frontend/modules/baserow_enterprise/plugin.js` |
| 79 | + |
| 80 | +## Define The Target Clearly |
| 81 | + |
| 82 | +Every notification should have a clear target: what object or page the user should land on when they click it. |
| 83 | + |
| 84 | +Prefer storing stable identifiers in `notification.data`, not display-only values. Usually that means IDs plus enough names to render a readable message. |
| 85 | + |
| 86 | +Good target payload examples: |
| 87 | + |
| 88 | +- Row or field event: `database_id`, `table_id`, `row_id`, `field_id` |
| 89 | +- Comment event: `comment_id`, `table_id`, `row_id` |
| 90 | +- Workspace-scoped event: `workspace_id` or object IDs resolvable within the workspace |
| 91 | +- Admin or global event: IDs and query parameters needed for an admin route |
| 92 | + |
| 93 | +Use these rules: |
| 94 | + |
| 95 | +1. Include the smallest set of IDs required to reconstruct the target route. |
| 96 | +2. Include names only for display or email text. |
| 97 | +3. Keep the target stable even if labels change later. |
| 98 | +4. Use `workspace=None` only when the event is truly user-global or instance-global. |
| 99 | +5. If the backend email link should point to the same place, keep the backend and frontend route assumptions aligned. |
| 100 | + |
| 101 | +There are two target implementations to consider: |
| 102 | + |
| 103 | +1. Backend `get_web_frontend_url(...)` |
| 104 | + Use this when the notification is emailed and should link into the app. |
| 105 | + `EmailNotificationTypeMixin` already provides the default `/notification/<workspace_id>/<notification_id>` route when `has_web_frontend_route = True`. |
| 106 | + Override it only when the target route cannot be expressed through that default flow. |
| 107 | +2. Frontend `getRoute(notificationData)` |
| 108 | + Return the real in-app route object based on the IDs stored in `notification.data`. |
| 109 | + |
| 110 | +If the notification redirects through the generic notification route, verify the frontend route can still resolve the final location from the stored data. |
| 111 | + |
| 112 | +## Prevent Duplicate Notifications |
| 113 | + |
| 114 | +Do not blindly create a new notification every time a signal fires. First decide whether repeated events should: |
| 115 | + |
| 116 | +1. Create a new notification every time. |
| 117 | +2. Reuse one existing unread notification for the same object. |
| 118 | +3. Suppress re-creation while a tracking record still exists. |
| 119 | +4. Mark an existing notification as read instead of creating a new one. |
| 120 | + |
| 121 | +The repo uses several duplicate-prevention patterns already: |
| 122 | + |
| 123 | +### Pattern 1: Query for an existing active notification |
| 124 | + |
| 125 | +Use this when the event has a natural unique object, such as an invitation, comment, sync, or scan. |
| 126 | + |
| 127 | +Typical lookup shape: |
| 128 | + |
| 129 | +```python |
| 130 | +NotificationHandler.get_notification_by( |
| 131 | + user, |
| 132 | + notificationrecipient__read=False, |
| 133 | + data__contains={"some_object_id": obj.id}, |
| 134 | +) |
| 135 | +``` |
| 136 | + |
| 137 | +Use this when you want at most one active notification per recipient and per object. If one already exists, do not create another. Depending on the behavior, you can: |
| 138 | + |
| 139 | +- return early |
| 140 | +- update the existing notification data |
| 141 | +- mark the existing notification as read as part of a follow-up action |
| 142 | + |
| 143 | +Choose `data` keys that uniquely identify the event target. If multiple objects can share the same notification type, the dedupe key must include all IDs needed to distinguish them. |
| 144 | + |
| 145 | +### Pattern 2: Persist or reuse an event-tracking row |
| 146 | + |
| 147 | +Use this when the same source content may be removed and re-added quickly, and a raw notification query is not enough. |
| 148 | + |
| 149 | +The rich text mention flow uses `RichTextFieldMention` rows to track mention existence and avoid duplicate notifications when content is briefly undone and redone. Follow that approach when the event source has lifecycle state that should outlive a single signal call. |
| 150 | + |
| 151 | +### Pattern 3: Group creation before writing recipients |
| 152 | + |
| 153 | +Use `UserNotificationsGrouper` when one operation can generate many notifications across many users. This reduces fan-out overhead and avoids ad hoc per-user creation loops. |
| 154 | + |
| 155 | +### Pattern 4: Update or mark read instead of inserting |
| 156 | + |
| 157 | +If the event resolves a prior notification, prefer updating state over inserting another notification. For example, invitation follow-up flows mark the original invitation notification as read. |
| 158 | + |
| 159 | +## Choosing A Dedupe Key |
| 160 | + |
| 161 | +A dedupe key is usually an implicit tuple made from: |
| 162 | + |
| 163 | +1. notification `type` |
| 164 | +2. recipient user |
| 165 | +3. active state, usually unread and uncleared |
| 166 | +4. one or more stable object IDs stored in `data` |
| 167 | + |
| 168 | +Examples: |
| 169 | + |
| 170 | +- One notification per invitation per user: |
| 171 | + `type + recipient + data.invitation_id` |
| 172 | +- One notification per row comment mention per user: |
| 173 | + `type + recipient + data.comment_id` |
| 174 | +- One notification per row-field mention per user: |
| 175 | + `type + recipient + data.field_id + data.row_id` |
| 176 | + |
| 177 | +Do not dedupe on mutable names or message text. |
| 178 | + |
| 179 | +## Implementation Checklist |
| 180 | + |
| 181 | +When adding a new notification, verify all of these: |
| 182 | + |
| 183 | +1. The `type` string is unique and stable. |
| 184 | +2. The payload contains stable target IDs. |
| 185 | +3. The workspace is correct for permission-scoped listing. |
| 186 | +4. The sender is correct, or `None` if there is no meaningful sender. |
| 187 | +5. Duplicate creation behavior is explicit. |
| 188 | +6. Backend registration is present. |
| 189 | +7. Frontend registration is present if the notification appears in-app. |
| 190 | +8. The route works for the intended target. |
| 191 | +9. Tests cover both creation and duplicate prevention behavior. |
| 192 | + |
| 193 | +## Testing Expectations |
| 194 | + |
| 195 | +Add the narrowest backend tests that prove: |
| 196 | + |
| 197 | +1. The right recipients are selected. |
| 198 | +2. The notification payload contains the target IDs. |
| 199 | +3. The notification is workspace-scoped correctly. |
| 200 | +4. A duplicate event does not create an extra active notification when dedupe is required. |
| 201 | +5. The route-related data needed by the frontend is present. |
| 202 | + |
| 203 | +Useful existing tests: |
| 204 | + |
| 205 | +- `premium/backend/tests/baserow_premium_tests/row_comments/test_row_comments_notification_types.py` |
| 206 | +- `enterprise/backend/tests/baserow_enterprise_tests/data_scanner/test_data_scanner_notification_types.py` |
| 207 | + |
| 208 | +If you add custom frontend routing or rendering logic, add or update a focused frontend unit test near the notification type or component. |
| 209 | + |
| 210 | +## Search Patterns |
| 211 | + |
| 212 | +Use these searches to move quickly: |
| 213 | + |
| 214 | +- `rg -n "class .*NotificationType" backend/src premium/backend/src enterprise/backend/src` |
| 215 | +- `rg -n "notification_type_registry.register" backend/src premium/backend/src enterprise/backend/src` |
| 216 | +- `rg -n "new .*NotificationType\\(context\\)" web-frontend premium/web-frontend enterprise/web-frontend` |
| 217 | +- `rg -n "NotificationHandler\\.create_direct_notification_for_users|UserNotificationsGrouper|create_broadcast_notification" backend/src premium/backend/src enterprise/backend/src` |
| 218 | +- `rg -n "data__contains=.*_id|get_notification_by\\(" backend/src premium/backend/src enterprise/backend/src` |
| 219 | + |
| 220 | +## Guardrails |
| 221 | + |
| 222 | +- Do not create a new notification type without checking whether an existing one should be reused or updated. |
| 223 | +- Do not store only display text if the notification needs to link back to an object. |
| 224 | +- Do not dedupe on mutable fields like names or messages. |
| 225 | +- Do not use broadcasts for ordinary per-user events. |
| 226 | +- Do not skip frontend registration when the notification must render in-app. |
| 227 | +- Do not create duplicate unread notifications for the same object unless that is explicitly the desired product behavior. |
0 commit comments