Skip to content

feat: Digest mail notifications settings drawer - EXO-89484 - eXIP7.3.0.22 - #6027

Merged
azayati merged 7 commits into
feature/experiencefrom
exip-7.3.0.22-us03-digest-drawer
Sep 1, 2026
Merged

feat: Digest mail notifications settings drawer - EXO-89484 - eXIP7.3.0.22#6027
azayati merged 7 commits into
feature/experiencefrom
exip-7.3.0.22-us03-digest-drawer

Conversation

@azayati

@azayati azayati commented Aug 28, 2026

Copy link
Copy Markdown
Member

Part of eXIP 7.3.0.22 — Digest Mail Notifications. Task EXO-89484 (US03).

Social half of US03. Depends on Meeds-io/commons#772merge that one first, this PR compiles against the DigestCategoryProvider SPI it introduces.

What the user gets

The digest entry added in US02 now opens a drawer with the two frequencies. Switching one on proposes every category the user can receive; he unchecks what he doesn't want. Both frequencies carry their own independent list, and the same category block is shown under each, per the design.

An enabled frequency with no category is refused by the drawer (Apply disabled) as well as by the server — an empty digest is never worth an email.

Server side

DigestRest returns the administrator switch, the categories and the user's own choices in a single call, and saves those choices. When the administrator has not allowed the digest, the payload carries nothing else and the PATCH is refused with a 403.

Category labels are translated on the server. The settings page loads only two resource bundles, so an addon label would simply never resolve in the browser. DigestCategoryLabelResolver reads the label from the bundle the category's own notification plugins already declare — which is exactly how the notification groups are displayed today, and means an addon has nothing new to declare beyond its category provider.

The language is taken from the request. The social webapp does not declare LocalizationFilter, so the portal locale of the thread is empty here — unlike the /rest webapp, where the JAX-RS notification settings service can rely on it. request.getLocale() is what every other Spring controller in this module uses, and a unit test now pins a non-English label.

DigestTimeZoneResolver reads the user.timeZone profile attribute and hands it to the digest. Commons owns the digest but cannot read a user profile, so the timezone is resolved here. Note this is the organization profile attribute, not social's Profile.TIME_ZONE, which nothing writes outside the CSV import.

Worth knowing for the review: user.timeZone is today written by the agenda addon alone, on every page load. Without agenda nothing fills it and the digest falls back to server time. That is recorded in the javadoc, and moving the synchronization into the platform is planned with the sending itself (US08).

Categories

Social contributes Spaces and Feed. The four others — News, Tasks, Agenda, Process — arrive with their own addon PRs, and the drawer shows only what is installed.

Also in here

The digest entry now opens the drawer through a ref, like the muted-spaces one, instead of the root event US02 left as a placeholder. That closes finding G from the US02 review.

Checks

  • Backend compiles; DigestCategoryLabelResolverTest green (4 tests, covering the translated label, the English fallback and the two id fallbacks); frontend bundles with webpack; ESLint reports 0 errors on the touched app (the 5 remaining warnings are pre-existing in files this PR does not change).

AI contribution

Classified N1 — human-driven, no auto-merge, author ≠ approver.

@azayati

azayati commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Update — the two category providers became configuration.

