+
@@ -32,6 +41,10 @@ defineProps({
type: Number,
required: true,
},
+ noOfConversations: {
+ type: Number,
+ required: true,
+ },
isLoading: {
type: Boolean,
default: false,
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue
new file mode 100644
index 000000000..5748366b6
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportFilters.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportItem.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportItem.vue
new file mode 100644
index 000000000..129265cdf
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAReportItem.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+ {{ `#${conversationId} ` }}
+
+ with
+ {{
+ conversation.contact.name
+ }}
+
+
+
+ {{ slaName }}
+
+
+
+ ---
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLATable.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLATable.vue
new file mode 100644
index 000000000..b761c699a
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLATable.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+ {{ $t('SLA_REPORTS.LOADING') }}
+
+
+
+
+
+ {{ $t('SLA_REPORTS.NO_RECORDS') }}
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAViewDetails.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAViewDetails.vue
new file mode 100644
index 000000000..72bc4749a
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SLA/SLAViewDetails.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+ {{ $t('SLA_REPORTS.TABLE.VIEW_DETAILS') }}
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
index 5e363553d..3792567da 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
@@ -9,6 +9,7 @@ const TeamReports = () => import('./TeamReports.vue');
const CsatResponses = () => import('./CsatResponses.vue');
const BotReports = () => import('./BotReports.vue');
const LiveReports = () => import('./LiveReports.vue');
+const SLAReports = () => import('./SLAReports.vue');
export default {
routes: [
@@ -151,5 +152,22 @@ export default {
},
],
},
+ {
+ path: frontendURL('accounts/:accountId/reports'),
+ component: SettingsContent,
+ props: {
+ headerTitle: 'SLA_REPORTS.HEADER',
+ icon: 'document-list-clock',
+ keepAlive: false,
+ },
+ children: [
+ {
+ path: 'sla',
+ name: 'sla_reports',
+ roles: ['administrator'],
+ component: SLAReports,
+ },
+ ],
+ },
],
};
diff --git a/app/javascript/dashboard/store/modules/SLAReports.js b/app/javascript/dashboard/store/modules/SLAReports.js
index 44c11fd0c..08ac714f7 100644
--- a/app/javascript/dashboard/store/modules/SLAReports.js
+++ b/app/javascript/dashboard/store/modules/SLAReports.js
@@ -5,7 +5,8 @@ import SLAReportsAPI from '../../api/slaReports';
export const state = {
records: [],
metrics: {
- numberOfSLABreaches: 0,
+ numberOfConversations: 0,
+ numberOfSLAMisses: 0,
hitRate: '0%',
},
uiFlags: {
@@ -72,19 +73,21 @@ export const mutations = {
[types.SET_SLA_REPORTS]: MutationHelpers.set,
[types.SET_SLA_REPORTS_METRICS](
_state,
- { number_of_sla_breaches: numberOfSLABreaches, hit_rate: hitRate }
+ {
+ number_of_sla_misses: numberOfSLAMisses,
+ hit_rate: hitRate,
+ total_applied_slas: numberOfConversations,
+ }
) {
_state.metrics = {
- numberOfSLABreaches,
+ numberOfSLAMisses,
hitRate,
+ numberOfConversations,
};
},
- [types.SET_SLA_REPORTS_META](
- _state,
- { total_applied_slas: totalAppliedSLAs, current_page: currentPage }
- ) {
+ [types.SET_SLA_REPORTS_META](_state, { count, current_page: currentPage }) {
_state.meta = {
- count: totalAppliedSLAs,
+ count,
currentPage,
};
},
diff --git a/app/javascript/dashboard/store/modules/specs/slaReports/getters.spec.js b/app/javascript/dashboard/store/modules/specs/slaReports/getters.spec.js
index 6eddb3f60..c50a72027 100644
--- a/app/javascript/dashboard/store/modules/specs/slaReports/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/slaReports/getters.spec.js
@@ -21,4 +21,33 @@ describe('#getters', () => {
isFetchingMetrics: false,
});
});
+
+ it('getMeta', () => {
+ const state = {
+ meta: {
+ count: 0,
+ currentPage: 1,
+ },
+ };
+ expect(getters.getMeta(state)).toEqual({
+ count: 0,
+ currentPage: 1,
+ });
+ });
+
+ it('getMetrics', () => {
+ const state = {
+ metrics: {
+ numberOfConversations: 27,
+ numberOfSLAMisses: 25,
+ hitRate: '7.41%',
+ },
+ };
+
+ expect(getters.getMetrics(state)).toEqual({
+ numberOfConversations: 27,
+ numberOfSLAMisses: 25,
+ hitRate: '7.41%',
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/modules/specs/slaReports/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/slaReports/mutations.spec.js
index 04e171889..fbeced61d 100644
--- a/app/javascript/dashboard/store/modules/specs/slaReports/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/slaReports/mutations.spec.js
@@ -23,12 +23,14 @@ describe('#mutations', () => {
it('set metrics', () => {
const state = { metrics: {} };
mutations[types.SET_SLA_REPORTS_METRICS](state, {
- number_of_sla_breaches: 1,
+ number_of_sla_misses: 1,
hit_rate: '100%',
+ total_applied_slas: 1,
});
expect(state.metrics).toEqual({
- numberOfSLABreaches: 1,
+ numberOfSLAMisses: 1,
hitRate: '100%',
+ numberOfConversations: 1,
});
});
});
@@ -37,7 +39,7 @@ describe('#mutations', () => {
it('set meta', () => {
const state = { meta: {} };
mutations[types.SET_SLA_REPORTS_META](state, {
- total_applied_slas: 1,
+ count: 1,
current_page: 1,
});
expect(state.meta).toEqual({
diff --git a/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb b/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb
index 4ec27bbbb..e195686a3 100644
--- a/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb
+++ b/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb
@@ -6,22 +6,23 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
before_action :set_applied_slas, only: [:index, :metrics, :download]
before_action :set_current_page, only: [:index]
- before_action :paginate_slas, only: [:index]
before_action :check_admin_authorization?
sort_on :created_at, type: :datetime
- def index; end
+ def index
+ @count = number_of_sla_misses
+ @applied_slas = @missed_applied_slas.page(@current_page).per(RESULTS_PER_PAGE)
+ end
def metrics
@total_applied_slas = total_applied_slas
- @number_of_sla_breaches = number_of_sla_breaches
+ @number_of_sla_misses = number_of_sla_misses
@hit_rate = hit_rate
end
def download
- @breached_slas = breached_slas
-
+ @missed_applied_slas = missed_applied_slas
response.headers['Content-Type'] = 'text/csv'
response.headers['Content-Disposition'] = 'attachment; filename=breached_conversation.csv'
render layout: false, formats: [:csv]
@@ -29,41 +30,38 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
private
- def breached_slas
- @applied_slas.includes(:sla_policy).joins(:conversation)
- .where.not(conversations: { status: :resolved })
- .where(applied_slas: { sla_status: :missed })
- end
-
def total_applied_slas
@total_applied_slas ||= @applied_slas.count
end
- def number_of_sla_breaches
- @number_of_sla_breaches ||= @applied_slas.missed.count
+ def number_of_sla_misses
+ @number_of_sla_misses ||= missed_applied_slas.count
end
def hit_rate
- number_of_sla_breaches.zero? ? '100%' : "#{hit_rate_percentage}%"
+ number_of_sla_misses.zero? ? '100%' : "#{hit_rate_percentage}%"
end
def hit_rate_percentage
- ((total_applied_slas - number_of_sla_breaches) / total_applied_slas.to_f * 100).round(2)
+ ((total_applied_slas - number_of_sla_misses) / total_applied_slas.to_f * 100).round(2)
end
def set_applied_slas
initial_query = Current.account.applied_slas.includes(:conversation)
- @applied_slas = initial_query
- .filter_by_date_range(range)
- .filter_by_inbox_id(params[:inbox_id])
- .filter_by_team_id(params[:team_id])
- .filter_by_sla_policy_id(params[:sla_policy_id])
- .filter_by_label_list(params[:label_list])
- .filter_by_assigned_agent_id(params[:assigned_agent_id])
+ @applied_slas = apply_filters(initial_query)
end
- def paginate_slas
- @applied_slas = @applied_slas.page(@current_page).per(RESULTS_PER_PAGE)
+ def apply_filters(query)
+ query.filter_by_date_range(range)
+ .filter_by_inbox_id(params[:inbox_id])
+ .filter_by_team_id(params[:team_id])
+ .filter_by_sla_policy_id(params[:sla_policy_id])
+ .filter_by_label_list(params[:label_list])
+ .filter_by_assigned_agent_id(params[:assigned_agent_id])
+ end
+
+ def missed_applied_slas
+ @missed_applied_slas ||= @applied_slas.missed
end
def set_current_page
diff --git a/enterprise/app/models/applied_sla.rb b/enterprise/app/models/applied_sla.rb
index ce67fad07..0ae634536 100644
--- a/enterprise/app/models/applied_sla.rb
+++ b/enterprise/app/models/applied_sla.rb
@@ -39,7 +39,7 @@ class AppliedSla < ApplicationRecord
joins(:conversation).where(conversations: { assigned_agent_id: assigned_agent_id })
end
}
- scope :missed, -> { where(sla_status: :missed) }
+ scope :missed, -> { where(sla_status: %i[missed active_with_misses]) }
after_update_commit :push_conversation_event
diff --git a/enterprise/app/views/api/v1/accounts/applied_slas/download.csv.erb b/enterprise/app/views/api/v1/accounts/applied_slas/download.csv.erb
index 676d6d680..9078eff25 100644
--- a/enterprise/app/views/api/v1/accounts/applied_slas/download.csv.erb
+++ b/enterprise/app/views/api/v1/accounts/applied_slas/download.csv.erb
@@ -10,8 +10,8 @@
] %>
<%= CSV.generate_line headers %>
-<% @breached_slas.each do |sla| %>
- <% breached_events = sla.sla_events.map(&:event_type).join(', ') %>
+<% @missed_applied_slas.each do |sla| %>
+ <% missed_events = sla.sla_events.map(&:event_type).join(', ') %>
<% conversation = sla.conversation %>
<%= CSV.generate_line([
conversation.display_id,
@@ -21,6 +21,6 @@
conversation.inbox&.name,
conversation.cached_label_list,
app_account_conversation_url(account_id: conversation.account_id, id: conversation.display_id),
- breached_events
+ missed_events
]) %>
<% end %>
diff --git a/enterprise/app/views/api/v1/accounts/applied_slas/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/applied_slas/index.json.jbuilder
index e9a905d56..35b723807 100644
--- a/enterprise/app/views/api/v1/accounts/applied_slas/index.json.jbuilder
+++ b/enterprise/app/views/api/v1/accounts/applied_slas/index.json.jbuilder
@@ -1,14 +1,22 @@
-json.array! @applied_slas do |applied_sla|
- json.id applied_sla.id
- json.sla_policy_id applied_sla.sla_policy_id
- json.conversation_id applied_sla.conversation_id
- json.sla_status applied_sla.sla_status
- json.created_at applied_sla.created_at
- json.updated_at applied_sla.updated_at
- json.conversation do
- json.partial! 'api/v1/models/conversation', conversation: applied_sla.conversation
- end
- json.sla_events applied_sla.sla_events do |sla_event|
- json.partial! 'api/v1/models/sla_event', formats: [:json], sla_event: sla_event
+json.payload do
+ json.array! @applied_slas do |applied_sla|
+ json.applied_sla applied_sla.push_event_data
+ json.conversation do
+ conversation = applied_sla.conversation
+ json.id conversation.id
+ json.contact do
+ json.name conversation.contact.name if conversation.contact
+ end
+ json.labels conversation.cached_label_list
+ json.assignee conversation.assignee.push_event_data if conversation.assignee
+ end
+ json.sla_events applied_sla.sla_events do |sla_event|
+ json.partial! 'api/v1/models/sla_event', formats: [:json], sla_event: sla_event
+ end
end
end
+
+json.meta do
+ json.count @count
+ json.current_page @current_page
+end
diff --git a/enterprise/app/views/api/v1/accounts/applied_slas/metrics.json.jbuilder b/enterprise/app/views/api/v1/accounts/applied_slas/metrics.json.jbuilder
index 13f184845..2752d901a 100644
--- a/enterprise/app/views/api/v1/accounts/applied_slas/metrics.json.jbuilder
+++ b/enterprise/app/views/api/v1/accounts/applied_slas/metrics.json.jbuilder
@@ -1,3 +1,3 @@
json.total_applied_slas @total_applied_slas
-json.number_of_sla_breaches @number_of_sla_breaches
+json.number_of_sla_misses @number_of_sla_misses
json.hit_rate @hit_rate
diff --git a/package.json b/package.json
index 313821424..e981ac52d 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"size-limit": [
{
"path": "public/packs/js/widget-*.js",
- "limit": "280 KB"
+ "limit": "281 KB"
},
{
"path": "public/packs/js/sdk.js",
diff --git a/spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb
index 3945c8c93..34389dad4 100644
--- a/spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb
+++ b/spec/enterprise/controllers/api/v1/accounts/applied_slas_controller_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
body = JSON.parse(response.body)
expect(body).to include('total_applied_slas' => 1)
- expect(body).to include('number_of_sla_breaches' => 1)
+ expect(body).to include('number_of_sla_misses' => 1)
expect(body).to include('hit_rate' => '0.0%')
end
@@ -48,7 +48,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
body = JSON.parse(response.body)
expect(body).to include('total_applied_slas' => 1)
- expect(body).to include('number_of_sla_breaches' => 0)
+ expect(body).to include('number_of_sla_misses' => 0)
expect(body).to include('hit_rate' => '100%')
end
@@ -64,7 +64,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
body = JSON.parse(response.body)
expect(body).to include('total_applied_slas' => 3)
- expect(body).to include('number_of_sla_breaches' => 1)
+ expect(body).to include('number_of_sla_misses' => 1)
expect(body).to include('hit_rate' => '66.67%')
end
@@ -80,7 +80,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
body = JSON.parse(response.body)
expect(body).to include('total_applied_slas' => 2)
- expect(body).to include('number_of_sla_breaches' => 1)
+ expect(body).to include('number_of_sla_misses' => 1)
expect(body).to include('hit_rate' => '50.0%')
end
@@ -98,7 +98,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
body = JSON.parse(response.body)
expect(body).to include('total_applied_slas' => 2)
- expect(body).to include('number_of_sla_breaches' => 1)
+ expect(body).to include('number_of_sla_misses' => 1)
expect(body).to include('hit_rate' => '50.0%')
end
end
@@ -128,7 +128,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
csv_data = CSV.parse(response.body)
csv_data.reject! { |row| row.all?(&:nil?) }
- expect(csv_data.size).to eq(2)
+ expect(csv_data.size).to eq(3)
expect(csv_data[1][0].to_i).to eq(conversation1.display_id)
end
end
@@ -145,21 +145,20 @@ RSpec.describe 'Applied SLAs API', type: :request do
context 'when it is an authenticated user' do
it 'returns the applied slas' do
create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2)
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, sla_status: 'missed')
get "/api/v1/accounts/#{account.id}/applied_slas",
headers: administrator.create_new_auth_token
expect(response).to have_http_status(:success)
body = JSON.parse(response.body)
-
- expect(body.size).to eq(2)
- expect(body.first).to include('id')
- expect(body.first).to include('sla_policy_id' => sla_policy1.id)
- expect(body.first).to include('conversation_id' => conversation1.id)
+ expect(body['payload'].size).to eq(1)
+ expect(body['payload'].first).to include('applied_sla')
+ expect(body['payload'].first['conversation']['id']).to eq(conversation2.id)
+ expect(body['meta']).to include('count' => 1)
end
it 'filters applied slas based on a date range' do
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, created_at: 10.days.ago)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago)
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, created_at: 10.days.ago, sla_status: 'missed')
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago, sla_status: 'missed')
get "/api/v1/accounts/#{account.id}/applied_slas",
params: { since: 5.days.ago.to_time.to_i.to_s, until: Time.zone.today.to_time.to_i.to_s },
@@ -167,13 +166,13 @@ RSpec.describe 'Applied SLAs API', type: :request do
expect(response).to have_http_status(:success)
body = JSON.parse(response.body)
- expect(body.size).to eq(1)
+ expect(body['payload'].size).to eq(1)
end
it 'filters applied slas based on a date range and agent ids' do
create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, created_at: 10.days.ago)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation3, created_at: 3.days.ago)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago)
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation3, created_at: 3.days.ago, sla_status: 'missed')
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago, sla_status: 'active_with_misses')
get "/api/v1/accounts/#{account.id}/applied_slas",
params: { agent_ids: [agent2.id] },
@@ -181,13 +180,13 @@ RSpec.describe 'Applied SLAs API', type: :request do
expect(response).to have_http_status(:success)
body = JSON.parse(response.body)
- expect(body.size).to eq(3)
+ expect(body['payload'].size).to eq(2)
end
it 'filters applied slas based on sla policy ids' do
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1)
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, sla_status: 'missed')
create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2)
- create(:applied_sla, sla_policy: sla_policy2, conversation: conversation2)
+ create(:applied_sla, sla_policy: sla_policy2, conversation: conversation2, sla_status: 'active_with_misses')
get "/api/v1/accounts/#{account.id}/applied_slas",
params: { sla_policy_id: sla_policy1.id },
@@ -195,15 +194,15 @@ RSpec.describe 'Applied SLAs API', type: :request do
expect(response).to have_http_status(:success)
body = JSON.parse(response.body)
- expect(body.size).to eq(2)
+ expect(body['payload'].size).to eq(1)
end
it 'filters applied slas based on labels' do
conversation2.update_labels('label1')
conversation3.update_labels('label1')
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, created_at: 10.days.ago)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago)
- create(:applied_sla, sla_policy: sla_policy1, conversation: conversation3, created_at: 3.days.ago)
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation1, created_at: 10.days.ago, sla_status: 'active_with_misses')
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation2, created_at: 3.days.ago, sla_status: 'missed')
+ create(:applied_sla, sla_policy: sla_policy1, conversation: conversation3, created_at: 3.days.ago, sla_status: 'missed')
get "/api/v1/accounts/#{account.id}/applied_slas",
params: { label_list: ['label1'] },
@@ -211,7 +210,7 @@ RSpec.describe 'Applied SLAs API', type: :request do
expect(response).to have_http_status(:success)
body = JSON.parse(response.body)
- expect(body.size).to eq(2)
+ expect(body['payload'].size).to eq(2)
end
end
end