mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
feat: Add the bot performance reports UI (#9036)
Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -84,6 +84,24 @@ class ReportsAPI extends ApiClient {
|
|||||||
params: { since, until, business_hours: businessHours },
|
params: { since, until, business_hours: businessHours },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBotMetrics({ from, to } = {}) {
|
||||||
|
return axios.get(`${this.url}/bot_metrics`, {
|
||||||
|
params: { since: from, until: to },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBotSummary({ from, to, groupBy, businessHours } = {}) {
|
||||||
|
return axios.get(`${this.url}/bot_summary`, {
|
||||||
|
params: {
|
||||||
|
since: from,
|
||||||
|
until: to,
|
||||||
|
type: 'account',
|
||||||
|
group_by: groupBy,
|
||||||
|
business_hours: businessHours,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ReportsAPI();
|
export default new ReportsAPI();
|
||||||
|
|||||||
@@ -111,6 +111,40 @@ describe('#Reports API', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#getBotMetrics', () => {
|
||||||
|
reportsAPI.getBotMetrics({ from: 1621103400, to: 1621621800 });
|
||||||
|
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/reports/bot_metrics',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
since: 1621103400,
|
||||||
|
until: 1621621800,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#getBotSummary', () => {
|
||||||
|
reportsAPI.getBotSummary({
|
||||||
|
from: 1621103400,
|
||||||
|
to: 1621621800,
|
||||||
|
groupBy: 'date',
|
||||||
|
businessHours: true,
|
||||||
|
});
|
||||||
|
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/reports/bot_summary',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
since: 1621103400,
|
||||||
|
until: 1621621800,
|
||||||
|
type: 'account',
|
||||||
|
group_by: 'date',
|
||||||
|
business_hours: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('#getConversationMetric', () => {
|
it('#getConversationMetric', () => {
|
||||||
reportsAPI.getConversationMetric('account');
|
reportsAPI.getConversationMetric('account');
|
||||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
expect(axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
||||||
import { frontendURL } from '../../../../helper/URLHelper';
|
import { frontendURL } from '../../../../helper/URLHelper';
|
||||||
|
|
||||||
const reports = accountId => ({
|
const reports = accountId => ({
|
||||||
@@ -6,6 +7,7 @@ const reports = accountId => ({
|
|||||||
'account_overview_reports',
|
'account_overview_reports',
|
||||||
'conversation_reports',
|
'conversation_reports',
|
||||||
'csat_reports',
|
'csat_reports',
|
||||||
|
'bot_reports',
|
||||||
'agent_reports',
|
'agent_reports',
|
||||||
'label_reports',
|
'label_reports',
|
||||||
'inbox_reports',
|
'inbox_reports',
|
||||||
@@ -33,6 +35,14 @@ const reports = accountId => ({
|
|||||||
toState: frontendURL(`accounts/${accountId}/reports/csat`),
|
toState: frontendURL(`accounts/${accountId}/reports/csat`),
|
||||||
toStateName: 'csat_reports',
|
toStateName: 'csat_reports',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'bot',
|
||||||
|
label: 'REPORTS_BOT',
|
||||||
|
hasSubMenu: false,
|
||||||
|
featureFlag: FEATURE_FLAGS.RESPONSE_BOT,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/bot`),
|
||||||
|
toStateName: 'bot_reports',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'people',
|
icon: 'people',
|
||||||
label: 'REPORTS_AGENT',
|
label: 'REPORTS_AGENT',
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ export const FEATURE_FLAGS = {
|
|||||||
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
|
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
|
||||||
INBOX_VIEW: 'inbox_view',
|
INBOX_VIEW: 'inbox_view',
|
||||||
SLA: 'sla',
|
SLA: 'sla',
|
||||||
|
RESPONSE_BOT: 'response_bot',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
"NAME": "Resolution Count",
|
"NAME": "Resolution Count",
|
||||||
"DESC": "( Total )"
|
"DESC": "( Total )"
|
||||||
},
|
},
|
||||||
|
"BOT_RESOLUTION_COUNT": {
|
||||||
|
"NAME": "Resolution Count",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"BOT_HANDOFF_COUNT": {
|
||||||
|
"NAME": "Handoff Count",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
"REPLY_TIME": {
|
"REPLY_TIME": {
|
||||||
"NAME": "Customer waiting time",
|
"NAME": "Customer waiting time",
|
||||||
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} replies)"
|
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} replies)"
|
||||||
@@ -86,20 +94,49 @@
|
|||||||
"MONTH": "Month",
|
"MONTH": "Month",
|
||||||
"YEAR": "Year"
|
"YEAR": "Year"
|
||||||
},
|
},
|
||||||
"GROUP_BY_DAY_OPTIONS": [{ "id": 1, "groupBy": "Day" }],
|
"GROUP_BY_DAY_OPTIONS": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"groupBy": "Day"
|
||||||
|
}
|
||||||
|
],
|
||||||
"GROUP_BY_WEEK_OPTIONS": [
|
"GROUP_BY_WEEK_OPTIONS": [
|
||||||
{ "id": 1, "groupBy": "Day" },
|
{
|
||||||
{ "id": 2, "groupBy": "Week" }
|
"id": 1,
|
||||||
|
"groupBy": "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"groupBy": "Week"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"GROUP_BY_MONTH_OPTIONS": [
|
"GROUP_BY_MONTH_OPTIONS": [
|
||||||
{ "id": 1, "groupBy": "Day" },
|
{
|
||||||
{ "id": 2, "groupBy": "Week" },
|
"id": 1,
|
||||||
{ "id": 3, "groupBy": "Month" }
|
"groupBy": "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"groupBy": "Week"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"groupBy": "Month"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"GROUP_BY_YEAR_OPTIONS": [
|
"GROUP_BY_YEAR_OPTIONS": [
|
||||||
{ "id": 2, "groupBy": "Week" },
|
{
|
||||||
{ "id": 3, "groupBy": "Month" },
|
"id": 2,
|
||||||
{ "id": 4, "groupBy": "Year" }
|
"groupBy": "Week"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"groupBy": "Month"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"groupBy": "Year"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"BUSINESS_HOURS": "Business Hours"
|
"BUSINESS_HOURS": "Business Hours"
|
||||||
},
|
},
|
||||||
@@ -404,6 +441,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BOT_REPORTS": {
|
||||||
|
"HEADER": "Bot Reports",
|
||||||
|
"METRIC": {
|
||||||
|
"TOTAL_CONVERSATIONS": {
|
||||||
|
"LABEL": "No. of Conversations",
|
||||||
|
"TOOLTIP": "Total number of conversations handled by the bot"
|
||||||
|
},
|
||||||
|
"TOTAL_RESPONSES": {
|
||||||
|
"LABEL": "Total Responses",
|
||||||
|
"TOOLTIP": "Total number of responses sent by the bot"
|
||||||
|
},
|
||||||
|
"RESOLUTION_RATE": {
|
||||||
|
"LABEL": "Resolution Rate",
|
||||||
|
"TOOLTIP": "Total number of conversations resolved by the bot / Total number of conversations handled by the bot * 100"
|
||||||
|
},
|
||||||
|
"HANDOFF_RATE": {
|
||||||
|
"LABEL": "Handoff Rate",
|
||||||
|
"TOOLTIP": "Total number of conversations handed off to agents / Total number of conversations handled by the bot * 100"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"OVERVIEW_REPORTS": {
|
"OVERVIEW_REPORTS": {
|
||||||
"HEADER": "Overview",
|
"HEADER": "Overview",
|
||||||
"LIVE": "Live",
|
"LIVE": "Live",
|
||||||
|
|||||||
@@ -234,6 +234,7 @@
|
|||||||
"CAMPAIGNS": "Campaigns",
|
"CAMPAIGNS": "Campaigns",
|
||||||
"ONGOING": "Ongoing",
|
"ONGOING": "Ongoing",
|
||||||
"ONE_OFF": "One off",
|
"ONE_OFF": "One off",
|
||||||
|
"REPORTS_BOT": "Bot",
|
||||||
"REPORTS_AGENT": "Agents",
|
"REPORTS_AGENT": "Agents",
|
||||||
"REPORTS_LABEL": "Labels",
|
"REPORTS_LABEL": "Labels",
|
||||||
"REPORTS_INBOX": "Inbox",
|
"REPORTS_INBOX": "Inbox",
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ import { mapGetters } from 'vuex';
|
|||||||
import { formatTime } from '@chatwoot/utils';
|
import { formatTime } from '@chatwoot/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
accountSummaryKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'getAccountSummary',
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
accountSummary: 'getAccountSummary',
|
|
||||||
accountReport: 'getAccountReports',
|
accountReport: 'getAccountReports',
|
||||||
}),
|
}),
|
||||||
|
accountSummary() {
|
||||||
|
return this.$store.getters[this.accountSummaryKey];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
calculateTrend(key) {
|
calculateTrend(key) {
|
||||||
|
|||||||
@@ -11,11 +11,42 @@ describe('reportMixin', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
getters = {
|
getters = {
|
||||||
getAccountSummary: () => reportFixtures.summary,
|
getAccountSummary: () => reportFixtures.summary,
|
||||||
|
getBotSummary: () => reportFixtures.botSummary,
|
||||||
getAccountReports: () => reportFixtures.report,
|
getAccountReports: () => reportFixtures.report,
|
||||||
};
|
};
|
||||||
store = new Vuex.Store({ getters });
|
store = new Vuex.Store({ getters });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('display the metric for account', async () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
title: 'TestComponent',
|
||||||
|
mixins: [reportMixin],
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component, { store, localVue });
|
||||||
|
await wrapper.setProps({
|
||||||
|
accountSummaryKey: 'getAccountSummary',
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000');
|
||||||
|
expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual(
|
||||||
|
'3 Min 18 Sec'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('display the metric for bot', async () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
title: 'TestComponent',
|
||||||
|
mixins: [reportMixin],
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component, { store, localVue });
|
||||||
|
await wrapper.setProps({
|
||||||
|
accountSummaryKey: 'getBotSummary',
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.displayMetric('bot_resolutions_count')).toEqual('10');
|
||||||
|
expect(wrapper.vm.displayMetric('bot_handoffs_count')).toEqual('20');
|
||||||
|
});
|
||||||
|
|
||||||
it('display the metric', () => {
|
it('display the metric', () => {
|
||||||
const Component = {
|
const Component = {
|
||||||
render() {},
|
render() {},
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ export default {
|
|||||||
},
|
},
|
||||||
resolutions_count: 3,
|
resolutions_count: 3,
|
||||||
},
|
},
|
||||||
|
botSummary: {
|
||||||
|
bot_resolutions_count: 10,
|
||||||
|
bot_handoffs_count: 20,
|
||||||
|
previous: {
|
||||||
|
bot_resolutions_count: 8,
|
||||||
|
bot_handoffs_count: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
report: {
|
report: {
|
||||||
data: [
|
data: [
|
||||||
{ value: '0.00', timestamp: 1647541800, count: 0 },
|
{ value: '0.00', timestamp: 1647541800, count: 0 },
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-1 overflow-auto p-4">
|
||||||
|
<report-filter-selector
|
||||||
|
:show-agents-filter="false"
|
||||||
|
:show-group-by-filter="true"
|
||||||
|
:show-business-hours-switch="false"
|
||||||
|
@filter-change="onFilterChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<bot-metrics :filters="requestPayload" />
|
||||||
|
<report-container
|
||||||
|
:group-by="groupBy"
|
||||||
|
:report-keys="reportKeys"
|
||||||
|
:account-summary-key="'getBotSummary'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import BotMetrics from './components/BotMetrics.vue';
|
||||||
|
import ReportFilterSelector from './components/FilterSelector.vue';
|
||||||
|
import { GROUP_BY_FILTER } from './constants';
|
||||||
|
import reportMixin from 'dashboard/mixins/reportMixin';
|
||||||
|
import ReportContainer from './ReportContainer.vue';
|
||||||
|
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'BotReports',
|
||||||
|
components: {
|
||||||
|
BotMetrics,
|
||||||
|
ReportFilterSelector,
|
||||||
|
ReportContainer,
|
||||||
|
},
|
||||||
|
mixins: [reportMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
from: 0,
|
||||||
|
to: 0,
|
||||||
|
groupBy: GROUP_BY_FILTER[1],
|
||||||
|
reportKeys: {
|
||||||
|
BOT_RESOLUTION_COUNT: 'bot_resolutions_count',
|
||||||
|
BOT_HANDOFF_COUNT: 'bot_handoffs_count',
|
||||||
|
},
|
||||||
|
businessHours: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
accountReport: 'getAccountReports',
|
||||||
|
}),
|
||||||
|
requestPayload() {
|
||||||
|
return {
|
||||||
|
from: this.from,
|
||||||
|
to: this.to,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchAllData() {
|
||||||
|
this.fetchBotSummary();
|
||||||
|
this.fetchChartData();
|
||||||
|
},
|
||||||
|
fetchBotSummary() {
|
||||||
|
try {
|
||||||
|
this.$store.dispatch('fetchBotSummary', this.getRequestPayload());
|
||||||
|
} catch {
|
||||||
|
this.showAlert(this.$t('REPORT.SUMMARY_FETCHING_FAILED'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchChartData() {
|
||||||
|
Object.keys(this.reportKeys).forEach(async key => {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('fetchAccountReport', {
|
||||||
|
metric: this.reportKeys[key],
|
||||||
|
...this.getRequestPayload(),
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
this.showAlert(this.$t('REPORT.DATA_FETCHING_FAILED'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getRequestPayload() {
|
||||||
|
const { from, to, groupBy, businessHours } = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
groupBy: groupBy?.period,
|
||||||
|
businessHours,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onFilterChange({ from, to, groupBy, businessHours }) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.groupBy = groupBy;
|
||||||
|
this.businessHours = businessHours;
|
||||||
|
this.fetchAllData();
|
||||||
|
|
||||||
|
this.$track(REPORTS_EVENTS.FILTER_REPORT, {
|
||||||
|
filterValue: { from, to, groupBy, businessHours },
|
||||||
|
reportType: 'bots',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
:key="metric.KEY"
|
:key="metric.KEY"
|
||||||
class="p-4 rounded-md mb-3"
|
class="p-4 rounded-md mb-3"
|
||||||
>
|
>
|
||||||
<chart-stats :metric="metric" />
|
<chart-stats :metric="metric" :account-summary-key="accountSummaryKey" />
|
||||||
<div class="mt-4 h-72">
|
<div class="mt-4 h-72">
|
||||||
<woot-loading-state
|
<woot-loading-state
|
||||||
v-if="accountReport.isFetching[metric.KEY]"
|
v-if="accountReport.isFetching[metric.KEY]"
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, watch, onMounted } from 'vue';
|
||||||
|
import ReportMetricCard from './ReportMetricCard.vue';
|
||||||
|
import ReportsAPI from 'dashboard/api/reports';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
filters: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const conversationCount = ref('0');
|
||||||
|
const messageCount = ref('0');
|
||||||
|
const resolutionRate = ref('0');
|
||||||
|
const handoffRate = ref('0');
|
||||||
|
|
||||||
|
const formatToPercent = value => {
|
||||||
|
return value ? `${value}%` : '--';
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMetrics = () => {
|
||||||
|
if (!props.filters.to || !props.filters.from) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReportsAPI.getBotMetrics(props.filters).then(response => {
|
||||||
|
conversationCount.value = response.data.conversation_count.toLocaleString();
|
||||||
|
messageCount.value = response.data.message_count.toLocaleString();
|
||||||
|
resolutionRate.value = response.data.resolution_rate.toString();
|
||||||
|
handoffRate.value = response.data.handoff_rate.toString();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.filters, fetchMetrics, { deep: true });
|
||||||
|
|
||||||
|
onMounted(fetchMetrics);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap mx-0 bg-white dark:bg-slate-800 rounded-[4px] p-4 mb-5 border border-solid border-slate-75 dark:border-slate-700"
|
||||||
|
>
|
||||||
|
<report-metric-card
|
||||||
|
:label="$t('BOT_REPORTS.METRIC.TOTAL_CONVERSATIONS.LABEL')"
|
||||||
|
:info-text="$t('BOT_REPORTS.METRIC.TOTAL_CONVERSATIONS.TOOLTIP')"
|
||||||
|
:value="conversationCount"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<report-metric-card
|
||||||
|
:label="$t('BOT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
|
||||||
|
:info-text="$t('BOT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
|
||||||
|
:value="messageCount"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<report-metric-card
|
||||||
|
:label="$t('BOT_REPORTS.METRIC.RESOLUTION_RATE.LABEL')"
|
||||||
|
:info-text="$t('BOT_REPORTS.METRIC.RESOLUTION_RATE.TOOLTIP')"
|
||||||
|
:value="formatToPercent(resolutionRate)"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<report-metric-card
|
||||||
|
:label="$t('BOT_REPORTS.METRIC.HANDOFF_RATE.LABEL')"
|
||||||
|
:info-text="$t('BOT_REPORTS.METRIC.HANDOFF_RATE.TOOLTIP')"
|
||||||
|
:value="formatToPercent(handoffRate)"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="text-slate-900 dark:text-slate-100">
|
||||||
<span class="text-sm">{{ metric.NAME }}</span>
|
<span class="text-sm">
|
||||||
|
{{ metric.NAME }}
|
||||||
|
</span>
|
||||||
<div class="flex items-end">
|
<div class="flex items-end">
|
||||||
<div class="font-medium text-xl">
|
<div class="font-medium text-xl">
|
||||||
{{ displayMetric(metric.KEY) }}
|
{{ displayMetric(metric.KEY) }}
|
||||||
|
|||||||
@@ -6,17 +6,20 @@
|
|||||||
:label="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
|
:label="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
|
||||||
:info-text="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
|
:info-text="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
|
||||||
:value="responseCount"
|
:value="responseCount"
|
||||||
|
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
|
||||||
/>
|
/>
|
||||||
<csat-metric-card
|
<csat-metric-card
|
||||||
:disabled="ratingFilterEnabled"
|
:disabled="ratingFilterEnabled"
|
||||||
:label="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL')"
|
:label="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL')"
|
||||||
:info-text="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP')"
|
:info-text="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP')"
|
||||||
:value="ratingFilterEnabled ? '--' : formatToPercent(satisfactionScore)"
|
:value="ratingFilterEnabled ? '--' : formatToPercent(satisfactionScore)"
|
||||||
|
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
|
||||||
/>
|
/>
|
||||||
<csat-metric-card
|
<csat-metric-card
|
||||||
:label="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL')"
|
:label="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL')"
|
||||||
:info-text="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP')"
|
:info-text="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP')"
|
||||||
:value="formatToPercent(responseRate)"
|
:value="formatToPercent(responseRate)"
|
||||||
|
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="reportMetricContainer"
|
ref="reportMetricContainer"
|
||||||
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%] m-0 p-4"
|
class="m-0 p-4"
|
||||||
:class="{
|
:class="{
|
||||||
'grayscale pointer-events-none opacity-30': disabled,
|
'grayscale pointer-events-none opacity-30': disabled,
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ export const METRIC_CHART = {
|
|||||||
reply_time: TIME_CHART_CONFIG,
|
reply_time: TIME_CHART_CONFIG,
|
||||||
avg_resolution_time: TIME_CHART_CONFIG,
|
avg_resolution_time: TIME_CHART_CONFIG,
|
||||||
resolutions_count: DEFAULT_CHART,
|
resolutions_count: DEFAULT_CHART,
|
||||||
|
bot_resolutions_count: DEFAULT_CHART,
|
||||||
|
bot_handoffs_count: DEFAULT_CHART,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OVERVIEW_METRICS = {
|
export const OVERVIEW_METRICS = {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const LabelReports = () => import('./LabelReports.vue');
|
|||||||
const InboxReports = () => import('./InboxReports.vue');
|
const InboxReports = () => import('./InboxReports.vue');
|
||||||
const TeamReports = () => import('./TeamReports.vue');
|
const TeamReports = () => import('./TeamReports.vue');
|
||||||
const CsatResponses = () => import('./CsatResponses.vue');
|
const CsatResponses = () => import('./CsatResponses.vue');
|
||||||
|
const BotReports = () => import('./BotReports.vue');
|
||||||
const LiveReports = () => import('./LiveReports.vue');
|
const LiveReports = () => import('./LiveReports.vue');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -66,6 +67,23 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
|
component: SettingsContent,
|
||||||
|
props: {
|
||||||
|
headerTitle: 'BOT_REPORTS.HEADER',
|
||||||
|
icon: 'bot',
|
||||||
|
keepAlive: false,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'bot',
|
||||||
|
name: 'bot_reports',
|
||||||
|
roles: ['administrator'],
|
||||||
|
component: BotReports,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: frontendURL('accounts/:accountId/reports'),
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
component: SettingsContent,
|
component: SettingsContent,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ const state = {
|
|||||||
avg_first_response_time: false,
|
avg_first_response_time: false,
|
||||||
avg_resolution_time: false,
|
avg_resolution_time: false,
|
||||||
resolutions_count: false,
|
resolutions_count: false,
|
||||||
|
bot_resolutions_count: false,
|
||||||
|
bot_handoffs_count: false,
|
||||||
reply_time: false,
|
reply_time: false,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@@ -28,6 +30,8 @@ const state = {
|
|||||||
avg_first_response_time: [],
|
avg_first_response_time: [],
|
||||||
avg_resolution_time: [],
|
avg_resolution_time: [],
|
||||||
resolutions_count: [],
|
resolutions_count: [],
|
||||||
|
bot_resolutions_count: [],
|
||||||
|
bot_handoffs_count: [],
|
||||||
reply_time: [],
|
reply_time: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -39,6 +43,13 @@ const state = {
|
|||||||
outgoing_messages_count: 0,
|
outgoing_messages_count: 0,
|
||||||
reply_time: 0,
|
reply_time: 0,
|
||||||
resolutions_count: 0,
|
resolutions_count: 0,
|
||||||
|
bot_resolutions_count: 0,
|
||||||
|
bot_handoffs_count: 0,
|
||||||
|
previous: {},
|
||||||
|
},
|
||||||
|
botSummary: {
|
||||||
|
bot_resolutions_count: 0,
|
||||||
|
bot_handoffs_count: 0,
|
||||||
previous: {},
|
previous: {},
|
||||||
},
|
},
|
||||||
overview: {
|
overview: {
|
||||||
@@ -60,6 +71,9 @@ const getters = {
|
|||||||
getAccountSummary(_state) {
|
getAccountSummary(_state) {
|
||||||
return _state.accountSummary;
|
return _state.accountSummary;
|
||||||
},
|
},
|
||||||
|
getBotSummary(_state) {
|
||||||
|
return _state.botSummary;
|
||||||
|
},
|
||||||
getAccountConversationMetric(_state) {
|
getAccountConversationMetric(_state) {
|
||||||
return _state.overview.accountConversationMetric;
|
return _state.overview.accountConversationMetric;
|
||||||
},
|
},
|
||||||
@@ -125,6 +139,20 @@ export const actions = {
|
|||||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
fetchBotSummary({ commit }, reportObj) {
|
||||||
|
Report.getBotSummary({
|
||||||
|
from: reportObj.from,
|
||||||
|
to: reportObj.to,
|
||||||
|
groupBy: reportObj.groupBy,
|
||||||
|
businessHours: reportObj.businessHours,
|
||||||
|
})
|
||||||
|
.then(botSummary => {
|
||||||
|
commit(types.default.SET_BOT_SUMMARY, botSummary.data);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
fetchAccountConversationMetric({ commit }, reportObj) {
|
fetchAccountConversationMetric({ commit }, reportObj) {
|
||||||
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
|
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
|
||||||
Report.getConversationMetric(reportObj.type)
|
Report.getConversationMetric(reportObj.type)
|
||||||
@@ -243,6 +271,9 @@ const mutations = {
|
|||||||
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
|
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
|
||||||
_state.accountSummary = summaryData;
|
_state.accountSummary = summaryData;
|
||||||
},
|
},
|
||||||
|
[types.default.SET_BOT_SUMMARY](_state, summaryData) {
|
||||||
|
_state.botSummary = summaryData;
|
||||||
|
},
|
||||||
[types.default.SET_ACCOUNT_CONVERSATION_METRIC](_state, metricData) {
|
[types.default.SET_ACCOUNT_CONVERSATION_METRIC](_state, metricData) {
|
||||||
_state.overview.accountConversationMetric = metricData;
|
_state.overview.accountConversationMetric = metricData;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ export default {
|
|||||||
SET_HEATMAP_DATA: 'SET_HEATMAP_DATA',
|
SET_HEATMAP_DATA: 'SET_HEATMAP_DATA',
|
||||||
TOGGLE_HEATMAP_LOADING: 'TOGGLE_HEATMAP_LOADING',
|
TOGGLE_HEATMAP_LOADING: 'TOGGLE_HEATMAP_LOADING',
|
||||||
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
||||||
|
SET_BOT_SUMMARY: 'SET_BOT_SUMMARY',
|
||||||
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
||||||
SET_ACCOUNT_CONVERSATION_METRIC: 'SET_ACCOUNT_CONVERSATION_METRIC',
|
SET_ACCOUNT_CONVERSATION_METRIC: 'SET_ACCOUNT_CONVERSATION_METRIC',
|
||||||
TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING:
|
TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING:
|
||||||
|
|||||||
Reference in New Issue
Block a user