feat: Conversation API to return applied_sla and sla_events (#9174)
* chore: Add sla_events to push_event_data * chore: Return SLA details in the API * chore: feature lock sla push event data * Update _conversation.json.jbuilder * chore: rubocop fixes
This commit is contained in:
@@ -25,3 +25,4 @@ class ApplicationController < ActionController::Base
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
ApplicationController.include_mod_with('Concerns::ApplicationControllerConcern')
|
||||||
|
|||||||
@@ -163,10 +163,14 @@ class ConversationFinder
|
|||||||
params[:page] || 1
|
params[:page] || 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversations
|
def conversations_base_query
|
||||||
@conversations = @conversations.includes(
|
@conversations.includes(
|
||||||
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
|
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def conversations
|
||||||
|
@conversations = conversations_base_query
|
||||||
|
|
||||||
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
|
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
|
||||||
@conversations = @conversations.send(sort_by, sort_order)
|
@conversations = @conversations.send(sort_by, sort_order)
|
||||||
@@ -178,3 +182,4 @@ class ConversationFinder
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
ConversationFinder.prepend_mod_with('ConversationFinder')
|
||||||
|
|||||||
@@ -312,5 +312,5 @@ class Conversation < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Conversation.include_mod_with('EnterpriseConversationConcern')
|
Conversation.include_mod_with('Concerns::Conversation')
|
||||||
Conversation.include_mod_with('SentimentAnalysisHelper')
|
Conversation.include_mod_with('SentimentAnalysisHelper')
|
||||||
|
|||||||
@@ -45,3 +45,4 @@ class Conversations::EventDataPresenter < SimpleDelegator
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Conversations::EventDataPresenter.prepend_mod_with('Conversations::EventDataPresenter')
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# TODO: Move this into models jbuilder
|
||||||
|
# Currently the file there is used only for search endpoint.
|
||||||
|
# Everywhere else we use conversation builder in partials folder
|
||||||
|
|
||||||
json.meta do
|
json.meta do
|
||||||
json.sender do
|
json.sender do
|
||||||
json.partial! 'api/v1/models/contact', formats: [:json], resource: conversation.contact
|
json.partial! 'api/v1/models/contact', formats: [:json], resource: conversation.contact
|
||||||
@@ -48,3 +52,4 @@ json.last_activity_at conversation.last_activity_at.to_i
|
|||||||
json.priority conversation.priority
|
json.priority conversation.priority
|
||||||
json.waiting_since conversation.waiting_since.to_i.to_i
|
json.waiting_since conversation.waiting_since.to_i.to_i
|
||||||
json.sla_policy_id conversation.sla_policy_id
|
json.sla_policy_id conversation.sla_policy_id
|
||||||
|
json.partial! 'enterprise/api/v1/conversations/partials/conversation', conversation: conversation if ChatwootApp.enterprise?
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# This file is used to render conversation data search API response.
|
||||||
|
|
||||||
json.id conversation.display_id
|
json.id conversation.display_id
|
||||||
json.uuid conversation.uuid
|
json.uuid conversation.uuid
|
||||||
json.created_at conversation.created_at.to_i
|
json.created_at conversation.created_at.to_i
|
||||||
|
|||||||
@@ -1,8 +1,2 @@
|
|||||||
class Api::V1::Accounts::EnterpriseAccountsController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::EnterpriseAccountsController < Api::V1::Accounts::BaseController
|
||||||
before_action :prepend_view_paths
|
|
||||||
|
|
||||||
# Prepend the view path to the enterprise/app/views won't be available by default
|
|
||||||
def prepend_view_paths
|
|
||||||
prepend_view_path 'enterprise/app/views/'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
module Enterprise::Concerns::ApplicationControllerConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_action :prepend_view_paths
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prepend the view path to the enterprise/app/views won't be available by default
|
||||||
|
def prepend_view_paths
|
||||||
|
prepend_view_path 'enterprise/app/views/'
|
||||||
|
end
|
||||||
|
end
|
||||||
5
enterprise/app/finders/enterprise/conversation_finder.rb
Normal file
5
enterprise/app/finders/enterprise/conversation_finder.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module Enterprise::ConversationFinder
|
||||||
|
def conversations_base_query
|
||||||
|
current_account.feature_enabled?('sla') ? super.includes(:applied_sla, :sla_events) : super
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -40,6 +40,23 @@ class AppliedSla < ApplicationRecord
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
scope :missed, -> { where(sla_status: :missed) }
|
scope :missed, -> { where(sla_status: :missed) }
|
||||||
|
|
||||||
|
def push_event_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
sla_id: sla_policy_id,
|
||||||
|
sla_status: sla_status,
|
||||||
|
created_at: created_at.to_i,
|
||||||
|
updated_at: updated_at.to_i,
|
||||||
|
sla_description: sla_policy.description,
|
||||||
|
sla_name: sla_policy.name,
|
||||||
|
sla_first_response_time_threshold: sla_policy.first_response_time_threshold,
|
||||||
|
sla_next_response_time_threshold: sla_policy.next_response_time_threshold,
|
||||||
|
sla_only_during_business_hours: sla_policy.only_during_business_hours,
|
||||||
|
sla_resolution_time_threshold: sla_policy.resolution_time_threshold
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_account_id
|
def ensure_account_id
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
module Enterprise::EnterpriseConversationConcern
|
module Enterprise::Concerns::Conversation
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
belongs_to :sla_policy, optional: true
|
belongs_to :sla_policy, optional: true
|
||||||
has_one :applied_sla, dependent: :destroy
|
has_one :applied_sla, dependent: :destroy_async
|
||||||
|
has_many :sla_events, dependent: :destroy_async
|
||||||
before_validation :validate_sla_policy, if: -> { sla_policy_id_changed? }
|
before_validation :validate_sla_policy, if: -> { sla_policy_id_changed? }
|
||||||
around_save :ensure_applied_sla_is_created, if: -> { sla_policy_id_changed? }
|
around_save :ensure_applied_sla_is_created, if: -> { sla_policy_id_changed? }
|
||||||
end
|
end
|
||||||
@@ -31,9 +31,18 @@ class SlaEvent < ApplicationRecord
|
|||||||
enum event_type: { frt: 0, nrt: 1, rt: 2 }
|
enum event_type: { frt: 0, nrt: 1, rt: 2 }
|
||||||
|
|
||||||
before_validation :ensure_applied_sla_id, :ensure_account_id, :ensure_inbox_id, :ensure_sla_policy_id
|
before_validation :ensure_applied_sla_id, :ensure_account_id, :ensure_inbox_id, :ensure_sla_policy_id
|
||||||
|
|
||||||
after_create_commit :create_notifications
|
after_create_commit :create_notifications
|
||||||
|
|
||||||
|
def push_event_data
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
event_type: event_type,
|
||||||
|
meta: meta,
|
||||||
|
created_at: created_at.to_i,
|
||||||
|
updated_at: updated_at.to_i
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_applied_sla_id
|
def ensure_applied_sla_id
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
module Enterprise::Conversations::EventDataPresenter
|
||||||
|
def push_data
|
||||||
|
if account.feature_enabled?('sla')
|
||||||
|
super.merge(
|
||||||
|
applied_sla: applied_sla&.push_event_data,
|
||||||
|
sla_events: sla_events.map(&:push_event_data)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
json.id resource.id
|
||||||
|
json.sla_id resource.sla_policy_id
|
||||||
|
json.sla_status resource.sla_status
|
||||||
|
json.created_at resource.created_at.to_i
|
||||||
|
json.updated_at resource.updated_at.to_i
|
||||||
|
json.sla_description resource.sla_policy.description
|
||||||
|
json.sla_name resource.sla_policy.name
|
||||||
|
json.sla_first_response_time_threshold resource.sla_policy.first_response_time_threshold
|
||||||
|
json.sla_next_response_time_threshold resource.sla_policy.next_response_time_threshold
|
||||||
|
json.sla_only_during_business_hours resource.sla_policy.only_during_business_hours
|
||||||
|
json.sla_resolution_time_threshold resource.sla_policy.resolution_time_threshold
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
if conversation.account.feature_enabled?('sla')
|
||||||
|
json.applied_sla do
|
||||||
|
json.partial! 'api/v1/models/applied_sla', formats: [:json], resource: conversation.applied_sla if conversation.applied_sla.present?
|
||||||
|
end
|
||||||
|
json.sla_events do
|
||||||
|
json.array! conversation.sla_events do |sla_event|
|
||||||
|
json.partial! 'api/v1/models/sla_event', formats: [:json], sla_event: sla_event
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Conversations API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/conversations/:id' do
|
||||||
|
it 'returns SLA data for the conversation if the feature is enabled' do
|
||||||
|
account.enable_features!('sla')
|
||||||
|
conversation = create(:conversation, account: account)
|
||||||
|
applied_sla = create(:applied_sla, conversation: conversation)
|
||||||
|
sla_event = create(:sla_event, conversation: conversation, applied_sla: applied_sla)
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}", headers: administrator.create_new_auth_token
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.parsed_body['applied_sla']['id']).to eq(applied_sla.id)
|
||||||
|
expect(response.parsed_body['sla_events'].first['id']).to eq(sla_event.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return SLA data for the conversation if the feature is disabled' do
|
||||||
|
account.disable_features!('sla')
|
||||||
|
conversation = create(:conversation, account: account)
|
||||||
|
create(:applied_sla, conversation: conversation)
|
||||||
|
create(:sla_event, conversation: conversation)
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}", headers: administrator.create_new_auth_token
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.parsed_body.keys).not_to include('applied_sla')
|
||||||
|
expect(response.parsed_body.keys).not_to include('sla_events')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,6 +7,27 @@ RSpec.describe AppliedSla, type: :model do
|
|||||||
it { is_expected.to belong_to(:conversation) }
|
it { is_expected.to belong_to(:conversation) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'push_event_data' do
|
||||||
|
it 'returns the correct hash' do
|
||||||
|
applied_sla = create(:applied_sla)
|
||||||
|
expect(applied_sla.push_event_data).to eq(
|
||||||
|
{
|
||||||
|
id: applied_sla.id,
|
||||||
|
sla_id: applied_sla.sla_policy_id,
|
||||||
|
sla_status: applied_sla.sla_status,
|
||||||
|
created_at: applied_sla.created_at.to_i,
|
||||||
|
updated_at: applied_sla.updated_at.to_i,
|
||||||
|
sla_description: applied_sla.sla_policy.description,
|
||||||
|
sla_name: applied_sla.sla_policy.name,
|
||||||
|
sla_first_response_time_threshold: applied_sla.sla_policy.first_response_time_threshold,
|
||||||
|
sla_next_response_time_threshold: applied_sla.sla_policy.next_response_time_threshold,
|
||||||
|
sla_only_during_business_hours: applied_sla.sla_policy.only_during_business_hours,
|
||||||
|
sla_resolution_time_threshold: applied_sla.sla_policy.resolution_time_threshold
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'validates_factory' do
|
describe 'validates_factory' do
|
||||||
it 'creates valid applied sla policy object' do
|
it 'creates valid applied sla policy object' do
|
||||||
applied_sla = create(:applied_sla)
|
applied_sla = create(:applied_sla)
|
||||||
|
|||||||
@@ -9,6 +9,21 @@ RSpec.describe SlaEvent, type: :model do
|
|||||||
it { is_expected.to belong_to(:inbox) }
|
it { is_expected.to belong_to(:inbox) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'push_event_data' do
|
||||||
|
it 'returns the correct hash' do
|
||||||
|
sla_event = create(:sla_event)
|
||||||
|
expect(sla_event.push_event_data).to eq(
|
||||||
|
{
|
||||||
|
id: sla_event.id,
|
||||||
|
event_type: 'frt',
|
||||||
|
meta: sla_event.meta,
|
||||||
|
created_at: sla_event.created_at.to_i,
|
||||||
|
updated_at: sla_event.updated_at.to_i
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'validates_factory' do
|
describe 'validates_factory' do
|
||||||
it 'creates valid sla event object' do
|
it 'creates valid sla event object' do
|
||||||
sla_event = create(:sla_event)
|
sla_event = create(:sla_event)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Conversations::EventDataPresenter do
|
||||||
|
let!(:presenter) { described_class.new(conversation) }
|
||||||
|
let!(:conversation) { create(:conversation) }
|
||||||
|
let!(:applied_sla) { create(:applied_sla, conversation: conversation) }
|
||||||
|
let!(:sla_event) { create(:sla_event, conversation: conversation, applied_sla: applied_sla) }
|
||||||
|
|
||||||
|
describe '#push_data' do
|
||||||
|
it 'returns push event payload with applied sla & sla events if the feature is enabled' do
|
||||||
|
conversation.account.enable_features!('sla')
|
||||||
|
|
||||||
|
expect(presenter.push_data).to include(
|
||||||
|
{
|
||||||
|
applied_sla: applied_sla.push_event_data,
|
||||||
|
sla_events: [sla_event.push_event_data]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns push event payload without applied sla & sla events if the feature is disabled' do
|
||||||
|
conversation.account.disable_features!('sla')
|
||||||
|
|
||||||
|
expect(presenter.push_data).not_to include(:applied_sla, :sla_events)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -38,7 +38,8 @@ RSpec.describe Conversations::EventDataPresenter do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'returns push event payload' do
|
it 'returns push event payload' do
|
||||||
expect(presenter.push_data).to eq(expected_data)
|
# the exceptions are the values that would be added in enterprise edition.
|
||||||
|
expect(presenter.push_data.except(:applied_sla, :sla_events)).to include(expected_data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user