fix: Consider timezone in the reports (#4027)
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
parent
4ca66c1195
commit
9b615f11f1
7 changed files with 73 additions and 46 deletions
|
@ -7,6 +7,9 @@ class V2::ReportBuilder
|
|||
def initialize(account, params)
|
||||
@account = account
|
||||
@params = params
|
||||
|
||||
timezone_offset = (params[:timezone_offset] || 0).to_f
|
||||
@timezone = ActiveSupport::TimeZone[timezone_offset]&.name
|
||||
end
|
||||
|
||||
def timeseries
|
||||
|
@ -64,60 +67,58 @@ class V2::ReportBuilder
|
|||
@team ||= account.teams.find(params[:id])
|
||||
end
|
||||
|
||||
def get_grouped_values(object_scope)
|
||||
object_scope.group_by_period(
|
||||
params[:group_by] || DEFAULT_GROUP_BY,
|
||||
:created_at,
|
||||
default_value: 0,
|
||||
range: range,
|
||||
permit: %w[day week month year],
|
||||
time_zone: @timezone
|
||||
)
|
||||
end
|
||||
|
||||
def conversations_count
|
||||
scope.conversations
|
||||
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||
.count
|
||||
(get_grouped_values scope.conversations).count
|
||||
end
|
||||
|
||||
def incoming_messages_count
|
||||
scope.messages.incoming.unscope(:order)
|
||||
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||
.count
|
||||
(get_grouped_values scope.messages.incoming.unscope(:order)).count
|
||||
end
|
||||
|
||||
def outgoing_messages_count
|
||||
scope.messages.outgoing.unscope(:order)
|
||||
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||
.count
|
||||
(get_grouped_values scope.messages.outgoing.unscope(:order)).count
|
||||
end
|
||||
|
||||
def resolutions_count
|
||||
scope.conversations
|
||||
.resolved
|
||||
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||
.count
|
||||
(get_grouped_values scope.conversations.resolved).count
|
||||
end
|
||||
|
||||
def avg_first_response_time
|
||||
scope.events
|
||||
.where(name: 'first_response')
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.average(:value)
|
||||
(get_grouped_values scope.events.where(name: 'first_response')).average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time
|
||||
scope.events.where(name: 'conversation_resolved')
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.average(:value)
|
||||
(get_grouped_values scope.events.where(name: 'conversation_resolved')).average(:value)
|
||||
end
|
||||
|
||||
# Taking average of average is not too accurate
|
||||
# https://en.wikipedia.org/wiki/Simpson's_paradox
|
||||
# TODO: Will optimize this later
|
||||
def avg_resolution_time_summary
|
||||
return 0 if avg_resolution_time.values.empty?
|
||||
avg_rt = scope.events
|
||||
.where(name: 'conversation_resolved', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
(avg_resolution_time.values.sum / avg_resolution_time.values.length)
|
||||
return 0 if avg_rt.blank?
|
||||
|
||||
avg_rt
|
||||
end
|
||||
|
||||
def avg_first_response_time_summary
|
||||
return 0 if avg_first_response_time.values.empty?
|
||||
avg_frt = scope.events
|
||||
.where(name: 'first_response', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
(avg_first_response_time.values.sum / avg_first_response_time.values.length)
|
||||
return 0 if avg_frt.blank?
|
||||
|
||||
avg_frt
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||
since: params[:since],
|
||||
until: params[:until],
|
||||
id: params[:id],
|
||||
group_by: params[:group_by]
|
||||
group_by: params[:group_by],
|
||||
timezone_offset: params[:timezone_offset]
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
const getTimeOffset = () => -new Date().getTimezoneOffset() / 60;
|
||||
|
||||
class ReportsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
||||
|
@ -8,13 +10,27 @@ class ReportsAPI extends ApiClient {
|
|||
|
||||
getReports(metric, since, until, type = 'account', id, group_by) {
|
||||
return axios.get(`${this.url}`, {
|
||||
params: { metric, since, until, type, id, group_by },
|
||||
params: {
|
||||
metric,
|
||||
since,
|
||||
until,
|
||||
type,
|
||||
id,
|
||||
group_by,
|
||||
timezone_offset: getTimeOffset(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getSummary(since, until, type = 'account', id, group_by) {
|
||||
return axios.get(`${this.url}/summary`, {
|
||||
params: { since, until, type, id, group_by },
|
||||
params: {
|
||||
since,
|
||||
until,
|
||||
type,
|
||||
id,
|
||||
group_by,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ describe('#Reports API', () => {
|
|||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
type: 'account',
|
||||
timezone_offset: -0,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,6 +50,7 @@ import subDays from 'date-fns/subDays';
|
|||
import startOfDay from 'date-fns/startOfDay';
|
||||
import getUnixTime from 'date-fns/getUnixTime';
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -79,9 +80,9 @@ export default {
|
|||
},
|
||||
to() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return this.fromCustomDate(this.customDateRange[1]);
|
||||
return this.toCustomDate(this.customDateRange[1]);
|
||||
}
|
||||
return this.fromCustomDate(new Date());
|
||||
return this.toCustomDate(new Date());
|
||||
},
|
||||
from() {
|
||||
if (this.isDateRangeSelected) {
|
||||
|
@ -134,6 +135,9 @@ export default {
|
|||
fromCustomDate(date) {
|
||||
return getUnixTime(startOfDay(date));
|
||||
},
|
||||
toCustomDate(date) {
|
||||
return getUnixTime(endOfDay(date));
|
||||
},
|
||||
changeDateSelection(selectedRange) {
|
||||
this.currentDateRangeSelection = selectedRange;
|
||||
this.onDateRangeChange();
|
||||
|
|
|
@ -148,13 +148,15 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
const CUSTOM_DATE_RANGE_ID = 5;
|
||||
import subDays from 'date-fns/subDays';
|
||||
import startOfDay from 'date-fns/startOfDay';
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
import getUnixTime from 'date-fns/getUnixTime';
|
||||
import startOfDay from 'date-fns/startOfDay';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
const CUSTOM_DATE_RANGE_ID = 5;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -194,9 +196,9 @@ export default {
|
|||
},
|
||||
to() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return this.fromCustomDate(this.customDateRange[1]);
|
||||
return this.toCustomDate(this.customDateRange[1]);
|
||||
}
|
||||
return this.fromCustomDate(new Date());
|
||||
return this.toCustomDate(new Date());
|
||||
},
|
||||
from() {
|
||||
if (this.isDateRangeSelected) {
|
||||
|
@ -253,6 +255,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onDateRangeChange() {
|
||||
console.log(this.from, this.to);
|
||||
this.$emit('date-range-change', {
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
|
@ -262,6 +265,9 @@ export default {
|
|||
fromCustomDate(date) {
|
||||
return getUnixTime(startOfDay(date));
|
||||
},
|
||||
toCustomDate(date) {
|
||||
return getUnixTime(endOfDay(date));
|
||||
},
|
||||
changeDateSelection(selectedRange) {
|
||||
this.currentDateRangeSelection = selectedRange;
|
||||
this.onDateRangeChange();
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
/* eslint no-console: 0 */
|
||||
/* eslint no-param-reassign: 0 */
|
||||
/* eslint no-shadow: 0 */
|
||||
import compareAsc from 'date-fns/compareAsc';
|
||||
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||
|
||||
import * as types from '../mutation-types';
|
||||
import Report from '../../api/reports';
|
||||
|
||||
|
@ -48,7 +45,8 @@ export const actions = {
|
|||
).then(accountReport => {
|
||||
let { data } = accountReport;
|
||||
data = data.filter(
|
||||
el => compareAsc(new Date(), fromUnixTime(el.timestamp)) > -1
|
||||
el =>
|
||||
reportObj.to - el.timestamp > 0 && el.timestamp - reportObj.from >= 0
|
||||
);
|
||||
if (
|
||||
reportObj.metric === 'avg_first_response_time' ||
|
||||
|
|
Loading…
Reference in a new issue