mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	feat: Consider business hours while generating the reports (#4330)
* feat: Consider business hours while generating the reports
This commit is contained in:
		
							
								
								
									
										3
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -125,6 +125,9 @@ gem 'procore-sift' | |||||||
| gem 'email_reply_trimmer' | gem 'email_reply_trimmer' | ||||||
| gem 'html2text' | gem 'html2text' | ||||||
|  |  | ||||||
|  | # to calculate working hours | ||||||
|  | gem 'working_hours' | ||||||
|  |  | ||||||
| group :production, :staging do | group :production, :staging do | ||||||
|   # we dont want request timing out in development while using byebug |   # we dont want request timing out in development while using byebug | ||||||
|   gem 'rack-timeout' |   gem 'rack-timeout' | ||||||
|   | |||||||
| @@ -636,6 +636,9 @@ GEM | |||||||
|       websocket-extensions (>= 0.1.0) |       websocket-extensions (>= 0.1.0) | ||||||
|     websocket-extensions (0.1.5) |     websocket-extensions (0.1.5) | ||||||
|     wisper (2.0.0) |     wisper (2.0.0) | ||||||
|  |     working_hours (1.4.1) | ||||||
|  |       activesupport (>= 3.2) | ||||||
|  |       tzinfo | ||||||
|     zeitwerk (2.5.4) |     zeitwerk (2.5.4) | ||||||
|  |  | ||||||
| PLATFORMS | PLATFORMS | ||||||
| @@ -746,6 +749,7 @@ DEPENDENCIES | |||||||
|   webpacker (~> 5.x) |   webpacker (~> 5.x) | ||||||
|   webpush |   webpush | ||||||
|   wisper (= 2.0.0) |   wisper (= 2.0.0) | ||||||
|  |   working_hours | ||||||
|  |  | ||||||
| RUBY VERSION | RUBY VERSION | ||||||
|    ruby 3.0.2p107 |    ruby 3.0.2p107 | ||||||
|   | |||||||
| @@ -47,36 +47,36 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController | |||||||
|     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? |     raise Pundit::NotAuthorizedError unless Current.account_user.administrator? | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def current_summary_params |   def common_params | ||||||
|     { |     { | ||||||
|       type: params[:type].to_sym, |       type: params[:type].to_sym, | ||||||
|       id: params[:id], |       id: params[:id], | ||||||
|       since: range[:current][:since], |       group_by: params[:group_by], | ||||||
|       until: range[:current][:until], |       business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]) | ||||||
|       group_by: params[:group_by] |  | ||||||
|     } |     } | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def current_summary_params | ||||||
|  |     common_params.merge({ | ||||||
|  |                           since: range[:current][:since], | ||||||
|  |                           until: range[:current][:until] | ||||||
|  |                         }) | ||||||
|  |   end | ||||||
|  |  | ||||||
|   def previous_summary_params |   def previous_summary_params | ||||||
|     { |     common_params.merge({ | ||||||
|       type: params[:type].to_sym, |  | ||||||
|       id: params[:id], |  | ||||||
|                           since: range[:previous][:since], |                           since: range[:previous][:since], | ||||||
|       until: range[:previous][:until], |                           until: range[:previous][:until] | ||||||
|       group_by: params[:group_by] |                         }) | ||||||
|     } |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def report_params |   def report_params | ||||||
|     { |     common_params.merge({ | ||||||
|                           metric: params[:metric], |                           metric: params[:metric], | ||||||
|       type: params[:type].to_sym, |  | ||||||
|                           since: params[:since], |                           since: params[:since], | ||||||
|                           until: params[:until], |                           until: params[:until], | ||||||
|       id: params[:id], |  | ||||||
|       group_by: params[:group_by], |  | ||||||
|                           timezone_offset: params[:timezone_offset] |                           timezone_offset: params[:timezone_offset] | ||||||
|     } |                         }) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def conversation_params |   def conversation_params | ||||||
|   | |||||||
| @@ -33,17 +33,23 @@ module ReportHelper | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def avg_first_response_time |   def avg_first_response_time | ||||||
|     (get_grouped_values scope.reporting_events.where(name: 'first_response')).average(:value) |     grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response')) | ||||||
|  |     return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] | ||||||
|  |  | ||||||
|  |     grouped_reporting_events.average(:value) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def avg_resolution_time |   def avg_resolution_time | ||||||
|     (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')).average(:value) |     grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')) | ||||||
|  |     return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours] | ||||||
|  |  | ||||||
|  |     grouped_reporting_events.average(:value) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def avg_resolution_time_summary |   def avg_resolution_time_summary | ||||||
|     avg_rt = scope.reporting_events |     reporting_events = scope.reporting_events | ||||||
|                             .where(name: 'conversation_resolved', created_at: range) |                             .where(name: 'conversation_resolved', created_at: range) | ||||||
|                   .average(:value) |     avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) | ||||||
|  |  | ||||||
|     return 0 if avg_rt.blank? |     return 0 if avg_rt.blank? | ||||||
|  |  | ||||||
| @@ -51,9 +57,9 @@ module ReportHelper | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def avg_first_response_time_summary |   def avg_first_response_time_summary | ||||||
|     avg_frt = scope.reporting_events |     reporting_events = scope.reporting_events | ||||||
|                             .where(name: 'first_response', created_at: range) |                             .where(name: 'first_response', created_at: range) | ||||||
|                    .average(:value) |     avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value) | ||||||
|  |  | ||||||
|     return 0 if avg_frt.blank? |     return 0 if avg_frt.blank? | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								app/helpers/reporting_event_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/helpers/reporting_event_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | module ReportingEventHelper | ||||||
|  |   def business_hours(inbox, from, to) | ||||||
|  |     return 0 unless inbox.working_hours_enabled? | ||||||
|  |  | ||||||
|  |     inbox_working_hours = configure_working_hours(inbox.working_hours) | ||||||
|  |     return 0 if inbox_working_hours.blank? | ||||||
|  |  | ||||||
|  |     # Configure working hours | ||||||
|  |     WorkingHours::Config.working_hours = inbox_working_hours | ||||||
|  |  | ||||||
|  |     # Configure timezone | ||||||
|  |     WorkingHours::Config.time_zone = inbox.timezone | ||||||
|  |  | ||||||
|  |     # Use inbox timezone to change from & to values. | ||||||
|  |     from_in_inbox_timezone = from.in_time_zone(inbox.timezone).to_time | ||||||
|  |     to_in_inbox_timezone = to.in_time_zone(inbox.timezone).to_time | ||||||
|  |     from_in_inbox_timezone.working_time_until(to_in_inbox_timezone) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def configure_working_hours(working_hours) | ||||||
|  |     working_hours.each_with_object({}) do |working_hour, object| | ||||||
|  |       object[day(working_hour.day_of_week)] = working_hour_range(working_hour) unless working_hour.closed_all_day? | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def day(day_of_week) | ||||||
|  |     week_days = { | ||||||
|  |       0 => :sun, | ||||||
|  |       1 => :mon, | ||||||
|  |       2 => :tue, | ||||||
|  |       3 => :wed, | ||||||
|  |       4 => :thu, | ||||||
|  |       5 => :fri, | ||||||
|  |       6 => :sat | ||||||
|  |     } | ||||||
|  |     week_days[day_of_week] | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def working_hour_range(working_hour) | ||||||
|  |     { format_time(working_hour.open_hour, working_hour.open_minutes) => format_time(working_hour.close_hour, working_hour.close_minutes) } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def format_time(hour, minute) | ||||||
|  |     hour = hour < 10 ? "0#{hour}" : hour | ||||||
|  |     minute = minute < 10 ? "0#{minute}" : minute | ||||||
|  |     "#{hour}:#{minute}" | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -8,7 +8,15 @@ class ReportsAPI extends ApiClient { | |||||||
|     super('reports', { accountScoped: true, apiVersion: 'v2' }); |     super('reports', { accountScoped: true, apiVersion: 'v2' }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getReports(metric, since, until, type = 'account', id, group_by) { |   getReports( | ||||||
|  |     metric, | ||||||
|  |     since, | ||||||
|  |     until, | ||||||
|  |     type = 'account', | ||||||
|  |     id, | ||||||
|  |     group_by, | ||||||
|  |     business_hours | ||||||
|  |   ) { | ||||||
|     return axios.get(`${this.url}`, { |     return axios.get(`${this.url}`, { | ||||||
|       params: { |       params: { | ||||||
|         metric, |         metric, | ||||||
| @@ -17,12 +25,13 @@ class ReportsAPI extends ApiClient { | |||||||
|         type, |         type, | ||||||
|         id, |         id, | ||||||
|         group_by, |         group_by, | ||||||
|  |         business_hours, | ||||||
|         timezone_offset: getTimeOffset(), |         timezone_offset: getTimeOffset(), | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getSummary(since, until, type = 'account', id, group_by) { |   getSummary(since, until, type = 'account', id, group_by, business_hours) { | ||||||
|     return axios.get(`${this.url}/summary`, { |     return axios.get(`${this.url}/summary`, { | ||||||
|       params: { |       params: { | ||||||
|         since, |         since, | ||||||
| @@ -30,6 +39,7 @@ class ReportsAPI extends ApiClient { | |||||||
|         type, |         type, | ||||||
|         id, |         id, | ||||||
|         group_by, |         group_by, | ||||||
|  |         business_hours, | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -78,5 +78,10 @@ | |||||||
|       font-size: $font-size-default; |       font-size: $font-size-default; | ||||||
|       color: $color-gray; |       color: $color-gray; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     .business-hours { | ||||||
|  |       margin: $space-normal; | ||||||
|  |       text-align: center; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,3 +25,21 @@ | |||||||
|   align-items: center; |   align-items: center; | ||||||
|   display: flex; |   display: flex; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .business-hours { | ||||||
|  |   align-items: center; | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: end; | ||||||
|  |   margin-bottom: var(--space-normal); | ||||||
|  |   margin-left: auto; | ||||||
|  |   padding-right: var(--space-normal); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .business-hours-text { | ||||||
|  |   font-size: var(--font-size-small); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .switch { | ||||||
|  |   margin-bottom: var(--space-zero); | ||||||
|  |   margin-left: var(--space-small); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -78,7 +78,8 @@ | |||||||
|       { "id": 2, "groupBy": "Week" }, |       { "id": 2, "groupBy": "Week" }, | ||||||
|       { "id": 3, "groupBy": "Month" }, |       { "id": 3, "groupBy": "Month" }, | ||||||
|       { "id": 4, "groupBy": "Year" } |       { "id": 4, "groupBy": "Year" } | ||||||
|     ] |     ], | ||||||
|  |     "BUSINESS_HOURS": "Business Hours" | ||||||
|   }, |   }, | ||||||
|   "AGENT_REPORTS": { |   "AGENT_REPORTS": { | ||||||
|     "HEADER": "Agents Overview", |     "HEADER": "Agents Overview", | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ | |||||||
|       :filter-items-list="filterItemsList" |       :filter-items-list="filterItemsList" | ||||||
|       @date-range-change="onDateRangeChange" |       @date-range-change="onDateRangeChange" | ||||||
|       @filter-change="onFilterChange" |       @filter-change="onFilterChange" | ||||||
|  |       @business-hours-toggle="onBusinessHoursToggle" | ||||||
|     /> |     /> | ||||||
|     <div class="row"> |     <div class="row"> | ||||||
|       <woot-report-stats-card |       <woot-report-stats-card | ||||||
| @@ -79,6 +80,7 @@ export default { | |||||||
|       groupBy: GROUP_BY_FILTER[1], |       groupBy: GROUP_BY_FILTER[1], | ||||||
|       filterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'), |       filterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'), | ||||||
|       selectedGroupByFilter: {}, |       selectedGroupByFilter: {}, | ||||||
|  |       businessHours: false, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -166,21 +168,23 @@ export default { | |||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     fetchAllData() { |     fetchAllData() { | ||||||
|       const { from, to, groupBy } = this; |       const { from, to, groupBy, businessHours } = this; | ||||||
|       this.$store.dispatch('fetchAccountSummary', { |       this.$store.dispatch('fetchAccountSummary', { | ||||||
|         from, |         from, | ||||||
|         to, |         to, | ||||||
|         groupBy: groupBy.period, |         groupBy: groupBy.period, | ||||||
|  |         businessHours, | ||||||
|       }); |       }); | ||||||
|       this.fetchChartData(); |       this.fetchChartData(); | ||||||
|     }, |     }, | ||||||
|     fetchChartData() { |     fetchChartData() { | ||||||
|       const { from, to, groupBy } = this; |       const { from, to, groupBy, businessHours } = this; | ||||||
|       this.$store.dispatch('fetchAccountReport', { |       this.$store.dispatch('fetchAccountReport', { | ||||||
|         metric: this.metrics[this.currentSelection].KEY, |         metric: this.metrics[this.currentSelection].KEY, | ||||||
|         from, |         from, | ||||||
|         to, |         to, | ||||||
|         groupBy: groupBy.period, |         groupBy: groupBy.period, | ||||||
|  |         businessHours, | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     downloadAgentReports() { |     downloadAgentReports() { | ||||||
| @@ -226,6 +230,10 @@ export default { | |||||||
|           return this.$t('REPORT.GROUP_BY_DAY_OPTIONS'); |           return this.$t('REPORT.GROUP_BY_DAY_OPTIONS'); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     onBusinessHoursToggle(value) { | ||||||
|  |       this.businessHours = value; | ||||||
|  |       this.fetchAllData(); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -61,6 +61,12 @@ | |||||||
|         @input="handleAgentsFilterSelection" |         @input="handleAgentsFilterSelection" | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="small-12 medium-3 business-hours"> | ||||||
|  |       <span class="business-hours-text">{{ $t('REPORT.BUSINESS_HOURS') }}</span> | ||||||
|  |       <span> | ||||||
|  |         <woot-switch v-model="businessHoursSelected" /> | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| <script> | <script> | ||||||
| @@ -105,6 +111,7 @@ export default { | |||||||
|       customDateRange: [new Date(), new Date()], |       customDateRange: [new Date(), new Date()], | ||||||
|       currentSelectedFilter: null, |       currentSelectedFilter: null, | ||||||
|       selectedAgents: [], |       selectedAgents: [], | ||||||
|  |       businessHoursSelected: false, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -153,6 +160,9 @@ export default { | |||||||
|     filterItemsList() { |     filterItemsList() { | ||||||
|       this.currentSelectedFilter = this.selectedGroupByFilter; |       this.currentSelectedFilter = this.selectedGroupByFilter; | ||||||
|     }, |     }, | ||||||
|  |     businessHoursSelected() { | ||||||
|  |       this.$emit('business-hours-toggle', this.businessHoursSelected); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.onDateRangeChange(); |     this.onDateRangeChange(); | ||||||
|   | |||||||
| @@ -145,6 +145,12 @@ | |||||||
|         @input="changeGroupByFilterSelection" |         @input="changeGroupByFilterSelection" | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="small-12 medium-3 business-hours"> | ||||||
|  |       <span class="business-hours-text">{{ $t('REPORT.BUSINESS_HOURS') }}</span> | ||||||
|  |       <span> | ||||||
|  |         <woot-switch v-model="businessHoursSelected" /> | ||||||
|  |       </span> | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
| <script> | <script> | ||||||
| @@ -188,6 +194,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()], | ||||||
|       currentSelectedGroupByFilter: null, |       currentSelectedGroupByFilter: null, | ||||||
|  |       businessHoursSelected: false, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -249,6 +256,9 @@ export default { | |||||||
|     groupByFilterItemsList() { |     groupByFilterItemsList() { | ||||||
|       this.currentSelectedGroupByFilter = this.selectedGroupByFilter; |       this.currentSelectedGroupByFilter = this.selectedGroupByFilter; | ||||||
|     }, |     }, | ||||||
|  |     businessHoursSelected() { | ||||||
|  |       this.$emit('business-hours-toggle', this.businessHoursSelected); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   mounted() { |   mounted() { | ||||||
|     this.onDateRangeChange(); |     this.onDateRangeChange(); | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|       @date-range-change="onDateRangeChange" |       @date-range-change="onDateRangeChange" | ||||||
|       @filter-change="onFilterChange" |       @filter-change="onFilterChange" | ||||||
|       @group-by-filter-change="onGroupByFilterChange" |       @group-by-filter-change="onGroupByFilterChange" | ||||||
|  |       @business-hours-toggle="onBusinessHoursToggle" | ||||||
|     /> |     /> | ||||||
|     <div> |     <div> | ||||||
|       <div v-if="filterItemsList.length" class="row"> |       <div v-if="filterItemsList.length" class="row"> | ||||||
| @@ -100,6 +101,7 @@ export default { | |||||||
|       groupBy: GROUP_BY_FILTER[1], |       groupBy: GROUP_BY_FILTER[1], | ||||||
|       groupByfilterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'), |       groupByfilterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'), | ||||||
|       selectedGroupByFilter: null, |       selectedGroupByFilter: null, | ||||||
|  |       businessHours: false, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
| @@ -202,19 +204,20 @@ export default { | |||||||
|   methods: { |   methods: { | ||||||
|     fetchAllData() { |     fetchAllData() { | ||||||
|       if (this.selectedFilter) { |       if (this.selectedFilter) { | ||||||
|         const { from, to, groupBy } = this; |         const { from, to, groupBy, businessHours } = this; | ||||||
|         this.$store.dispatch('fetchAccountSummary', { |         this.$store.dispatch('fetchAccountSummary', { | ||||||
|           from, |           from, | ||||||
|           to, |           to, | ||||||
|           type: this.type, |           type: this.type, | ||||||
|           id: this.selectedFilter.id, |           id: this.selectedFilter.id, | ||||||
|           groupBy: groupBy.period, |           groupBy: groupBy.period, | ||||||
|  |           businessHours, | ||||||
|         }); |         }); | ||||||
|         this.fetchChartData(); |         this.fetchChartData(); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     fetchChartData() { |     fetchChartData() { | ||||||
|       const { from, to, groupBy } = this; |       const { from, to, groupBy, businessHours } = this; | ||||||
|       this.$store.dispatch('fetchAccountReport', { |       this.$store.dispatch('fetchAccountReport', { | ||||||
|         metric: this.metrics[this.currentSelection].KEY, |         metric: this.metrics[this.currentSelection].KEY, | ||||||
|         from, |         from, | ||||||
| @@ -222,6 +225,7 @@ export default { | |||||||
|         type: this.type, |         type: this.type, | ||||||
|         id: this.selectedFilter.id, |         id: this.selectedFilter.id, | ||||||
|         groupBy: groupBy.period, |         groupBy: groupBy.period, | ||||||
|  |         businessHours, | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     downloadReports() { |     downloadReports() { | ||||||
| @@ -288,6 +292,10 @@ export default { | |||||||
|           return this.$t('REPORT.GROUP_BY_DAY_OPTIONS'); |           return this.$t('REPORT.GROUP_BY_DAY_OPTIONS'); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     onBusinessHoursToggle(value) { | ||||||
|  |       this.businessHours = value; | ||||||
|  |       this.fetchAllData(); | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -42,7 +42,8 @@ export const actions = { | |||||||
|       reportObj.to, |       reportObj.to, | ||||||
|       reportObj.type, |       reportObj.type, | ||||||
|       reportObj.id, |       reportObj.id, | ||||||
|       reportObj.groupBy |       reportObj.groupBy, | ||||||
|  |       reportObj.businessHours | ||||||
|     ).then(accountReport => { |     ).then(accountReport => { | ||||||
|       let { data } = accountReport; |       let { data } = accountReport; | ||||||
|       data = data.filter( |       data = data.filter( | ||||||
| @@ -69,7 +70,8 @@ export const actions = { | |||||||
|       reportObj.to, |       reportObj.to, | ||||||
|       reportObj.type, |       reportObj.type, | ||||||
|       reportObj.id, |       reportObj.id, | ||||||
|       reportObj.groupBy |       reportObj.groupBy, | ||||||
|  |       reportObj.businessHours | ||||||
|     ) |     ) | ||||||
|       .then(accountSummary => { |       .then(accountSummary => { | ||||||
|         commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data); |         commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| class ReportingEventListener < BaseListener | class ReportingEventListener < BaseListener | ||||||
|  |   include ReportingEventHelper | ||||||
|   def conversation_resolved(event) |   def conversation_resolved(event) | ||||||
|     conversation = extract_conversation_and_account(event)[0] |     conversation = extract_conversation_and_account(event)[0] | ||||||
|     time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i |     time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i | ||||||
| @@ -6,10 +7,14 @@ class ReportingEventListener < BaseListener | |||||||
|     reporting_event = ReportingEvent.new( |     reporting_event = ReportingEvent.new( | ||||||
|       name: 'conversation_resolved', |       name: 'conversation_resolved', | ||||||
|       value: time_to_resolve, |       value: time_to_resolve, | ||||||
|  |       value_in_business_hours: business_hours(conversation.inbox, conversation.created_at, | ||||||
|  |                                               conversation.updated_at), | ||||||
|       account_id: conversation.account_id, |       account_id: conversation.account_id, | ||||||
|       inbox_id: conversation.inbox_id, |       inbox_id: conversation.inbox_id, | ||||||
|       user_id: conversation.assignee_id, |       user_id: conversation.assignee_id, | ||||||
|       conversation_id: conversation.id |       conversation_id: conversation.id, | ||||||
|  |       event_start_time: conversation.created_at, | ||||||
|  |       event_end_time: conversation.updated_at | ||||||
|     ) |     ) | ||||||
|     reporting_event.save |     reporting_event.save | ||||||
|   end |   end | ||||||
| @@ -22,10 +27,14 @@ class ReportingEventListener < BaseListener | |||||||
|     reporting_event = ReportingEvent.new( |     reporting_event = ReportingEvent.new( | ||||||
|       name: 'first_response', |       name: 'first_response', | ||||||
|       value: first_response_time, |       value: first_response_time, | ||||||
|  |       value_in_business_hours: business_hours(conversation.inbox, conversation.created_at, | ||||||
|  |                                               message.created_at), | ||||||
|       account_id: conversation.account_id, |       account_id: conversation.account_id, | ||||||
|       inbox_id: conversation.inbox_id, |       inbox_id: conversation.inbox_id, | ||||||
|       user_id: conversation.assignee_id, |       user_id: conversation.assignee_id, | ||||||
|       conversation_id: conversation.id |       conversation_id: conversation.id, | ||||||
|  |       event_start_time: conversation.created_at, | ||||||
|  |       event_end_time: message.created_at | ||||||
|     ) |     ) | ||||||
|     reporting_event.save |     reporting_event.save | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -3,8 +3,11 @@ | |||||||
| # Table name: reporting_events | # Table name: reporting_events | ||||||
| # | # | ||||||
| #  id                      :bigint           not null, primary key | #  id                      :bigint           not null, primary key | ||||||
|  | #  event_end_time          :datetime | ||||||
|  | #  event_start_time        :datetime | ||||||
| #  name                    :string | #  name                    :string | ||||||
| #  value                   :float | #  value                   :float | ||||||
|  | #  value_in_business_hours :float | ||||||
| #  created_at              :datetime         not null | #  created_at              :datetime         not null | ||||||
| #  updated_at              :datetime         not null | #  updated_at              :datetime         not null | ||||||
| #  account_id              :integer | #  account_id              :integer | ||||||
|   | |||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | class AddValueInBusinessHoursToReportingEvent < ActiveRecord::Migration[6.1] | ||||||
|  |   def change | ||||||
|  |     change_table :reporting_events, bulk: true do |t| | ||||||
|  |       t.float :value_in_business_hours, default: nil | ||||||
|  |       t.datetime :event_start_time, default: nil | ||||||
|  |       t.datetime :event_end_time, default: nil | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -657,6 +657,9 @@ ActiveRecord::Schema.define(version: 2022_04_05_092033) do | |||||||
|     t.integer "conversation_id" |     t.integer "conversation_id" | ||||||
|     t.datetime "created_at", precision: 6, null: false |     t.datetime "created_at", precision: 6, null: false | ||||||
|     t.datetime "updated_at", precision: 6, null: false |     t.datetime "updated_at", precision: 6, null: false | ||||||
|  |     t.float "value_in_business_hours" | ||||||
|  |     t.datetime "event_start_time" | ||||||
|  |     t.datetime "event_end_time" | ||||||
|     t.index ["account_id"], name: "index_reporting_events_on_account_id" |     t.index ["account_id"], name: "index_reporting_events_on_account_id" | ||||||
|     t.index ["created_at"], name: "index_reporting_events_on_created_at" |     t.index ["created_at"], name: "index_reporting_events_on_created_at" | ||||||
|     t.index ["inbox_id"], name: "index_reporting_events_on_inbox_id" |     t.index ["inbox_id"], name: "index_reporting_events_on_inbox_id" | ||||||
|   | |||||||
| @@ -17,6 +17,21 @@ describe ReportingEventListener do | |||||||
|       listener.conversation_resolved(event) |       listener.conversation_resolved(event) | ||||||
|       expect(account.reporting_events.where(name: 'conversation_resolved').count).to be 1 |       expect(account.reporting_events.where(name: 'conversation_resolved').count).to be 1 | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  |     context 'when business hours enabled for inbox' do | ||||||
|  |       let(:created_at) { Time.zone.parse('March 20, 2022 00:00') } | ||||||
|  |       let(:updated_at) { Time.zone.parse('March 26, 2022 23:59') } | ||||||
|  |       let!(:new_inbox) { create(:inbox, working_hours_enabled: true, account: account) } | ||||||
|  |       let!(:new_conversation) do | ||||||
|  |         create(:conversation, created_at: created_at, updated_at: updated_at, account: account, inbox: new_inbox, assignee: user) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'creates conversation_resolved event with business hour value' do | ||||||
|  |         event = Events::Base.new('conversation.resolved', Time.zone.now, conversation: new_conversation) | ||||||
|  |         listener.conversation_resolved(event) | ||||||
|  |         expect(account.reporting_events.where(name: 'conversation_resolved')[0]['value_in_business_hours']).to be 144_000.0 | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   describe '#first_reply_created' do |   describe '#first_reply_created' do | ||||||
| @@ -26,5 +41,24 @@ describe ReportingEventListener do | |||||||
|       listener.first_reply_created(event) |       listener.first_reply_created(event) | ||||||
|       expect(account.reporting_events.where(name: 'first_response').count).to eql previous_count + 1 |       expect(account.reporting_events.where(name: 'first_response').count).to eql previous_count + 1 | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  |     context 'when business hours enabled for inbox' do | ||||||
|  |       let(:conversation_created_at) { Time.zone.parse('March 20, 2022 00:00') } | ||||||
|  |       let(:message_created_at) { Time.zone.parse('March 26, 2022 23:59') } | ||||||
|  |       let!(:new_inbox) { create(:inbox, working_hours_enabled: true, account: account) } | ||||||
|  |       let!(:new_conversation) do | ||||||
|  |         create(:conversation, created_at: conversation_created_at, account: account, inbox: new_inbox, assignee: user) | ||||||
|  |       end | ||||||
|  |       let!(:new_message) do | ||||||
|  |         create(:message, message_type: 'outgoing', created_at: message_created_at, | ||||||
|  |                          account: account, inbox: new_inbox, conversation: new_conversation) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'creates first_response event with business hour value' do | ||||||
|  |         event = Events::Base.new('first.reply.created', Time.zone.now, message: new_message) | ||||||
|  |         listener.first_reply_created(event) | ||||||
|  |         expect(account.reporting_events.where(name: 'first_response')[0]['value_in_business_hours']).to be 144_000.0 | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Aswin Dev P.S
					Aswin Dev P.S