Merge pull request 'docs(profile): sync inbox unread badge + inboxPollSeconds' (#22) from docs/inbox-unread-badge into develop
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Waiting to run
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Waiting to run
This commit was merged in pull request #22.
This commit is contained in:
@@ -12,6 +12,7 @@ How the Profile **Inbox** is built and how data flows. The feature spans three s
|
||||
| Concern | Service | Where |
|
||||
|---|---|---|
|
||||
| UI (view/compose/tabs) | `hiveops-profile` | `frontend/src/components/Profile/Inbox.svelte` |
|
||||
| Sidebar unread badge + poll | `hiveops-profile` | `App.svelte` + `lib/stores.ts` (`unreadCount`, `refreshUnread()`); interval from `inboxPollSeconds`, an **auth**-owned pref — see [profile.notifications] |
|
||||
| Message CRUD + inbox/sent/read/delete | `hiveops-messaging` (8086, `hiveiq_messaging`) | `MessageController` → `MessageService` |
|
||||
| Recipient picker (`GET /auth/api/admin/users`) | `hiveops-auth` | `UserAdminController` |
|
||||
| Institution dropdown (`GET /api/v1/msp/institutions`) | `hiveops-mgmt` | `MspController` |
|
||||
|
||||
@@ -20,7 +20,7 @@ Terse agent reference. Owner service: **`hiveops-messaging`** (port 8086, DB `hi
|
||||
| GET | `/api/messaging/messages?page&size&sort` | paged inbox (Page<MessageResponse>) |
|
||||
| GET | `/api/messaging/messages/sent` | sent list (sender_user_id = you) |
|
||||
| GET | `/api/messaging/messages/unread` | unread list |
|
||||
| GET | `/api/messaging/messages/unread/count` | `{unreadCount}`; cached per userId |
|
||||
| GET | `/api/messaging/messages/unread/count` | `{unreadCount}`; cached per userId. Drives the sidebar Inbox badge (see below) |
|
||||
| POST | `/api/messaging/messages` | send; body `SendMessageRequest`; 201 |
|
||||
| PATCH | `/api/messaging/messages/{id}/read` | mark one read; 204 |
|
||||
| PATCH | `/api/messaging/messages/read-all` | mark all read; 204 |
|
||||
@@ -56,6 +56,15 @@ Terse agent reference. Owner service: **`hiveops-messaging`** (port 8086, DB `hi
|
||||
- unread count is `@Cacheable("unreadCount", key=userId)`, evicted on read/read-all/delete.
|
||||
- `loadRecipients()` and `loadInstitutions()` fail silently in the SPA (non-fatal catch).
|
||||
|
||||
## Sidebar unread badge (added 2026-07-16)
|
||||
- Frontend-only feature over the existing count endpoint; **no backend change in messaging**.
|
||||
- `lib/stores.ts` owns `unreadCount` + `refreshUnread()`. `refreshUnread()` swallows errors on purpose (it runs on a timer; a toast per failed poll is worse than a stale badge), so a broken count fails **silent**, not loud.
|
||||
- `App.svelte` renders the badge on the Inbox nav item (capped `99+`) and arms the poll from `$userPreferences.inboxPollSeconds` — reactively, so a pref change re-arms without reload. `0` = off. That field is owned by **hiveops-auth**, not messaging (see [profile.notifications]).
|
||||
- `Inbox.svelte` calls `refreshUnread()` after markRead / markAllRead / delete / auto-refresh so the badge clears immediately.
|
||||
- Badge counts **unread only**, never total. Two read messages = no badge; that is correct, not a bug.
|
||||
- Badge colour is `#ef4444`, deliberately NOT the `#2563eb` of Inbox's own `.tab-badge`: the nav sits on the sidebar gradient (`#081651` → `#1c49b8`) where blue-on-blue disappears.
|
||||
- The nav badge re-calls the endpoint rather than reusing Inbox's local `messages.filter(m => !m.read).length`, which is page-local (size 50) and would undercount past page 0. **Do NOT "simplify" it to use the local tally.**
|
||||
|
||||
## Do NOT
|
||||
- Do NOT create message endpoints/tables outside `hiveops-messaging`.
|
||||
- Do NOT hard-delete from `messages` to "remove" a user's message — use per-user soft-delete.
|
||||
|
||||
@@ -50,6 +50,7 @@ hiveops-auth
|
||||
| `notifyOnStatusChange` | boolean | `false` |
|
||||
| `notificationQuietStart` | String `HH:MM` \| null | `null` |
|
||||
| `notificationQuietEnd` | String `HH:MM` \| null | `null` |
|
||||
| `inboxPollSeconds` | int (0–3600) | `60` |
|
||||
|
||||
The same document also carries display/appearance/security prefs (itemsPerPage, dateFormat, timeFormat, timezone, themeMode, tableDensity, sessionTimeoutMinutes) — one blob, one column, shared across all Profile tabs.
|
||||
|
||||
@@ -64,7 +65,8 @@ The same document also carries display/appearance/security prefs (itemsPerPage,
|
||||
`updatePreferences` applies each incoming field only when non-null (`if (req.getX() != null) prefs.setX(...)`), so partial PUTs are safe — the Notifications tab sends only its own fields and leaves display/appearance/security untouched. **Consequence:** a `null` for a quiet-hours field is a no-op, so quiet hours cannot be cleared through this endpoint once set (see Internal tab).
|
||||
|
||||
## What is NOT here (by design / by gap)
|
||||
- No producer to any notification/delivery topic; no consumer reads these flags. Delivery wiring (email/push, severity gating, quiet-hours enforcement) does not exist in the codebase yet — the screen is a stored-preference surface awaiting a consumer.
|
||||
- No producer to any notification/delivery topic; no consumer reads the **email/push/severity/quiet-hours** flags. Delivery wiring (email/push, severity gating, quiet-hours enforcement) does not exist in the codebase yet — for those flags the screen is a stored-preference surface awaiting a consumer.
|
||||
- **`inboxPollSeconds` (added 2026-07-16) is the one field here with a live consumer.** `hiveops-profile` `App.svelte` reads it reactively to arm the poll timer behind the Inbox unread badge — see [profile.inbox]. Still no Kafka: the consumer is the SPA itself, over the same synchronous prefs read.
|
||||
- No admin/cross-user editing path; identity is pinned to the JWT principal.
|
||||
|
||||
Cross-links: [platform.data-architecture] · [platform.kafka] · [platform.service-ownership]
|
||||
|
||||
@@ -28,16 +28,19 @@ Terse agent reference. Act from this; do not infer beyond it.
|
||||
## Storage (exact)
|
||||
- Table **`users`**, column **`preferences`** — `JSONB NOT NULL DEFAULT '{}'` (Flyway `V9__add_user_preferences.sql`).
|
||||
- Hibernate: `@JdbcTypeCode(SqlTypes.JSON)` on `User.preferences` → POJO `UserPreferences` (not an entity).
|
||||
- Notification JSON keys: `emailNotificationsEnabled`, `pushNotificationsEnabled`, `notifyOnCritical`, `notifyOnHigh`, `notifyOnAssignment`, `notifyOnStatusChange`, `notificationQuietStart`, `notificationQuietEnd`.
|
||||
- Defaults: email/push/critical/high = `true`; assignment/statusChange = `false`; quiet start/end = `null`.
|
||||
- Notification JSON keys: `emailNotificationsEnabled`, `pushNotificationsEnabled`, `notifyOnCritical`, `notifyOnHigh`, `notifyOnAssignment`, `notifyOnStatusChange`, `notificationQuietStart`, `notificationQuietEnd`, `inboxPollSeconds`.
|
||||
- Defaults: email/push/critical/high = `true`; assignment/statusChange = `false`; quiet start/end = `null`; `inboxPollSeconds` = `60`.
|
||||
- `inboxPollSeconds` — how often the profile SPA re-fetches the Inbox unread count for the nav badge. `0` = polling off (badge refreshes on page load only). Added 2026-07-16; **no migration** (absent key reads as the POJO default, so existing rows are 60).
|
||||
|
||||
## Validation (PUT body → `UpdatePreferencesRequest`)
|
||||
- `notificationQuietStart` / `notificationQuietEnd`: regex `^([01]\d|2[0-3]):[0-5]\d$` (24h `HH:MM`) or omit.
|
||||
- `inboxPollSeconds`: `@Min(0) @Max(3600)`. Out of range = HTTP 400.
|
||||
- (Non-notification, same doc) `sessionTimeoutMinutes` 5–480; `itemsPerPage` 1–500.
|
||||
|
||||
## Gotchas
|
||||
- Merge is null-skip: PUT `notificationQuietStart: null` does **not** clear it — old value persists. To clear, edit DB: `UPDATE users SET preferences = preferences - 'notificationQuietStart' - 'notificationQuietEnd' WHERE email=...`.
|
||||
- Flags are **stored only** — no consumer anywhere sends email/push, gates on severity, or enforces quiet hours. Changing toggles has no delivery effect currently.
|
||||
- The **email/push/severity/quiet-hours flags are stored only** — no consumer anywhere sends email/push, gates on severity, or enforces quiet hours. Changing those toggles has no delivery effect currently.
|
||||
- **`inboxPollSeconds` is the exception: it has a real consumer.** `hiveops-profile` `App.svelte` reads it reactively to arm the unread-count poll timer. It is the only setting on this tab that does something today. Do not lump it in with the stored-only flags.
|
||||
- Empty/`{}` `preferences` column reads as all-defaults, not an error.
|
||||
- DB is on VM `10.10.10.188` (ProxyJump via services VM), not the services host.
|
||||
- CORS: `profile.bcos.cloud` must be in `hiveiq-auth` `CORS_ALLOWED_ORIGINS` (and mgmt).
|
||||
@@ -45,6 +48,6 @@ Terse agent reference. Act from this; do not infer beyond it.
|
||||
## Do NOT
|
||||
- Do NOT look for a `hiveops-profile` backend, a `preferences` table, or a Kafka topic — none exist.
|
||||
- Do NOT claim admin/`MSP_ADMIN` can edit another user's notification prefs here.
|
||||
- Do NOT tell a user these toggles change alert delivery — no pipeline consumes them yet.
|
||||
- Do NOT tell a user the email/push/severity/quiet-hours toggles change alert delivery — no pipeline consumes them yet. (`inboxPollSeconds` DOES take effect.)
|
||||
- Do NOT send `null` to clear quiet hours and expect it to clear (server skips nulls).
|
||||
- Do NOT route these calls to incident/messaging/devices — the Notifications tab uses only `hiveops-auth`.
|
||||
|
||||
@@ -30,10 +30,12 @@ Ops/support view of the **Notifications** preference screen. This is a **self-se
|
||||
## Common failure modes + fixes
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---|---|---|
|
||||
| PUT returns 400 on save | Quiet start/end not `HH:MM` 24h | Enter a valid time or clear (see caveat) |
|
||||
| PUT returns 400 on save | Quiet start/end not `HH:MM` 24h, or `inboxPollSeconds` outside 0–3600 | Enter a valid time / interval |
|
||||
| Save 401 | Stale JWT | Re-login on `profile.bcos.cloud` |
|
||||
| Save 403 / CORS error | `profile.bcos.cloud` not in `CORS_ALLOWED_ORIGINS` | Add origin to `hiveiq-auth` env, restart |
|
||||
| Toggles save, no alerts arrive | No delivery pipeline reads these flags | Product gap — set expectations, do not "fix" in this screen |
|
||||
| Toggles save, no alerts arrive | No delivery pipeline reads the email/push/severity/quiet-hours flags | Product gap — set expectations, do not "fix" in this screen. (Does **not** apply to **Check for new messages**, which works.) |
|
||||
| Inbox badge never updates | **Check for new messages** set to **Off** | By design: `Off` refreshes the badge on page load only. Pick an interval |
|
||||
| Inbox badge stale but not Off | `GET /messaging/messages/unread/count` failing | The SPA swallows poll errors on purpose (no toast per failed poll). Check `hiveiq-messaging` health and the browser console |
|
||||
| Quiet hours won't clear | Service skips null fields on merge | Known bug — DB edit or backend fix needed |
|
||||
|
||||
## Direct DB inspection (hiveiq_auth, on database VM 10.10.10.188 via ProxyJump)
|
||||
|
||||
@@ -21,6 +21,14 @@ Decide which events are worth notifying you about:
|
||||
- **Incident assignments** — get notified when an incident is assigned to you.
|
||||
- **Status changes** — get notified when an incident's status is updated.
|
||||
|
||||
## 📬 Check for New Messages
|
||||
Choose how often HiveIQ looks for new messages while you have it open. The unread count appears as a red badge on the **Inbox** item in the sidebar.
|
||||
|
||||
1. Pick an interval: **Off**, every **30 seconds**, every **minute** (the default), every **5 minutes**, or every **15 minutes**.
|
||||
2. Choosing **Off** means the badge only updates when you reload the page.
|
||||
|
||||
> The badge counts **unread** messages only, not your total. Once you read a message the badge goes down straight away, and it disappears when nothing is unread.
|
||||
|
||||
## 🕐 Set Quiet Hours
|
||||
1. Set a **From** and **To** time to define a do-not-disturb window.
|
||||
2. During that window, no notifications will be sent.
|
||||
|
||||
@@ -23,13 +23,17 @@ No simulator devices are needed — this is a **personal, per-user settings page
|
||||
|
||||
| # | Do this | ✅ Pass if |
|
||||
|---|---------|-----------|
|
||||
| 1 | As `customer_a`, open **Profile → Notifications** | Page loads with three sections — **Channels**, **Alert severity**, **Quiet hours** — and **Save** is greyed out (nothing to save yet) |
|
||||
| 1 | As `customer_a`, open **Profile → Notifications** | Page loads with four sections — **Channels**, **Alert severity**, **Messages**, **Quiet hours** — and **Save** is greyed out (nothing to save yet) |
|
||||
| 2 | Toggle **Email notifications** off | An **Unsaved changes** badge appears at the top, a **Discard** button appears, and **Save** becomes clickable |
|
||||
| 3 | Click **Discard** | The toggle jumps back to its saved state; the **Unsaved changes** badge and **Discard** button disappear and **Save** is greyed again |
|
||||
| 4 | Toggle **Incident assignments** on, then click **Save** | A green **"Notification settings saved"** toast shows; badge disappears and **Save** greys out again |
|
||||
| 5 | Reload the page (F5) | Your change stuck — **Incident assignments** is still on |
|
||||
| 6 | Under **Quiet hours**, set **From** and **To** times, then **Save**, then reload | Toast confirms save and both times are still shown after the reload |
|
||||
| 7 | Log in as `msp_a`, open **Notifications** | You see the **same self-service screen** editing your own prefs — there is **no institution filter** and no way to pick or edit another user's notifications |
|
||||
| 8 | Log in as `bcos_a` (admin), open **Notifications** | Same screen as everyone else — **no extra admin controls** to edit other users' notification settings (admins only manage their own here) |
|
||||
| 7 | Under **Messages**, set **Check for new messages** to *Every 30 seconds*, **Save**, then reload | Toast confirms save and the dropdown still reads *Every 30 seconds* after the reload |
|
||||
| 8 | Set it to **Off** | A line appears warning the Inbox badge will only update when you reload the page |
|
||||
| 9 | Set it back to *Every minute* and **Save**. Have someone send you a message (or send one to yourself from **Inbox → Compose**) and wait about a minute **without** opening Inbox | A red count badge appears on the **Inbox** item in the sidebar |
|
||||
| 10 | Open **Inbox** and read that message | The sidebar badge drops by one straight away (it does not wait for the next check), and disappears once nothing is unread |
|
||||
| 11 | Log in as `msp_a`, open **Notifications** | You see the **same self-service screen** editing your own prefs — there is **no institution filter** and no way to pick or edit another user's notifications |
|
||||
| 12 | Log in as `bcos_a` (admin), open **Notifications** | Same screen as everyone else — **no extra admin controls** to edit other users' notification settings (admins only manage their own here) |
|
||||
|
||||
**Report a fail with:** the row #, which login you used, and what you actually saw.
|
||||
|
||||
Reference in New Issue
Block a user