From 27419eef66de5dd0aba860783cab02412d14a24a Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Thu, 20 Jul 2023 12:01:22 -0700 Subject: [PATCH] feat: Add report on customer waiting time (#7545) --- app/builders/v2/report_builder.rb | 5 +- app/helpers/report_helper.rb | 17 +++ .../dashboard/i18n/locale/en/report.json | 4 + .../dashboard/mixins/reportMixin.js | 22 ++-- .../dashboard/settings/reports/Index.vue | 2 + .../settings/reports/ReportContainer.vue | 2 + .../reports/components/WootReports.vue | 2 + .../dashboard/settings/reports/constants.js | 106 +++++++----------- .../dashboard/store/modules/reports.js | 3 + 9 files changed, 81 insertions(+), 82 deletions(-) diff --git a/app/builders/v2/report_builder.rb b/app/builders/v2/report_builder.rb index b0f6ba25e..c96950ee9 100644 --- a/app/builders/v2/report_builder.rb +++ b/app/builders/v2/report_builder.rb @@ -20,7 +20,7 @@ class V2::ReportBuilder # For backward compatible with old report def build - if %w[avg_first_response_time avg_resolution_time].include?(params[:metric]) + if %w[avg_first_response_time avg_resolution_time reply_time].include?(params[:metric]) timeseries.each_with_object([]) do |p, arr| arr << { value: p[1], timestamp: p[0].in_time_zone(@timezone).to_i, count: @grouped_values.count[p[0]] } end @@ -38,7 +38,8 @@ class V2::ReportBuilder outgoing_messages_count: outgoing_messages.count, avg_first_response_time: avg_first_response_time_summary, avg_resolution_time: avg_resolution_time_summary, - resolutions_count: resolutions.count + resolutions_count: resolutions.count, + reply_time: reply_time_summary } end diff --git a/app/helpers/report_helper.rb b/app/helpers/report_helper.rb index 796986e5b..2b0f43b3f 100644 --- a/app/helpers/report_helper.rb +++ b/app/helpers/report_helper.rb @@ -56,6 +56,13 @@ module ReportHelper grouped_reporting_events.average(:value) end + def reply_time + grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'reply_time', account_id: account.id)) + return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] + + grouped_reporting_events.average(:value) + end + def avg_resolution_time grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id)) return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] @@ -77,6 +84,16 @@ module ReportHelper avg_rt end + def reply_time_summary + reporting_events = scope.reporting_events + .where(name: 'reply_time', account_id: account.id, created_at: range) + reply_time = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) + + return 0 if reply_time.blank? + + reply_time + end + def avg_first_response_time_summary reporting_events = scope.reporting_events .where(name: 'first_response', account_id: account.id, created_at: range) diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json index f384adad7..0b234ec16 100644 --- a/app/javascript/dashboard/i18n/locale/en/report.json +++ b/app/javascript/dashboard/i18n/locale/en/report.json @@ -34,6 +34,10 @@ "RESOLUTION_COUNT": { "NAME": "Resolution Count", "DESC": "( Total )" + }, + "REPLY_TIME": { + "NAME": "Customer waiting time", + "TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} conversations)" } }, "DATE_RANGE_OPTIONS": { diff --git a/app/javascript/dashboard/mixins/reportMixin.js b/app/javascript/dashboard/mixins/reportMixin.js index 3a53f1196..2b8a5f87d 100644 --- a/app/javascript/dashboard/mixins/reportMixin.js +++ b/app/javascript/dashboard/mixins/reportMixin.js @@ -7,19 +7,13 @@ export default { accountSummary: 'getAccountSummary', accountReport: 'getAccountReports', }), - calculateTrend() { - return metric_key => { - if (!this.accountSummary.previous[metric_key]) return 0; - const diff = - this.accountSummary[metric_key] - - this.accountSummary.previous[metric_key]; - return Math.round( - (diff / this.accountSummary.previous[metric_key]) * 100 - ); - }; - }, }, methods: { + calculateTrend(key) { + if (!this.accountSummary.previous[key]) return 0; + const diff = this.accountSummary[key] - this.accountSummary.previous[key]; + return Math.round((diff / this.accountSummary.previous[key]) * 100); + }, displayMetric(key) { if (this.isAverageMetricType(key)) { return formatTime(this.accountSummary[key]); @@ -39,7 +33,11 @@ export default { return ''; }, isAverageMetricType(key) { - return ['avg_first_response_time', 'avg_resolution_time'].includes(key); + return [ + 'avg_first_response_time', + 'avg_resolution_time', + 'reply_time', + ].includes(key); }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue index b04216bff..4ab4a8216 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue @@ -35,6 +35,7 @@ const REPORTS_KEYS = { FIRST_RESPONSE_TIME: 'avg_first_response_time', RESOLUTION_TIME: 'avg_resolution_time', RESOLUTION_COUNT: 'resolutions_count', + REPLY_TIME: 'reply_time', }; export default { @@ -78,6 +79,7 @@ export default { 'FIRST_RESPONSE_TIME', 'RESOLUTION_TIME', 'RESOLUTION_COUNT', + 'REPLY_TIME', ].forEach(async key => { try { await this.$store.dispatch('fetchAccountReport', { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue index 0fa504a36..3dbd4eb57 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue @@ -44,6 +44,7 @@ const REPORTS_KEYS = { FIRST_RESPONSE_TIME: 'avg_first_response_time', RESOLUTION_TIME: 'avg_resolution_time', RESOLUTION_COUNT: 'resolutions_count', + REPLY_TIME: 'reply_time', }; export default { @@ -60,6 +61,7 @@ export default { const reportKeys = [ 'CONVERSATIONS', 'FIRST_RESPONSE_TIME', + 'REPLY_TIME', 'RESOLUTION_TIME', 'RESOLUTION_COUNT', 'INCOMING_MESSAGES', diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue index fdac063ba..6ed2545b6 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -38,6 +38,7 @@ const REPORTS_KEYS = { FIRST_RESPONSE_TIME: 'avg_first_response_time', RESOLUTION_TIME: 'avg_resolution_time', RESOLUTION_COUNT: 'resolutions_count', + REPLY_TIME: 'reply_time', }; export default { @@ -106,6 +107,7 @@ export default { 'FIRST_RESPONSE_TIME', 'RESOLUTION_TIME', 'RESOLUTION_COUNT', + 'REPLY_TIME', ].forEach(async key => { try { const { from, to, groupBy, businessHours } = this; diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js b/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js index cc23529b8..f4eb5a6dc 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js @@ -151,78 +151,48 @@ export const DEFAULT_CHART = { }, }; +const TIME_CHART_CONFIG = { + datasets: [DEFAULT_BAR_CHART], + scales: { + xAxes: [ + { + ticks: { + fontFamily: CHART_FONT_FAMILY, + }, + gridLines: { + drawOnChartArea: false, + }, + }, + ], + yAxes: [ + { + id: 'y-left', + type: 'linear', + position: 'left', + ticks: { + fontFamily: CHART_FONT_FAMILY, + callback: (value, index, values) => { + if (!index || index === values.length - 1) { + return formatTime(value); + } + return ''; + }, + }, + gridLines: { + drawOnChartArea: false, + }, + }, + ], + }, +}; + export const METRIC_CHART = { conversations_count: DEFAULT_CHART, incoming_messages_count: DEFAULT_CHART, outgoing_messages_count: DEFAULT_CHART, - avg_first_response_time: { - datasets: [DEFAULT_BAR_CHART], - scales: { - xAxes: [ - { - ticks: { - fontFamily: CHART_FONT_FAMILY, - }, - gridLines: { - drawOnChartArea: false, - }, - }, - ], - yAxes: [ - { - id: 'y-left', - type: 'linear', - position: 'left', - ticks: { - fontFamily: CHART_FONT_FAMILY, - callback: (value, index, values) => { - if (!index || index === values.length - 1) { - return formatTime(value); - } - return ''; - }, - }, - gridLines: { - drawOnChartArea: false, - }, - }, - ], - }, - }, - avg_resolution_time: { - datasets: [DEFAULT_BAR_CHART], - scales: { - xAxes: [ - { - ticks: { - fontFamily: CHART_FONT_FAMILY, - }, - gridLines: { - drawOnChartArea: false, - }, - }, - ], - yAxes: [ - { - id: 'y-left', - type: 'linear', - position: 'left', - ticks: { - fontFamily: CHART_FONT_FAMILY, - callback: (value, index, values) => { - if (!index || index === values.length - 1) { - return formatTime(value); - } - return ''; - }, - }, - gridLines: { - drawOnChartArea: false, - }, - }, - ], - }, - }, + avg_first_response_time: TIME_CHART_CONFIG, + reply_time: TIME_CHART_CONFIG, + avg_resolution_time: TIME_CHART_CONFIG, resolutions_count: DEFAULT_CHART, }; diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js index 7f51eaf42..b529730fe 100644 --- a/app/javascript/dashboard/store/modules/reports.js +++ b/app/javascript/dashboard/store/modules/reports.js @@ -19,6 +19,7 @@ const state = { avg_first_response_time: false, avg_resolution_time: false, resolutions_count: false, + reply_time: false, }, data: { conversations_count: [], @@ -27,6 +28,7 @@ const state = { avg_first_response_time: [], avg_resolution_time: [], resolutions_count: [], + reply_time: [], }, }, accountSummary: { @@ -35,6 +37,7 @@ const state = { conversations_count: 0, incoming_messages_count: 0, outgoing_messages_count: 0, + reply_time: 0, resolutions_count: 0, previous: {}, },