Skip to content

Commit

Permalink
R2-2765 - Targeted user notification settings
Browse files Browse the repository at this point in the history
  • Loading branch information
aespinoza-quoin committed Mar 7, 2024
1 parent 10f99b4 commit c0b35aa
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 37 deletions.
11 changes: 10 additions & 1 deletion app/javascript/components/pages/admin/users-form/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,14 @@ export const FIELD_NAMES = Object.freeze({
LOCATION: "location",
DISABLED: "disabled",
SEND_MAIL: "send_mail",
RECEIVE_WEBPUSH: "receive_webpush"
SEND_MAIL_PREFERENCES: "settings.notifications.send_mail",
RECEIVE_WEBPUSH: "receive_webpush",
RECEIVE_WEBPUSH_PREFERENCES: "settings.notifications.receive_webpush"
});

export const NOTIFICATIONS_PREFERENCES = [
"approval_request",
"approval_response",
"transition_notification",
"transfer_request"
];
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe("Verifying user constant", () => {

[
"IDENTITY_PROVIDER_ID",
"NOTIFICATIONS_PREFERENCES",
"PASSWORD_MODAL",
"PASSWORD_SELF_OPTION",
"PASSWORD_USER_OPTION",
Expand Down Expand Up @@ -50,7 +51,9 @@ describe("values", () => {
"LOCATION",
"DISABLED",
"SEND_MAIL",
"RECEIVE_WEBPUSH"
"SEND_MAIL_PREFERENCES",
"RECEIVE_WEBPUSH",
"RECEIVE_WEBPUSH_PREFERENCES"
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ describe("<UsersForm />", () => {
expect(component.find("header button").at(1).contains("buttons.save")).to.be.true;
});

it("renders 18 fields", () => {
expect(getVisibleFields(component.find("FormSection").props().formSection.fields)).to.have.lengthOf(20);
it("renders 22 fields", () => {
expect(getVisibleFields(component.find("FormSection").props().formSection.fields)).to.have.lengthOf(22);
});

it("renders submit button with valid props", () => {
Expand Down Expand Up @@ -150,8 +150,8 @@ describe("<UsersForm />", () => {
"/admin/users/1"
]);

it("should render 17 fields", () => {
expect(getVisibleFields(newComponent.find("FormSection").props().formSection.fields)).to.have.lengthOf(17);
it("should render 19 fields", () => {
expect(getVisibleFields(newComponent.find("FormSection").props().formSection.fields)).to.have.lengthOf(19);
});

it("should fetch user groups and roles", () => {
Expand Down
33 changes: 32 additions & 1 deletion app/javascript/components/pages/admin/users-form/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@ import {
PASSWORD_USER_OPTION,
USER_GROUP_UNIQUE_IDS,
USERGROUP_PRIMERO_GBV,
FIELD_NAMES
FIELD_NAMES,
NOTIFICATIONS_PREFERENCES
} from "./constants";

const passwordPlaceholder = formMode => (formMode.get("isEdit") ? "•••••" : "");

const notificationPreferences = i18n =>
NOTIFICATIONS_PREFERENCES.map(preferences => ({
id: preferences,
display_text: i18n.t(`user.notification_preferences.${preferences}`)
}));

const sharedUserFields = (
i18n,
formMode,
Expand Down Expand Up @@ -198,12 +205,36 @@ const sharedUserFields = (
type: TICK_FIELD,
selected_value: formMode.get("isNew")
},
{
display_name: i18n.t("user.send_mail_preferences.label"),
name: FIELD_NAMES.SEND_MAIL_PREFERENCES,
type: SELECT_FIELD,
multi_select: true,
help_text: i18n.t("user.send_mail_preferences.help_text"),
option_strings_text: notificationPreferences(i18n),
watchedInputs: FIELD_NAMES.SEND_MAIL,
handleWatchedInputs: value => ({
visible: Boolean(value)
})
},
{
display_name: i18n.t("user.receive_webpush.label"),
name: FIELD_NAMES.RECEIVE_WEBPUSH,
type: TICK_FIELD,
help_text: i18n.t("user.receive_webpush.help_text"),
visible: webPushConfig?.get("enabled", false)
},
{
display_name: i18n.t("user.receive_webpush_preferences.label"),
name: FIELD_NAMES.RECEIVE_WEBPUSH_PREFERENCES,
type: SELECT_FIELD,
multi_select: true,
help_text: i18n.t("user.receive_webpush_preferences.help_text"),
option_strings_text: notificationPreferences(i18n),
watchedInputs: FIELD_NAMES.RECEIVE_WEBPUSH,
handleWatchedInputs: value => ({
visible: Boolean(value)
})
}
];

Expand Down
16 changes: 10 additions & 6 deletions app/mailers/record_action_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def manager_approval_request(approval_notification)
@approval_notification = approval_notification

return unless @approval_notification.send_notification?
return unless assert_notifications_enabled(@approval_notification.manager)
return unless assert_notifications_enabled(@approval_notification.manager, Approval::NOTIFICATION_ACTIONS_REQUEST)

mail(to: @approval_notification.manager.email, subject: @approval_notification.subject)
end
Expand All @@ -21,7 +21,7 @@ def manager_approval_response(approval_notification)
@approval_notification = approval_notification

return unless @approval_notification.send_notification?
return unless assert_notifications_enabled(@approval_notification.owner)
return unless assert_notifications_enabled(@approval_notification.owner, Approval::NOTIFICATION_ACTIONS_RESPONSE)

mail(to: @approval_notification.owner.email, subject: @approval_notification.subject)
end
Expand All @@ -30,7 +30,9 @@ def transition_notify(transition_notification)
@transition_notification = transition_notification

return if @transition_notification.transition.nil?
return unless assert_notifications_enabled(@transition_notification.transitioned_to)
return unless assert_notifications_enabled(
@transition_notification.transitioned_to, Transition::NOTIFICATION_ACTION
)

mail(to: @transition_notification&.transitioned_to&.email, subject: transition_notification.subject)
end
Expand All @@ -39,7 +41,9 @@ def transfer_request(transfer_request_notification)
@transfer_request_notification = transfer_request_notification

return if @transfer_request_notification.transition.nil?
return unless assert_notifications_enabled(@transfer_request_notification.transitioned_to)
return unless assert_notifications_enabled(
@transfer_request_notification.transitioned_to, Transfer::NOTIFICATION_ACTION
)

mail(to: @transfer_request_notification&.transitioned_to&.email, subject: @transfer_request_notification.subject)
end
Expand All @@ -56,8 +60,8 @@ def alert_notify(alert_notification)

private

def assert_notifications_enabled(user)
return true if user&.emailable?
def assert_notifications_enabled(user, action = nil)
return true if user&.emailable? && (action.nil? || user&.specific_notification?('send_mail', action))

Rails.logger.info("Mail not sent. Mail notifications disabled for #{user&.user_name || 'nil user'}")

Expand Down
4 changes: 4 additions & 0 deletions app/models/approval.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (c) 2014 - 2023 UNICEF. All rights reserved.

# Represents actions to request approval for a record and to approve those requests
# rubocop:disable Metrics/ClassLength
class Approval < ValueObject
attr_accessor :record, :fields, :user, :approval_type, :approval_id, :comments

Expand Down Expand Up @@ -54,6 +55,8 @@ class Approval < ValueObject
approved_comments: 'gbv_closure_approved_comments'
}.freeze

NOTIFICATION_ACTIONS_REQUEST = 'approval_request'
NOTIFICATION_ACTIONS_RESPONSE = 'approval_response'
class << self
def get!(approval_id, record, user, params = {})
raise Errors::UnknownPrimeroEntityType, 'approvals.error_invalid_approval' if types.exclude?(approval_id)
Expand Down Expand Up @@ -156,3 +159,4 @@ def delete_approval_alerts
record.alerts.where(type: approval_id, alert_for: Alertable::APPROVAL).destroy_all
end
end
# rubocop:enable Metrics/ClassLength
1 change: 1 addition & 0 deletions app/models/transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Transfer < Transition

TRANSFER_FORM_UNIQUE_ID = 'transfers_assignments'
TRANSFER_ALERT_TYPE = 'transfer'
NOTIFICATION_ACTION = 'transfer_request'

class << self
def alert_form_unique_id
Expand Down
1 change: 1 addition & 0 deletions app/models/transition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Transition < ApplicationRecord
STATUS_INPROGRESS = 'in_progress'
STATUS_DONE = 'done'
STATUS_REVOKED = 'revoked'
NOTIFICATION_ACTION = 'transition_notification'

belongs_to :record, polymorphic: true
belongs_to :transitioned_to_user, class_name: 'User', foreign_key: 'transitioned_to',
Expand Down
32 changes: 28 additions & 4 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ class User < ApplicationRecord
'services' => { 'type' => 'array' }, 'module_unique_ids' => { 'type' => 'array' },
'password_reset' => { 'type' => 'boolean' }, 'role_id' => { 'type' => 'string' },
'agency_office' => { 'type' => 'string' }, 'code_of_conduct_id' => { 'type' => 'integer' },
'send_mail' => { 'type' => 'boolean' }, 'receive_webpush' => { 'type' => 'boolean' }
'send_mail' => { 'type' => 'boolean' }, 'receive_webpush' => { 'type' => 'boolean' },
'settings' => {
'type' => %w[object null], 'properties' => {
'notifications' => {
'type' => 'object',
'properties' => {
'send_mail' => { 'type' => 'array' },
'receive_webpush' => { 'type' => 'array' }
}
}
}
}
}.freeze

attr_accessor :should_send_password_reset_instructions, :user_groups_changed
Expand All @@ -42,6 +53,8 @@ class User < ApplicationRecord

self.unique_id_attribute = 'user_name'

store_accessor :settings, :notifications

belongs_to :role
belongs_to :agency, optional: true
belongs_to :identity_provider, optional: true
Expand Down Expand Up @@ -136,14 +149,19 @@ def order_insensitive_attribute_names
%w[full_name user_name position]
end

def permitted_api_params(current_user = nil, target_user = nil)
permitted_params = (
def default_permitted_params
(
User.permitted_attribute_names + User.password_parameters +
[
{ 'user_group_ids' => [] }, { 'user_group_unique_ids' => [] },
{ 'module_unique_ids' => [] }, 'role_unique_id', 'identity_provider_unique_id'
{ 'module_unique_ids' => [] }, 'role_unique_id', 'identity_provider_unique_id',
{ 'settings' => { 'notifications' => { 'send_mail' => [], 'receive_webpush' => [] } } }
]
) - User.hidden_attributes
end

def permitted_api_params(current_user = nil, target_user = nil)
permitted_params = User.default_permitted_params

return permitted_params if current_user.nil? || target_user.nil?

Expand Down Expand Up @@ -547,6 +565,12 @@ def permitted_to_access_record?(record)
end
end

def specific_notification?(notifier, action)
return false if notifier.blank? || action.blank?

(notifications&.[](notifier) || []).include?(action)
end

private

def set_locale
Expand Down
20 changes: 12 additions & 8 deletions app/webpush_notifiers/record_action_webpush_notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ def self.transfer_request(transfer_request_notification)

def transition_notify(transition_notification)
return if transition_notification.transition.nil?
return unless webpush_notifications_enabled?(transition_notification&.transitioned_to)
return unless webpush_notifications_enabled?(
transition_notification&.transitioned_to, Transition::NOTIFICATION_ACTION
)

WebpushService.send_notifications(
transition_notification&.transitioned_to,
Expand All @@ -38,7 +40,7 @@ def transition_notify(transition_notification)

def manager_approval_request(approval_notification)
return unless approval_notification.send_notification?
return unless webpush_notifications_enabled?(approval_notification.manager)
return unless webpush_notifications_enabled?(approval_notification.manager, Approval::NOTIFICATION_ACTIONS_REQUEST)

WebpushService.send_notifications(
approval_notification.manager,
Expand All @@ -52,7 +54,7 @@ def manager_approval_request(approval_notification)

def manager_approval_response(approval_notification)
return unless approval_notification.send_notification?
return unless webpush_notifications_enabled?(approval_notification.owner)
return unless webpush_notifications_enabled?(approval_notification.owner, Approval::NOTIFICATION_ACTIONS_RESPONSE)

WebpushService.send_notifications(
approval_notification.owner,
Expand All @@ -62,7 +64,9 @@ def manager_approval_response(approval_notification)

def transfer_request(transfer_request_notification)
return if transfer_request_notification.transition.nil?
return unless webpush_notifications_enabled?(transfer_request_notification&.transitioned_to)
return unless webpush_notifications_enabled?(
transfer_request_notification&.transitioned_to, Transfer::NOTIFICATION_ACTION
)

WebpushService.send_notifications(
transfer_request_notification&.transitioned_to,
Expand Down Expand Up @@ -101,12 +105,12 @@ def message_structure(record_action_notification)

private

def webpush_notifications_enabled?(user)
web_push_enabled? && user_web_push_enabled?(user)
def webpush_notifications_enabled?(user, action = nil)
web_push_enabled? && user_web_push_enabled?(user, action)
end

def user_web_push_enabled?(user)
return true if user&.receive_webpush?
def user_web_push_enabled?(user, action)
return true if user&.receive_webpush? && (action.nil? || user&.specific_notification?('receive_webpush', action))

Rails.logger.info("Webpush not sent. Webpush notifications disabled for #{user&.user_name || 'nil user'}")

Expand Down
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4305,10 +4305,21 @@ en:
provider_username_help: 'Example: my.username@%{domain}'
role_id: Role
send_mail: Receive email notifications?
send_mail_preferences:
label: Which types of email notifications would you like to receive?
help_text: If you leave this field blank, you will receive all email notifications.
receive_webpush:
label: Receive push notifications?
help_text: Note that you will still need to allow push notifications on your device’s browser.
tooltip: You must first edit your account and enable push notifications for your user.
receive_webpush_preferences:
label: Which types of push notifications would you like to receive?
help_text: If you leave this field blank, you will receive all push notifications.
notification_preferences:
approval_request: 'Approval Request (Managers)'
approval_response: 'Approval Response (Case Workers)'
transition_notification: 'Assignments, Transfers, and Referrals'
transfer_request: 'Transfer Requests'
user_group_unique_ids: User Groups
user_name: Username
services: Services
Expand Down
8 changes: 8 additions & 0 deletions db/migrate/20240306154915_add_settings_to_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

# Copyright (c) 2014 - 2024 UNICEF. All rights reserved.
class AddSettingsToUser < ActiveRecord::Migration[6.1]
def change
add_column :users, :settings, :jsonb
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_11_21_151051) do
ActiveRecord::Schema.define(version: 2024_03_06_154915) do

# These are extensions that must be enabled in order to support this database
enable_extension "ltree"
Expand Down Expand Up @@ -657,6 +657,7 @@
t.datetime "code_of_conduct_accepted_on"
t.bigint "code_of_conduct_id"
t.boolean "receive_webpush"
t.jsonb "settings"
t.index ["agency_id"], name: "index_users_on_agency_id"
t.index ["code_of_conduct_id"], name: "index_users_on_code_of_conduct_id"
t.index ["email"], name: "index_users_on_email", unique: true
Expand Down
1 change: 1 addition & 0 deletions spec/factories/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
association :role, factory: :role, strategy: :build
user_group_ids { [FactoryBot.create(:user_group).id] }
agency_office { 'Agency Office 1' }
settings { { notifications: {} } }
end

factory :user_group, traits: [:active_model] do
Expand Down
Loading

0 comments on commit c0b35aa

Please sign in to comment.