From fc67edcb6bb02b28b0c9205ccb5a077263a70ef1 Mon Sep 17 00:00:00 2001 From: John Manuel Derecho Date: Sat, 13 Apr 2024 02:25:06 +0800 Subject: [PATCH 1/2] feature: custom report filters --- .../csat_survey_responses_controller.rb | 1 + .../api/v2/accounts/reports_controller.rb | 3 +- app/helpers/report_helper.rb | 118 +++++++++++-- app/javascript/dashboard/api/csatReports.js | 12 +- app/javascript/dashboard/api/reports.js | 18 +- .../settings/reports/CsatResponses.vue | 23 ++- .../dashboard/settings/reports/Index.vue | 28 ++- .../reports/components/FilterSelector.vue | 22 ++- .../reports/components/Filters/Inboxes.vue | 7 + .../reports/components/Filters/Labels.vue | 7 + .../reports/components/Filters/Ratings.vue | 10 +- .../reports/components/Filters/Teams.vue | 7 + .../reports/components/ReportFilters.vue | 163 +++++++++++++++++- .../reports/components/WootReports.vue | 22 ++- .../dashboard/store/modules/reports.js | 6 +- app/models/csat_survey_response.rb | 2 +- 16 files changed, 408 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb index 25d479f0c83b7..45068dbcca279 100644 --- a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb +++ b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb @@ -45,6 +45,7 @@ def set_csat_survey_responses .filter_by_inbox_id(params[:inbox_id]) .filter_by_team_id(params[:team_id]) .filter_by_rating(params[:rating]) + .filter_by_label(params[:label]) end def set_current_page_surveys diff --git a/app/controllers/api/v2/accounts/reports_controller.rb b/app/controllers/api/v2/accounts/reports_controller.rb index c67b74a431f7a..b62ce54b6db7a 100644 --- a/app/controllers/api/v2/accounts/reports_controller.rb +++ b/app/controllers/api/v2/accounts/reports_controller.rb @@ -76,7 +76,8 @@ def common_params type: params[:type].to_sym, id: params[:id], group_by: params[:group_by], - business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]) + business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]), + custom_filter: params[:custom_filter] } end diff --git a/app/helpers/report_helper.rb b/app/helpers/report_helper.rb index 99f3fd36be6b3..cc43a5dfc10f1 100644 --- a/app/helpers/report_helper.rb +++ b/app/helpers/report_helper.rb @@ -16,6 +16,100 @@ def scope end end + def custom_filter(collection) + return collection if collection.blank? + + case collection.model_name.name + when 'Conversation' + filter_conversations(collection) + when 'Message' + filter_messages(collection) + when 'ReportingEvent' + filter_reporting_events(collection) + else + collection + end + end + + def get_filter(key, field) + filter = params.dig(:custom_filter, key) + return [] unless filter.present? + + filter.to_unsafe_h.values + end + + def selected_label + get_filter(:selected_label, :title) + end + + def selected_team + get_filter(:selected_team, :id) + end + + def selected_inbox + get_filter(:selected_inbox, :id) + end + + def selected_rating + get_filter(:selected_rating, :value) + end + + def filter_conversations(collection) + if selected_label.present? + collection = collection.where(cached_label_list: selected_label) + end + + if selected_team.present? + collection = collection.where(team_id: selected_team) + end + + if selected_inbox.present? + collection = collection.where(inbox_id: selected_inbox) + end + + if selected_rating.present? + collection = collection.joins(:csat_survey_responses).where(csat_survey_responses: {rating: selected_rating}) + end + + collection + end + + def filter_messages(collection) + if selected_label.present? + collection = collection.joins(:conversation).where(conversations: {cached_label_list: selected_label}) + end + + if selected_team.present? + collection = collection.joins(:conversation).where(conversations: {team_id: selected_team}) + end + + if selected_inbox.present? + collection = collection.where(inbox_id: selected_inbox) + end + + if selected_rating.present? + collection = collection.joins(:csat_survey_response).where(csat_survey_responses: {rating: selected_rating}) + end + + collection + end + + def filter_reporting_events(collection) + if selected_label.present? + collection = collection.joins(:conversation).where(conversations: {cached_label_list: selected_label}) + end + + if selected_team.present? + collection = collection.joins(:conversation).where(conversations: {team_id: selected_team}) + end + + if selected_inbox.present? + collection = collection.where(inbox_id: selected_inbox) + end + + collection + end + def conversations_count (get_grouped_values conversations).count end @@ -41,55 +135,55 @@ def bot_handoffs_count end def conversations - scope.conversations.where(account_id: account.id, created_at: range) + custom_filter(scope.conversations).where(account_id: account.id, created_at: range) end def incoming_messages - scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order) + custom_filter(scope.messages).where(account_id: account.id, created_at: range).incoming.unscope(:order) end def outgoing_messages - scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order) + custom_filter(scope.messages).where(account_id: account.id, created_at: range).outgoing.unscope(:order) end def resolutions - scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved, + custom_filter(scope.reporting_events).joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_resolved, conversations: { status: :resolved }, created_at: range).distinct end def bot_resolutions - scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_resolved, + custom_filter(scope.reporting_events).joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_resolved, conversations: { status: :resolved }, created_at: range).distinct end def bot_handoffs - scope.reporting_events.joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_handoff, + custom_filter(scope.reporting_events).joins(:conversation).select(:conversation_id).where(account_id: account.id, name: :conversation_bot_handoff, created_at: range).distinct end def avg_first_response_time - grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response', account_id: account.id)) + grouped_reporting_events = (get_grouped_values custom_filter(scope.reporting_events).where(name: 'first_response', account_id: account.id)) return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] 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)) + grouped_reporting_events = (get_grouped_values custom_filter(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)) + grouped_reporting_events = (get_grouped_values custom_filter(scope.reporting_events).where(name: 'conversation_resolved', 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_summary - reporting_events = scope.reporting_events + reporting_events = custom_filter(scope.reporting_events) .where(name: 'conversation_resolved', account_id: account.id, created_at: range) avg_rt = if params[:business_hours].present? reporting_events.average(:value_in_business_hours) @@ -103,7 +197,7 @@ def avg_resolution_time_summary end def reply_time_summary - reporting_events = scope.reporting_events + reporting_events = custom_filter(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) @@ -113,7 +207,7 @@ def reply_time_summary end def avg_first_response_time_summary - reporting_events = scope.reporting_events + reporting_events = custom_filter(scope.reporting_events) .where(name: 'first_response', account_id: account.id, created_at: range) avg_frt = if params[:business_hours].present? reporting_events.average(:value_in_business_hours) diff --git a/app/javascript/dashboard/api/csatReports.js b/app/javascript/dashboard/api/csatReports.js index 8dc5e44421b52..1c4d771ab2cf8 100644 --- a/app/javascript/dashboard/api/csatReports.js +++ b/app/javascript/dashboard/api/csatReports.js @@ -15,6 +15,7 @@ class CSATReportsAPI extends ApiClient { team_id, rating, question_id, + label, } = {}) { return axios.get(this.url, { params: { @@ -27,11 +28,12 @@ class CSATReportsAPI extends ApiClient { team_id, rating, question_id, + label, }, }); } - getQuestions({ from, to, user_ids, inbox_id, team_id, rating } = {}) { + getQuestions({ from, to, user_ids, inbox_id, team_id, rating, label } = {}) { return axios.get(`${this.url}/questions`, { params: { since: from, @@ -41,11 +43,12 @@ class CSATReportsAPI extends ApiClient { inbox_id, team_id, rating, + label }, }); } - download({ from, to, user_ids, inbox_id, team_id, rating } = {}) { + download({ from, to, user_ids, inbox_id, team_id, rating, label } = {}) { return axios.get(`${this.url}/download`, { params: { since: from, @@ -55,14 +58,15 @@ class CSATReportsAPI extends ApiClient { inbox_id, team_id, rating, + label }, }); } - getMetrics({ from, to, user_ids, inbox_id, team_id, rating } = {}) { + getMetrics({ from, to, user_ids, inbox_id, team_id, rating, label } = {}) { // no ratings for metrics return axios.get(`${this.url}/metrics`, { - params: { since: from, until: to, user_ids, inbox_id, team_id, rating }, + params: { since: from, until: to, user_ids, inbox_id, team_id, rating, label }, }); } } diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index 52fa7f444d761..30b87654d1538 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -13,6 +13,10 @@ class ReportsAPI extends ApiClient { from, to, type = 'account', + selectedLabel, + selectedTeam, + selectedInbox, + selectedRating, id, groupBy, businessHours, @@ -22,6 +26,12 @@ class ReportsAPI extends ApiClient { metric, since: from, until: to, + custom_filter: { + selected_label: selectedLabel, + selected_team: selectedTeam, + selected_inbox: selectedInbox, + selected_rating: selectedRating + }, type, id, group_by: groupBy, @@ -32,11 +42,17 @@ class ReportsAPI extends ApiClient { } // eslint-disable-next-line default-param-last - getSummary(since, until, type = 'account', id, groupBy, businessHours) { + getSummary(since, until, type = 'account', selectedLabel, selectedTeam, selectedInbox, selectedRating, id, groupBy, businessHours) { return axios.get(`${this.url}/summary`, { params: { since, until, + custom_filter: { + selected_label: selectedLabel, + selected_team: selectedTeam, + selected_inbox: selectedInbox, + selected_rating: selectedRating + }, type, id, group_by: groupBy, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue index 516ed06c84bc4..15c97c4d9ca98 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue @@ -4,9 +4,14 @@ :show-agents-filter="true" :show-inbox-filter="true" :show-rating-filter="true" + :show-labels-filter="true" :show-team-filter="isTeamsEnabled" :show-business-hours-switch="false" @filter-change="onFilterChange" + :multiple-labels="true" + :multiple-teams="true" + :multiple-inboxes="true" + :multiple-ratings="true" /> el.id); - this.inbox = selectedInbox?.id; - this.team = selectedTeam?.id; - this.rating = selectedRating?.value; + this.userIds = selectedAgents && selectedAgents.map(el => el.id); + this.inbox = selectedInbox &&selectedInbox.map(el => el.id); + this.team = selectedTeam && selectedTeam.map(el => el.id); + this.rating = selectedRating && selectedRating.map(el => el.value); + this.label = selectedLabel && selectedLabel.map(el => el.title); this.getAllData(); }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue index 528053c575f60..608508e9081a7 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue @@ -9,8 +9,16 @@ {{ $t('REPORT.DOWNLOAD_AGENT_REPORTS') }} @@ -49,6 +57,10 @@ export default { return { from: 0, to: 0, + selectedLabel: [], + selectedTeam: [], + selectedInbox: [], + selectedRating: [], groupBy: GROUP_BY_FILTER[1], businessHours: false, }; @@ -67,7 +79,7 @@ export default { fetchAccountSummary() { try { this.$store.dispatch('fetchAccountSummary', this.getRequestPayload()); - } catch { + } catch (error) { this.showAlert(this.$t('REPORT.SUMMARY_FETCHING_FAILED')); } }, @@ -92,11 +104,15 @@ export default { }); }, getRequestPayload() { - const { from, to, groupBy, businessHours } = this; + const { from, to, selectedLabel, selectedTeam, selectedInbox, selectedRating, groupBy, businessHours } = this; return { from, to, + selectedLabel, + selectedTeam, + selectedInbox, + selectedRating, groupBy: groupBy?.period, businessHours, }; @@ -109,15 +125,19 @@ export default { )}.csv`; this.$store.dispatch('downloadAgentReports', { from, to, fileName }); }, - onFilterChange({ from, to, groupBy, businessHours }) { + onFilterChange({ from, to, selectedLabel, selectedTeam, selectedInbox, selectedRating, groupBy, businessHours }) { this.from = from; this.to = to; + this.selectedLabel = selectedLabel && selectedLabel.map(label => label.title); + this.selectedTeam = selectedTeam && selectedTeam.map(team => team.id); + this.selectedInbox = selectedInbox && selectedInbox.map(inbox => inbox.id); + this.selectedRating = selectedRating && selectedRating.map(rating => rating.value) this.groupBy = groupBy; this.businessHours = businessHours; this.fetchAllData(); this.$track(REPORTS_EVENTS.FILTER_REPORT, { - filterValue: { from, to, groupBy, businessHours }, + filterValue: { from, to, selectedLabel, selectedTeam, selectedInbox, selectedRating, groupBy, businessHours }, reportType: 'conversations', }); }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue index 84bae6198df17..ffda5079055ca 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue @@ -24,21 +24,25 @@ -
+
{{ $t('REPORT.BUSINESS_HOURS') }} @@ -105,6 +109,22 @@ export default { type: Boolean, default: true, }, + multipleInboxes: { + type: Boolean, + default: false, + }, + multipleLabels: { + type: Boolean, + default: false, + }, + multipleTeams: { + type: Boolean, + default: false, + }, + multipleRatings: { + type: Boolean, + default: false, + } }, data() { return { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue index 652cb06a65667..b26b763f9ae4e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Inboxes.vue @@ -10,6 +10,7 @@ :option-height="24" :show-labels="false" @input="handleInput" + :multiple="multiple" />
@@ -18,6 +19,12 @@ import { mapGetters } from 'vuex'; export default { name: 'ReportsFiltersInboxes', + props: { + multiple: { + type: Boolean, + default: false, + } + }, data() { return { selectedOption: null, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue index d3287950774d5..a805f39027358 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Labels.vue @@ -10,6 +10,7 @@ :option-height="24" :show-labels="false" @input="handleInput" + :multiple="multiple" > @@ -19,6 +20,12 @@ import { CSAT_RATINGS } from 'shared/constants/messages'; export default { name: 'ReportFiltersRatings', + props: { + multiple: { + type: Boolean, + default: false, + } + }, data() { const translatedOptions = CSAT_RATINGS.reverse().map(option => ({ ...option, @@ -28,6 +35,7 @@ export default { return { selectedOption: null, options: translatedOptions, + selectRating: 'Select CSAT Rating' }; }, methods: { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue index f834a9e719cbd..c5ee3819b650f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/Filters/Teams.vue @@ -10,6 +10,7 @@ :option-height="24" :show-labels="false" @input="handleInput" + :multiple="multiple" />
@@ -18,6 +19,12 @@ import { mapGetters } from 'vuex'; export default { name: 'ReportsFiltersTeams', + props: { + multiple: { + type: Boolean, + default: false, + } + }, data() { return { selectedOption: null, diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue index 88eae0541bbe9..67892eef44df1 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue @@ -1,9 +1,9 @@ -
+