The categories SPI moved to a Kernel registry in commons (see Meeds-io/commons#772, last comment): each webapp has its own Spring context, so a Spring-bean SPI could never be implemented by every addon — processes holds no Spring at all. Spaces and Feed are now declared in notification-plugins-configuration.xml, next to the notification plugins they cover, and the two Java provider classes are gone.

The four addon categories are open as pure-configuration PRs: Meeds-io/content#897, Meeds-io/task#640, exoplatform/agenda#1070, exoplatform/processes#491 — all blocked on commons#772 merging first.

@azayati

azayati commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

Third commit added — the platform timezone synchronization (tech spec §4 Timezone).

Implemented exactly as ruled in the spec:

  • Profile.USER_TIME_ZONE — the new constant naming the user.timeZone profile attribute, the source of truth (the old Profile.TIME_ZONE is only removed by the §5 cleanup, later).
  • UserTimeZoneService.saveUserTimeZone(username, zoneId) — the new service method the spec names: validates the zone, saves the attribute, then broadcasts social.timeZone.saved. The digest listener refreshing TIMEZONE in NTF_DIGEST_USERS (US05+) subscribes to that event — a ProfileListenerPlugin never fires for this attribute (rejected alternative 7).
  • POST /social/rest/timezone — the small endpoint the spec asks for.
  • The page head publishes eXo.env.portal.userTimezone and the userTimeZoneSync script (baseGRP, every page) refreshes the platform when the browser disagrees — the same mechanism agenda carried, now platform-owned so the digest works without agenda.
  • The digest reads the timezone through this service; DigestTimeZoneResolver disappears (it existed only in this PR).

7 new unit tests on the service (round-trip, first-page-load null, profile creation, unknown-zone refusal, event broadcast, broadcast-failure isolation).

The agenda delegation (deprecate + delegate + remove the duplicated page scripts) is in exoplatform/agenda#1070, which now depends on this PR.

@exo-swf
exo-swf force-pushed the feature/experience branch 2 times, most recently from d71a1ea to 9759b16 Compare August 29, 2026 23:17
@azayati

azayati commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

Timezone review round — 4 findings, all fixed in 3884ad9eed.

  1. 🔴 The sync never ran. userTimeZoneSync was declared in gatein-resources but an AMD module nobody requires never executes — agenda's old script only ran because its name (agendaBaseExtension) rode the includeExtensions('BaseExtension') suffix convention. With agenda's copy removed, no browser would ever have POSTed a timezone again, silently. The head template now requires SHARED/userTimeZoneSync explicitly for logged-in users (same pattern as UISocialBottomContainer.gtmpl).
  2. 🟠 Unescaped legacy value in the head script block. The old agenda endpoint saved raw POST bodies with no validation, so a pre-upgrade database can hold a value with a quote that would break the entire socialHeadScripts block. The gtmpl now validates the stored value with ZoneId.of and falls back to null.
  3. 🟠 (agenda side) the deprecated endpoint returned 500 for an unknown zone through the delegation — mapped to 400 in exoplatform/agenda 6ec7c0cd.
  4. 🟢 The gtmpl hardcoded "user.timeZone" next to the commit introducing Profile.USER_TIME_ZONE — now uses the constant.

Cleared by the same review: the kernel bridge lookup agenda relies on (adapter keyed on the concrete @service class, lazily resolved from social's Spring context, unreachable fallback in production), the broadcast(String,S,D) signature, the scan-root coverage of io.meeds.social.timezone, the text/plain @RequestBody shape, webpack.watch.js, and the deleted webui-configuration.xml content.

@azayati
azayati force-pushed the exip-7.3.0.22-us03-digest-drawer branch from 3884ad9 to 2858612 Compare August 31, 2026 14:25
@azayati
azayati marked this pull request as draft August 31, 2026 14:51
@azayati
azayati marked this pull request as ready for review August 31, 2026 14:51
@azayati
azayati requested review from mkrout and removed request for mkrout August 31, 2026 14:52
@exo-swf
exo-swf force-pushed the feature/experience branch from 5f9e726 to 906075d Compare August 31, 2026 23:17
azayati and others added 7 commits September 1, 2026 08:56
….0.22

Lets a user choose when he wants his digest and what it talks about.

The entry added to the notification settings now opens a drawer holding the
two frequencies, each with the list of the categories the installed addons
offer. A frequency enabled for the first time proposes everything the user can
receive and he unchecks what he doesn't want, an enabled frequency with no
category being refused by the drawer as well as by the server.

- DigestRest returns the administrator switch, the categories and the choices
  of the user in a single call, and saves those choices.
- The categories are translated on the server, in the bundle the notification
  plugins of the category already use: the settings page only loads the
  resource bundles of the platform, so an addon label would never be found in
  the browser. This is how the notification groups are already displayed.
  The language is the one of the request, since the social webapp doesn't fill
  the portal locale of the thread the way the /rest webapp does.
- DigestTimeZoneResolver reads the user profile timezone and hands it to the
  digest, which lives in commons and can't read a profile itself.
- Social contributes its two categories, Spaces and Feed. The four other ones
  come with their own addon.
- The entry now opens the drawer through a ref, like the muted spaces one,
  instead of going through a root event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…- eXIP7.3.0.22

Follows the digest categories moving to a Kernel registry: Spaces and Feed are
now declared in the notification configuration of social, next to the
notification plugins they cover, and their two Java classes disappear.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…89484 - eXIP7.3.0.22

The timezone of each user, the one his browser lives in, used to be kept up to
date by the agenda addon alone: without agenda, nothing fills it and every
digest goes out on the server hour. As decided in the tech spec, the platform
now owns the synchronization, and agenda delegates its own copy.

- Profile.USER_TIME_ZONE: the new constant naming the profile attribute, the
  source of truth.
- UserTimeZoneService.saveUserTimeZone(username, zoneId) validates the
  timezone, saves it and broadcasts an event: whatever keeps a copy of the
  timezone, like the digest work list, refreshes it on that event, a profile
  listener never firing for this attribute.
- POST /social/rest/timezone: the endpoint the browser calls.
- The page head publishes eXo.env.portal.userTimezone and the userTimeZoneSync
  script, loaded on every page, refreshes the platform when the browser
  disagrees, exactly the way the agenda script did.
- The digest reads the timezone through this service, its own resolver
  disappears.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-89484 - eXIP7.3.0.22

Review fixes on the timezone synchronization:

- The userTimeZoneSync module was declared but never required, and a module
  nobody requires never runs: the agenda script it replaces only ran because
  its name rode the BaseExtension suffix convention. The head template now
  requires it explicitly for logged in users.
- The timezone read from the profile is validated before being printed in the
  head script block: the legacy agenda endpoint saved raw bodies without any
  validation, and a poisoned stored value would have broken the whole block.
- The head template uses the Profile.USER_TIME_ZONE constant it introduced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ies - EXO-89484 - eXIP7.3.0.22

The category declarations covered every notification of their group, but the
functional spec lists exactly which notifications the digest considers: only
those 12 plugins are declared, nothing else enters a digest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…0.22

webpack.dev.js carries each developer's own server location and must never be
committed, it was swept in by mistake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…9484 - eXIP7.3.0.22

The core module only runs the tests a suite declares: a test class left out is
silently never executed by the build. The two new unit test classes join
NoContainerTestSuite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azayati
azayati force-pushed the exip-7.3.0.22-us03-digest-drawer branch from 82a891f to 3c01199 Compare September 1, 2026 07:57
@azayati
azayati merged commit 20183b3 into feature/experience Sep 1, 2026
1 of 3 checks passed
@azayati
azayati deleted the exip-7.3.0.22-us03-digest-drawer branch September 1, 2026 10:12
azayati added a commit that referenced this pull request Sep 1, 2026
azayati added a commit that referenced this pull request Sep 1, 2026
azayati added a commit that referenced this pull request Sep 2, 2026
azayati added a commit that referenced this pull request Sep 3, 2026
azayati added a commit that referenced this pull request Sep 8, 2026
azayati added a commit that referenced this pull request Sep 10, 2026
azayati added a commit that referenced this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant