feat: Add push/email notification support for SLA (#9140)
* feat: update SLA evaluation logic * Update enterprise/app/services/sla/evaluate_applied_sla_service.rb Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> * chore: refactor spec to bring down expecations in a single block * chore: fix process_account_applied_sla spec * chore: add spec to test multiple nrt misses * feat: persist sla notifications * feat: revert persist sla notifications * feat: add SLA push/email notification support * chore: refactor sla_status to include active_with_misses * chore: add support for sla push/email notifications * chore: refactor * chore: add liquid templates * chore: add spec for liquid templates * chore: add spec for sla email notifications * chore: add spec for SlaPolicyDrop * chore: refactor to ee namespace * chore: set enterprise test type to mailer * feat: enable sla notification settings only if SLA enabled * chore: refactor * chore: fix spec --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -83,7 +83,10 @@
|
||||
"CONVERSATION_CREATION": "Send email notifications when a new conversation is created",
|
||||
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation",
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in a participating conversation"
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in a participating conversation",
|
||||
"SLA_MISSED_FIRST_RESPONSE": "Send email notifications when a conversation misses first response SLA",
|
||||
"SLA_MISSED_NEXT_RESPONSE": "Send email notifications when a conversation misses next response SLA",
|
||||
"SLA_MISSED_RESOLUTION": "Send email notifications when a conversation misses resolution SLA"
|
||||
},
|
||||
"API": {
|
||||
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
|
||||
@@ -98,7 +101,10 @@
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation",
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in a participating conversation",
|
||||
"HAS_ENABLED_PUSH": "You have enabled push for this browser.",
|
||||
"REQUEST_PUSH": "Enable push notifications"
|
||||
"REQUEST_PUSH": "Enable push notifications",
|
||||
"SLA_MISSED_FIRST_RESPONSE": "Send push notifications when a conversation misses first response SLA",
|
||||
"SLA_MISSED_NEXT_RESPONSE": "Send push notifications when a conversation misses next response SLA",
|
||||
"SLA_MISSED_RESOLUTION": "Send push notifications when a conversation misses resolution SLA"
|
||||
},
|
||||
"PROFILE_IMAGE": {
|
||||
"LABEL": "Profile Image"
|
||||
|
||||
@@ -236,6 +236,54 @@
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedEmailFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_sla_missed_first_response"
|
||||
@input="handleEmailInput"
|
||||
/>
|
||||
<label for="sla_missed_first_response">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.SLA_MISSED_FIRST_RESPONSE'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedEmailFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_sla_missed_next_response"
|
||||
@input="handleEmailInput"
|
||||
/>
|
||||
<label for="sla_missed_next_response">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.SLA_MISSED_NEXT_RESPONSE'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedEmailFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="email_sla_missed_resolution"
|
||||
@input="handleEmailInput"
|
||||
/>
|
||||
<label for="sla_missed_resolution">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.SLA_MISSED_RESOLUTION'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -352,6 +400,57 @@
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedPushFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="push_sla_missed_first_response"
|
||||
@input="handlePushInput"
|
||||
/>
|
||||
<label for="sla_missed_first_response">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.SLA_MISSED_FIRST_RESPONSE'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedPushFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="push_sla_missed_next_response"
|
||||
@input="handlePushInput"
|
||||
/>
|
||||
<label for="sla_missed_next_response">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.SLA_MISSED_NEXT_RESPONSE'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="isSLAEnabled" class="flex items-center gap-2 mb-1">
|
||||
<input
|
||||
v-model="selectedPushFlags"
|
||||
class="notification--checkbox"
|
||||
type="checkbox"
|
||||
value="push_sla_missed_resolution"
|
||||
@input="handlePushInput"
|
||||
/>
|
||||
<label for="sla_missed_resolution">
|
||||
{{
|
||||
$t(
|
||||
'PROFILE_SETTINGS.FORM.PUSH_NOTIFICATIONS_SECTION.SLA_MISSED_RESOLUTION'
|
||||
)
|
||||
}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -367,6 +466,7 @@ import {
|
||||
requestPushPermissions,
|
||||
verifyServiceWorkerExistence,
|
||||
} from '../../../../helper/pushHelper';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin, configMixin, uiSettingsMixin],
|
||||
@@ -393,13 +493,18 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
emailFlags: 'userNotificationSettings/getSelectedEmailFlags',
|
||||
pushFlags: 'userNotificationSettings/getSelectedPushFlags',
|
||||
uiSettings: 'getUISettings',
|
||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||
}),
|
||||
hasPushAPISupport() {
|
||||
return !!('Notification' in window);
|
||||
},
|
||||
isSLAEnabled() {
|
||||
return this.isFeatureEnabledonAccount(this.accountId, FEATURE_FLAGS.SLA);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
emailFlags(value) {
|
||||
|
||||
@@ -61,7 +61,10 @@ class AgentNotifications::ConversationNotificationsMailer < ApplicationMailer
|
||||
user: @agent,
|
||||
conversation: @conversation,
|
||||
inbox: @conversation.inbox,
|
||||
message: @message
|
||||
message: @message,
|
||||
sla_policy: @sla_policy
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
AgentNotifications::ConversationNotificationsMailer.include_mod_with('AgentNotifications::ConversationNotificationsMailer')
|
||||
|
||||
@@ -5,11 +5,13 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
before_validation :validates_column_content_length
|
||||
|
||||
# the models that exposed in email templates through liquid
|
||||
DROPPABLES = %w[Account Channel Conversation Inbox User Message].freeze
|
||||
def droppables
|
||||
%w[Account Channel Conversation Inbox User Message]
|
||||
end
|
||||
|
||||
# ModelDrop class should exist in app/drops
|
||||
def to_drop
|
||||
return unless DROPPABLES.include?(self.class.name)
|
||||
return unless droppables.include?(self.class.name)
|
||||
|
||||
"#{self.class.name}Drop".constantize.new(self)
|
||||
end
|
||||
@@ -47,3 +49,5 @@ class ApplicationRecord < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ApplicationRecord.include_mod_with('Enterprise::ApplicationRecord')
|
||||
|
||||
@@ -118,11 +118,11 @@ class Notification < ApplicationRecord
|
||||
|
||||
def push_message_body
|
||||
case notification_type
|
||||
when 'conversation_creation'
|
||||
when 'conversation_creation', 'sla_missed_first_response'
|
||||
message_body(conversation.messages.first)
|
||||
when 'assigned_conversation_new_message', 'participating_conversation_new_message', 'conversation_mention'
|
||||
message_body(secondary_actor)
|
||||
when 'conversation_assignment'
|
||||
when 'conversation_assignment', 'sla_missed_next_response', 'sla_missed_resolution'
|
||||
message_body(conversation.messages.incoming.last)
|
||||
else
|
||||
''
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<p>Hi {{user.available_name}},</p>
|
||||
|
||||
<p>
|
||||
Conversation #{{conversation.display_id}} in {{ inbox.name }}
|
||||
has missed the SLA for first response under policy {{ sla_policy.name }}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{action_url}}">Please address immediately.</a>
|
||||
</p>
|
||||
@@ -0,0 +1,10 @@
|
||||
<p>Hi {{user.available_name}},</p>
|
||||
|
||||
<p>
|
||||
Conversation #{{conversation.display_id}} in {{ inbox.name }}
|
||||
has missed the SLA for next response under policy {{ sla_policy.name }}..
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{action_url}}">Please address immediately.</a>
|
||||
</p>
|
||||
@@ -0,0 +1,10 @@
|
||||
<p>Hi {{user.available_name}},</p>
|
||||
|
||||
<p>
|
||||
Conversation #{{conversation.display_id}} in {{ inbox.name }}
|
||||
has missed the SLA for resolution time under policy {{ sla_policy.name }}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{action_url}}">Please address immediately.</a>
|
||||
</p>
|
||||
9
enterprise/app/drops/sla_policy_drop.rb
Normal file
9
enterprise/app/drops/sla_policy_drop.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class SlaPolicyDrop < BaseDrop
|
||||
def name
|
||||
@obj.try(:name)
|
||||
end
|
||||
|
||||
def description
|
||||
@obj.try(:description)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
module Enterprise::AgentNotifications::ConversationNotificationsMailer
|
||||
def sla_missed_first_response(conversation, agent, sla_policy)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
@agent = agent
|
||||
@conversation = conversation
|
||||
@sla_policy = sla_policy
|
||||
subject = "Conversation [ID - #{@conversation.display_id}] missed SLA for first response"
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
send_mail_with_liquid(to: @agent.email, subject: subject) and return
|
||||
end
|
||||
|
||||
def sla_missed_next_response(conversation, agent, sla_policy)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
@agent = agent
|
||||
@conversation = conversation
|
||||
@sla_policy = sla_policy
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
send_mail_with_liquid(to: @agent.email, subject: "Conversation [ID - #{@conversation.display_id}] missed SLA for next response") and return
|
||||
end
|
||||
|
||||
def sla_missed_resolution(conversation, agent, sla_policy)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
@agent = agent
|
||||
@conversation = conversation
|
||||
@sla_policy = sla_policy
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
send_mail_with_liquid(to: @agent.email, subject: "Conversation [ID - #{@conversation.display_id}] missed SLA for resolution time") and return
|
||||
end
|
||||
end
|
||||
5
enterprise/app/models/enterprise/application_record.rb
Normal file
5
enterprise/app/models/enterprise/application_record.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
module Enterprise::ApplicationRecord
|
||||
def droppables
|
||||
super + %w[SlaPolicy]
|
||||
end
|
||||
end
|
||||
15
spec/enterprise/drops/sla_policy_drop_spec.rb
Normal file
15
spec/enterprise/drops/sla_policy_drop_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe SlaPolicyDrop do
|
||||
subject(:sla_policy_drop) { described_class.new(sla_policy) }
|
||||
|
||||
let!(:sla_policy) { create(:sla_policy) }
|
||||
|
||||
it 'returns name' do
|
||||
expect(sla_policy_drop.name).to eq sla_policy.name
|
||||
end
|
||||
|
||||
it 'returns description' do
|
||||
expect(sla_policy_drop.description).to eq sla_policy.description
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# rails helper is using infer filetype to detect rspec type
|
||||
# so we need to include type: :mailer to make this test work in enterprise namespace
|
||||
RSpec.describe AgentNotifications::ConversationNotificationsMailer, type: :mailer do
|
||||
let(:class_instance) { described_class.new }
|
||||
let!(:account) { create(:account) }
|
||||
let(:agent) { create(:user, email: 'agent1@example.com', account: account) }
|
||||
let(:conversation) { create(:conversation, assignee: agent, account: account) }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:new).and_return(class_instance)
|
||||
allow(class_instance).to receive(:smtp_config_set_or_development?).and_return(true)
|
||||
end
|
||||
|
||||
describe 'sla_missed_first_response' do
|
||||
let(:sla_policy) { create(:sla_policy, account: account) }
|
||||
let(:mail) { described_class.with(account: account).sla_missed_first_response(conversation, agent, sla_policy).deliver_now }
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(mail.subject).to eq("Conversation [ID - #{conversation.display_id}] missed SLA for first response")
|
||||
end
|
||||
|
||||
it 'renders the receiver email' do
|
||||
expect(mail.to).to eq([agent.email])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sla_missed_next_response' do
|
||||
let(:sla_policy) { create(:sla_policy, account: account) }
|
||||
let(:mail) { described_class.with(account: account).sla_missed_next_response(conversation, agent, sla_policy).deliver_now }
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(mail.subject).to eq("Conversation [ID - #{conversation.display_id}] missed SLA for next response")
|
||||
end
|
||||
|
||||
it 'renders the receiver email' do
|
||||
expect(mail.to).to eq([agent.email])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sla_missed_resolution' do
|
||||
let(:sla_policy) { create(:sla_policy, account: account) }
|
||||
let(:mail) { described_class.with(account: account).sla_missed_resolution(conversation, agent, sla_policy).deliver_now }
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(mail.subject).to eq("Conversation [ID - #{conversation.display_id}] missed SLA for resolution time")
|
||||
end
|
||||
|
||||
it 'renders the receiver email' do
|
||||
expect(mail.to).to eq([agent.email])
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user