Repository navigation
feat(notification): support $current_user placeholder in rule conditions - #277
Conversation
Notification rule conditions can now keep the "$current_user" placeholder instead of the frontend replacing it with the user's entity id when the config is created. The backend resolves it once when loading a config into the NotificationConfigCache, from the Keycloak user attribute "exact_username" (with the legacy "User:" prefix fallback). - new generic common/placeholder package: PlaceholderResolver replaces placeholder values in any JSON tree; PlaceholderValueProvider looks up values per user account (Keycloak, or none without Keycloak admin access) - a rule whose placeholder has no value for its user is skipped with a warning, without affecting the user's other rules - a temporary Keycloak failure fails the cache load (retried with the next change); a single config refresh keeps the previously cached version - the notification config cache is reloaded once a day (notification.config-cache.ttl-millis) so changed user links are applied Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
- allow @ConditionalOnKeycloakAdminAccess on @bean methods - wrap long method chains and arguments in tests as ktlint requires - give the placeholder value copy an explicit type Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
|
tested this on a full local stack, and seems to working as expected, wasn't able to break anything
Both are minor improvements and can be addressed separately. |
…led rules - LazySnapshot can now serve the previous copy when a reload fails (retryAfterFailedReload), instead of throwing to every reader - the notification config cache uses it: if the daily reload fails (e.g. Keycloak unreachable), notifications keep working with the previous configs and the reload is tried again after 5 minutes - disabled rules are not cached, so their placeholders are not looked up and do not log warnings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
|
Thanks for testing! I addressed both suggestions in this PR (b5f1367):
Both have new tests in Generated by Claude Code |
…ak service Keycloak no longer knows about placeholders: - common/keycloak: new UserProfileProvider (KeycloakUserProfileProvider) returns a user's profile, including the entity linked to the account - common/placeholder: UserPlaceholderValueProvider maps the profile to placeholder values; without a UserProfileProvider (no Keycloak admin access) it has no values, replacing UnavailablePlaceholderValueProvider Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
- the email notification handler gets the address from UserProfileProvider; UserEmailProvider, KeycloakUserEmailProvider and the Spring email cache are removed. KeycloakUserProfileProvider caches profiles for 5 minutes instead (failed lookups are not cached). - a failed email address lookup (e.g. Keycloak unreachable) is now a transient failure that the outbox retries, instead of silently skipping the email - PlaceholderValueProvider is a single concrete class that works out all placeholders, replacing the interface and UserPlaceholderValueProvider Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
PlaceholderResolver.resolve(node, userIdentifier) now finds the placeholders, looks up their values (only if any are used) and replaces them, merging PlaceholderValueProvider into it. The notification config cache only wraps its existing condition parsing with that one call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
…solved A failed user profile lookup (e.g. Keycloak unreachable) no longer fails the whole cache load or keeps a stale config on refresh: like a missing value, it is logged as a warning and only the affected rule is skipped. Both are PlaceholderResolutionException, so the cache catches them in one place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL
Backend part of Aam-Digital/ndb-core#4545: notification rule conditions can keep the
"$current_user"placeholder (e.g.{"assignedTo": {"$elemMatch": "$current_user"}}). The backend resolves it, so the frontend no longer needs to replace it with the user's entity ID when it creates aNotificationConfigfrom the template.Changes
New generic
common/placeholderpackage (not tied to conditions, notifications or Keycloak)Placeholders: the placeholder names, shared with the frontend'sPLACEHOLDERSenum. For now this is only$current_user.PlaceholderResolver.resolve(json, userIdentifier): one call that replaces all placeholders in any JSON tree with their values for that user.$current_useris the entity linked to the account, taken from theUserProfileProvider.$…strings.PlaceholderResolutionExceptioninstead of putting innull(in a query,{"field": null}also matches documents that don't have the field). There are two cases:UnresolvedPlaceholderException: the placeholder has no value for this user, e.g. no entity is linked to the account.PlaceholderValueLookupException: the lookup itself failed, e.g. Keycloak is unreachable.UserProfileProvider(no Keycloak admin access) there are no values, and it logs a warning the first time a placeholder is actually used.common/keycloak: generic user profile lookupUserProfileProvider/KeycloakUserProfileProviderreturn a user's profile (email and linked entity). It knows nothing about placeholders, and profiles are cached for 5 minutes (failed lookups are not cached).exact_username. A value without an entity type gets the legacyUser:prefix, as in the frontend.@ConditionalOnKeycloakAdminAccesscan now also be put on@Beanmethods.Notification emails use the same lookup
EmailCreateNotificationHandlergets the address fromUserProfileProvider.UserEmailProvider,KeycloakUserEmailProviderand the Springuser-emailscache are removed.TransientNotificationException, so the outbox retries it. Before, the email was silently skipped.Notification config cache
documentConditionEngine.parse(placeholderResolver.resolve(conditions, user)). This happens once, when a config is loaded or refreshed, not on every change.notification.config-cache.ttl-millis, default86400000), so a changed user–entity link takes effect without a restart.retryAfterFailedReloadoption onLazySnapshot.Docs: the notification module README and
docs/modules/notification.md.Not included
$now, which isn't needed for notification conditions yet./actuator/featuresflag: frontend and backend versions are matched during rollout.$current_userinnotification-settings.component.ts. Configs saved earlier with concrete entity IDs keep working unchanged.Testing
PlaceholderResolver,KeycloakUserProfileProvider(including its cache),LazySnapshot, the email handler's transient lookup failure, and the bean wiring (PlaceholderConfigurationTest,NotificationEmailHandlerWiringTest).DefaultNotificationConfigCacheTest:🤖 Generated with Claude Code
https://claude.ai/code/session_01Fb2hmoP1JTL1ea9sPYBsYL