feat: Add agents filter in CSAT reports (#4106)
* add agents filter in csat reports
This commit is contained in:
parent
c76b588850
commit
b94e67f5d7
9 changed files with 130 additions and 14 deletions
|
@ -30,8 +30,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
|
||||||
def set_csat_survey_responses
|
def set_csat_survey_responses
|
||||||
@csat_survey_responses = filtrate(
|
@csat_survey_responses = filtrate(
|
||||||
Current.account.csat_survey_responses.includes([:conversation, :assigned_agent, :contact])
|
Current.account.csat_survey_responses.includes([:conversation, :assigned_agent, :contact])
|
||||||
)
|
).filter_by_created_at(range).filter_by_assigned_agent_id(params[:user_ids])
|
||||||
@csat_survey_responses = @csat_survey_responses.where(created_at: range) if range.present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_current_page_surveys
|
def set_current_page_surveys
|
||||||
|
|
|
@ -6,15 +6,21 @@ class CSATReportsAPI extends ApiClient {
|
||||||
super('csat_survey_responses', { accountScoped: true });
|
super('csat_survey_responses', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
get({ page, from, to } = {}) {
|
get({ page, from, to, user_ids } = {}) {
|
||||||
return axios.get(this.url, {
|
return axios.get(this.url, {
|
||||||
params: { page, since: from, until: to, sort: '-created_at' },
|
params: {
|
||||||
|
page,
|
||||||
|
since: from,
|
||||||
|
until: to,
|
||||||
|
sort: '-created_at',
|
||||||
|
user_ids,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetrics({ from, to } = {}) {
|
getMetrics({ from, to, user_ids } = {}) {
|
||||||
return axios.get(`${this.url}/metrics`, {
|
return axios.get(`${this.url}/metrics`, {
|
||||||
params: { since: from, until: to },
|
params: { since: from, until: to, user_ids },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,6 +333,11 @@
|
||||||
"CSAT_REPORTS": {
|
"CSAT_REPORTS": {
|
||||||
"HEADER": "CSAT Reports",
|
"HEADER": "CSAT Reports",
|
||||||
"NO_RECORDS": "There are no CSAT survey responses available.",
|
"NO_RECORDS": "There are no CSAT survey responses available.",
|
||||||
|
"FILTERS": {
|
||||||
|
"AGENTS": {
|
||||||
|
"PLACEHOLDER": "Choose Agents"
|
||||||
|
}
|
||||||
|
},
|
||||||
"TABLE": {
|
"TABLE": {
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"CONTACT_NAME": "Contact",
|
"CONTACT_NAME": "Contact",
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="column content-box">
|
<div class="column content-box">
|
||||||
<report-filter-selector @date-range-change="onDateRangeChange" />
|
<report-filter-selector
|
||||||
|
agents-filter
|
||||||
|
:agents-filter-items-list="agentList"
|
||||||
|
@date-range-change="onDateRangeChange"
|
||||||
|
@agents-filter-change="onAgentsFilterChange"
|
||||||
|
/>
|
||||||
<csat-metrics />
|
<csat-metrics />
|
||||||
<csat-table :page-index="pageIndex" @page-change="onPageNumberChange" />
|
<csat-table :page-index="pageIndex" @page-change="onPageNumberChange" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,6 +14,7 @@
|
||||||
import CsatMetrics from './components/CsatMetrics';
|
import CsatMetrics from './components/CsatMetrics';
|
||||||
import CsatTable from './components/CsatTable';
|
import CsatTable from './components/CsatTable';
|
||||||
import ReportFilterSelector from './components/FilterSelector';
|
import ReportFilterSelector from './components/FilterSelector';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CsatResponses',
|
name: 'CsatResponses',
|
||||||
|
@ -18,11 +24,23 @@ export default {
|
||||||
ReportFilterSelector,
|
ReportFilterSelector,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { pageIndex: 1, from: 0, to: 0 };
|
return { pageIndex: 1, from: 0, to: 0, user_ids: [] };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
agentList: 'agents/getAgents',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getAllData() {
|
getAllData() {
|
||||||
this.$store.dispatch('csat/getMetrics', { from: this.from, to: this.to });
|
this.$store.dispatch('csat/getMetrics', {
|
||||||
|
from: this.from,
|
||||||
|
to: this.to,
|
||||||
|
user_ids: this.user_ids,
|
||||||
|
});
|
||||||
this.getResponses();
|
this.getResponses();
|
||||||
},
|
},
|
||||||
getResponses() {
|
getResponses() {
|
||||||
|
@ -30,6 +48,7 @@ export default {
|
||||||
page: this.pageIndex,
|
page: this.pageIndex,
|
||||||
from: this.from,
|
from: this.from,
|
||||||
to: this.to,
|
to: this.to,
|
||||||
|
user_ids: this.user_ids,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPageNumberChange(pageIndex) {
|
onPageNumberChange(pageIndex) {
|
||||||
|
@ -41,6 +60,10 @@ export default {
|
||||||
this.to = to;
|
this.to = to;
|
||||||
this.getAllData();
|
this.getAllData();
|
||||||
},
|
},
|
||||||
|
onAgentsFilterChange(agents) {
|
||||||
|
this.user_ids = agents.map(el => el.id);
|
||||||
|
this.getAllData();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
</woot-button>
|
</woot-button>
|
||||||
|
|
||||||
<report-filter-selector
|
<report-filter-selector
|
||||||
|
group-by-filter
|
||||||
:selected-group-by-filter="selectedGroupByFilter"
|
:selected-group-by-filter="selectedGroupByFilter"
|
||||||
:filter-items-list="filterItemsList"
|
:filter-items-list="filterItemsList"
|
||||||
@date-range-change="onDateRangeChange"
|
@date-range-change="onDateRangeChange"
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="notLast7Days"
|
v-if="notLast7Days && groupByFilter"
|
||||||
class="small-12 medium-3 pull-right margin-left-small"
|
class="small-12 medium-3 pull-right margin-left-small"
|
||||||
>
|
>
|
||||||
<p aria-hidden="true" class="hide">
|
<p aria-hidden="true" class="hide">
|
||||||
|
@ -41,6 +41,26 @@
|
||||||
@input="changeFilterSelection"
|
@input="changeFilterSelection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="agentsFilter"
|
||||||
|
class="small-12 medium-3 pull-right margin-left-small"
|
||||||
|
>
|
||||||
|
<multiselect
|
||||||
|
v-model="selectedAgents"
|
||||||
|
:options="agentsFilterItemsList"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:multiple="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:clear-on-select="false"
|
||||||
|
:hide-selected="true"
|
||||||
|
:placeholder="$t('CSAT_REPORTS.FILTERS.AGENTS.PLACEHOLDER')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
|
||||||
|
@input="handleAgentsFilterSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -61,10 +81,22 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
agentsFilterItemsList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
selectedGroupByFilter: {
|
selectedGroupByFilter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
groupByFilter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
agentsFilter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -72,6 +104,7 @@ export default {
|
||||||
dateRange: this.$t('REPORT.DATE_RANGE'),
|
dateRange: this.$t('REPORT.DATE_RANGE'),
|
||||||
customDateRange: [new Date(), new Date()],
|
customDateRange: [new Date(), new Date()],
|
||||||
currentSelectedFilter: null,
|
currentSelectedFilter: null,
|
||||||
|
selectedAgents: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -149,6 +182,9 @@ export default {
|
||||||
changeFilterSelection() {
|
changeFilterSelection() {
|
||||||
this.$emit('filter-change', this.currentSelectedFilter);
|
this.$emit('filter-change', this.currentSelectedFilter);
|
||||||
},
|
},
|
||||||
|
handleAgentsFilterSelection() {
|
||||||
|
this.$emit('agents-filter-change', this.selectedAgents);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -82,10 +82,13 @@ export const getters = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
get: async function getResponses({ commit }, { page = 1, from, to } = {}) {
|
get: async function getResponses(
|
||||||
|
{ commit },
|
||||||
|
{ page = 1, from, to, user_ids } = {}
|
||||||
|
) {
|
||||||
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: true });
|
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: true });
|
||||||
try {
|
try {
|
||||||
const response = await CSATReports.get({ page, from, to });
|
const response = await CSATReports.get({ page, from, to, user_ids });
|
||||||
commit(types.SET_CSAT_RESPONSE, response.data);
|
commit(types.SET_CSAT_RESPONSE, response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
|
@ -93,10 +96,10 @@ export const actions = {
|
||||||
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: false });
|
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetching: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getMetrics: async function getMetrics({ commit }, { from, to }) {
|
getMetrics: async function getMetrics({ commit }, { from, to, user_ids }) {
|
||||||
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: true });
|
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: true });
|
||||||
try {
|
try {
|
||||||
const response = await CSATReports.getMetrics({ from, to });
|
const response = await CSATReports.getMetrics({ from, to, user_ids });
|
||||||
commit(types.SET_CSAT_RESPONSE_METRICS, response.data);
|
commit(types.SET_CSAT_RESPONSE_METRICS, response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
|
|
|
@ -40,4 +40,7 @@ class CsatSurveyResponse < ApplicationRecord
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :contact_id, presence: true
|
validates :contact_id, presence: true
|
||||||
validates :conversation_id, presence: true
|
validates :conversation_id, presence: true
|
||||||
|
|
||||||
|
scope :filter_by_created_at, ->(range) { where(created_at: range) if range.present? }
|
||||||
|
scope :filter_by_assigned_agent_id, ->(user_ids) { where(assigned_agent_id: user_ids) if user_ids.present? }
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,6 +48,25 @@ RSpec.describe 'CSAT Survey Responses API', type: :request do
|
||||||
expect(response_data.pluck('id')).not_to include(csat_10_days_ago.id)
|
expect(response_data.pluck('id')).not_to include(csat_10_days_ago.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'filters csat responses based on a date range and agent ids' do
|
||||||
|
csat1_assigned_agent = create(:user, account: account, role: :agent)
|
||||||
|
csat2_assigned_agent = create(:user, account: account, role: :agent)
|
||||||
|
|
||||||
|
create(:csat_survey_response, account: account, created_at: 10.days.ago, assigned_agent: csat1_assigned_agent)
|
||||||
|
create(:csat_survey_response, account: account, created_at: 3.days.ago, assigned_agent: csat2_assigned_agent)
|
||||||
|
create(:csat_survey_response, account: account, created_at: 5.days.ago)
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/csat_survey_responses",
|
||||||
|
params: { since: 11.days.ago.to_time.to_i.to_s, until: Time.zone.today.to_time.to_i.to_s,
|
||||||
|
user_ids: [csat1_assigned_agent.id, csat2_assigned_agent.id] },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
response_data = JSON.parse(response.body)
|
||||||
|
expect(response_data.size).to eq 2
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns csat responses even if the agent is deleted from account' do
|
it 'returns csat responses even if the agent is deleted from account' do
|
||||||
deleted_agent_csat = create(:csat_survey_response, account: account, assigned_agent: agent)
|
deleted_agent_csat = create(:csat_survey_response, account: account, assigned_agent: agent)
|
||||||
deleted_agent_csat.assigned_agent.account_users.destroy_all
|
deleted_agent_csat.assigned_agent.account_users.destroy_all
|
||||||
|
@ -106,6 +125,27 @@ RSpec.describe 'CSAT Survey Responses API', type: :request do
|
||||||
expect(response_data['total_sent_messages_count']).to eq 0
|
expect(response_data['total_sent_messages_count']).to eq 0
|
||||||
expect(response_data['ratings_count']).to eq({ '1' => 1 })
|
expect(response_data['ratings_count']).to eq({ '1' => 1 })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'filters csat metrics based on a date range and agent ids' do
|
||||||
|
csat1_assigned_agent = create(:user, account: account, role: :agent)
|
||||||
|
csat2_assigned_agent = create(:user, account: account, role: :agent)
|
||||||
|
|
||||||
|
create(:csat_survey_response, account: account, created_at: 10.days.ago, assigned_agent: csat1_assigned_agent)
|
||||||
|
create(:csat_survey_response, account: account, created_at: 3.days.ago, assigned_agent: csat2_assigned_agent)
|
||||||
|
create(:csat_survey_response, account: account, created_at: 5.days.ago)
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/csat_survey_responses/metrics",
|
||||||
|
params: { since: 11.days.ago.to_time.to_i.to_s, until: Time.zone.today.to_time.to_i.to_s,
|
||||||
|
user_ids: [csat1_assigned_agent.id, csat2_assigned_agent.id] },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
response_data = JSON.parse(response.body)
|
||||||
|
expect(response_data['total_count']).to eq 2
|
||||||
|
expect(response_data['total_sent_messages_count']).to eq 0
|
||||||
|
expect(response_data['ratings_count']).to eq({ '1' => 2 })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue