feat: notify platform admins on user creation and deletion (resolves #3040)#3042
feat: notify platform admins on user creation and deletion (resolves #3040)#3042rvs1212 wants to merge 6 commits intoaccessibility-exchange:devfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #3042 +/- ##
============================================
+ Coverage 96.99% 97.04% +0.04%
- Complexity 2472 2505 +33
============================================
Files 379 389 +10
Lines 11287 11477 +190
============================================
+ Hits 10948 11138 +190
Misses 339 339 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
app/Observers/UserObserver.php
Outdated
| 'name' | ||
| ))->decryptValue($user->name); | ||
|
|
||
| $admins = User::whereAdministrator()->where('id', '!=', $user->id)->get(); |
There was a problem hiding this comment.
I don't think the extra where is needed as it appears that this is only run when the $user is not an administrator.
app/Observers/UserObserver.php
Outdated
| userName: $name, | ||
| userEmail: $email, | ||
| userContext: $user->context, |
There was a problem hiding this comment.
This will expose the users name and info outside of TAE's system. Is this information actually necessary?
There was a problem hiding this comment.
Modified the email notification content considering privacy
| if ($invitationable instanceof Organization || $invitationable instanceof RegulatedOrganization) { | ||
| $invitee = $this->retrieveUserByEmail($inviteeEmail); | ||
|
|
||
| if ($invitee) { |
There was a problem hiding this comment.
Is there a case where this is falsey? Depending on the case there may be something else that should be done.
There was a problem hiding this comment.
There won't be a case where $invitee can be falsey. The request validation confirms the authenticated user's email matches the invitation email, so the user will exist. Removed that.
|
|
||
| private function contextLabel(): string | ||
| { | ||
| return UserContext::labels()[$this->userContext] ?? $this->userContext; |
There was a problem hiding this comment.
Realistically there shouldn't be a case where the contextLabel doesn't return a value. I wonder if you want to return something like __('Unknown') or leave as null in this case and log an error.
| 'title' => __('New member joined organization'), | ||
| 'body' => __(':name has joined :organization as :role.', [ | ||
| 'name' => $this->memberName, | ||
| 'organization' => $this->organization->getTranslation('name', locale()), | ||
| 'role' => $this->memberRole, |
There was a problem hiding this comment.
This should follow a similar pattern as the other notifications. These strings are built up in the notification view component instead. This should just pass along the necessary parts needed by the view. Apart from consistency, I believe this will also mean that localization happens at the time the notification view is rendered, instead of when the notification is generated.
There was a problem hiding this comment.
Oke. Modified the approach.
| public string $memberRole, | ||
| public string $userContext, |
There was a problem hiding this comment.
It's likely you'll need to pass in the actually object instance instead of the string, so that you can pass it along to the notification view. This way it can do the localization at render time.
There was a problem hiding this comment.
sure, modified the approach
| <p> | ||
| @lang('mail.salutation'),<br> | ||
| {{ config('app.name') }} | ||
| </p> |
There was a problem hiding this comment.
I think this may already be included in another template.
platform/resources/views/vendor/notifications/email.blade.php
Lines 42 to 48 in bf76f37
jobara
left a comment
There was a problem hiding this comment.
I think it would be be helpful
| $this->body = __('A new user, :name, has registered as :context.', [ | ||
| 'name' => $notification->data['user_name'], | ||
| 'context' => UserContext::labels()[$notification->data['user_context']], | ||
| ]); |
There was a problem hiding this comment.
I don't think this is actually enough information, for example multiple users could have the same name. Also if you just pass along the email address, the user could change that at any time, so it also may not match. This can be probably be mostly mitigated by passing along the User and displaying the email as it is at render time. In an ideal world, you'd provide a link directly to the user, but there isn't a page for that yet, and is something more along the lines of #1802.
There was a problem hiding this comment.
Could also make the user name a link to their public page if one has been created/drafted.
NOTE: this isn't intended as a comment to address the previous comment above.
| Notification::send($admins, new NewUserRegistered( | ||
| userName: $name, | ||
| userContext: UserContext::from($user->context), | ||
| )); |
There was a problem hiding this comment.
I think this is actually too soon to send, as the user hasn't actually complete the registration process yet. It kind of depends on what you want to do with the information. On one hand it would be useful to know when a user is created, even if they aren't fully registered. However, if the point is to use that information to go into the manage accounts, the user may not show up yet.
You can replicate this by creating a new individual user, and monitoring the emails in mailpit. You'll get the email notification before the registration process completes. I think right after the password is created. If you look in manage accounts, the user won't be visible. They need to complete the other registration steps before they'll show up in the manage accounts screen.
|
|
||
| class OrganizationObserver | ||
| { | ||
| public function created(Organization $organization): void |
There was a problem hiding this comment.
This may be too soon, and/or there may be a bug in the manage accounts view. The issue is that the Org does appear in the manage accounts screen but it isn't searchable until after the org has drafted a public page.
The org "Org" is in the manage accounts list.
However, searching for "Org" says there are two results but doesn't render the one we're actually looking for.
After drafting a public page for the Org, it is now searchable.
| Notification::send($admins, new NewOrganizationRegistered( | ||
| creatorName: $creator->name, | ||
| organization: $regulatedOrganization, | ||
| userContext: UserContext::from($creator->context), | ||
| )); |
There was a problem hiding this comment.
Same notification class is used for both the Organizations and Regulated Organizations. Due to this and the nature of its implementation, it's harder to distinguish the actual type of organization created. The context is included in the app notification but there the title is still just organization. Maybe check with Erick to see if this is sufficient, but you might want to actually title these as Organization and Regulated Organization. If you do make this change, you could either created separate notification classes or you could include logic to distinguish the type based on the UserContext.
| <x-notification :notification="$notification"> | ||
| <x-slot name="title">{{ $title }}</x-slot> | ||
| <x-slot name="body">{{ $body }}</x-slot> | ||
| </x-notification> |
There was a problem hiding this comment.
For user types that can be managed, it would be helpful to add an actions slot with a link to the Manage Accounts page. This also applies to organization notifications.
| $this->body = __(':name has joined :organization as :role.', [ | ||
| 'name' => $notification->data['member_name'], | ||
| 'organization' => $organization->getTranslation('name', locale()), | ||
| 'role' => TeamRole::labels()[$notification->data['team_role']], | ||
| ]); |
There was a problem hiding this comment.
The organization name should be a link to the organization's public page.
| $organizationType = $notification->data['organization_type']; | ||
| $organization = $organizationType::find($notification->data['organization_id']); | ||
|
|
||
| $this->body = __(':name (:context) from :organization has deleted their account.', [ |
There was a problem hiding this comment.
I think in this case the context is actually referring to the organization and should be moved after the organization's name instead of the user's name.
There was a problem hiding this comment.
Also may want to make the organization's name a link to the organizations public page.
| Notification::send($admins, new NewMemberJoinedOrganization( | ||
| memberName: $invitee->name, | ||
| organization: $invitationable, | ||
| teamRole: TeamRole::from($role), | ||
| )); |
There was a problem hiding this comment.
The NewMemeberJoinedOrganization is used for both organizations and regulated organizations but doesn't distinguish between them in the notification. As mentioned in another comment for new orgs, you should consider making this distinction to keep things clear on the system. Could either be separate classes or logic in the same class that distinguishes the two.
| 'organization' => $organization->getTranslation('name', locale()), | ||
| ]); | ||
| } else { | ||
| $this->body = __(':name (:context) has deleted their account.', [ |
There was a problem hiding this comment.
This is slightly confusing in the context of a user who was part of an organization but was removed from the organization before they deleted their account.
In this case this case the context is still "Community Organization" or "Regulated Organization". Probably best to check with Erick to see how he feels about this.
Steps to reproduce:
- Create an org or fro
- Invite a new user to it
- Create the new user account and accept the invite
- From the org/fro remove the new user
- As the invited user, delete their account
| public function __construct(DatabaseNotification $notification) | ||
| { | ||
| $this->title = __('New user registered'); | ||
| $this->body = __('A new user, :name, has registered as :context.', [ |
There was a problem hiding this comment.
I think the grammar is a bit off and will likely need to be toggled based on the context.
For example:
- 'A new user, :name, has registered as a :context.' (e.g. Training Participant)
- 'A new user, :name, has registered as an :context.' (e.g. Individual)
Added notification feature for platform admins when:
Platform admins receive both email and dashboard notifications.
Resolves #3040
Prerequisites
If this PR changes PHP code or dependencies:
composer formatand fixed any code formatting issues.composer analyzeand addressed any static analysis issues.php artisan testand ensured that all tests pass.composer localizeto update localization source files and committed any changes.If this PR changes CSS or JavaScript code or dependencies:
npm run lintand fixed any linting issues.npm run buildand ensured that CSS and JavaScript assets can be compiled.