fix: Consider timezone in the reports (#4027)

Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Pranav Raj S 2022-02-28 10:56:24 +05:30 committed by GitHub
parent 4ca66c1195
commit 9b615f11f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 73 additions and 46 deletions

View file

@ -7,6 +7,9 @@ class V2::ReportBuilder
def initialize(account, params) def initialize(account, params)
@account = account @account = account
@params = params @params = params
timezone_offset = (params[:timezone_offset] || 0).to_f
@timezone = ActiveSupport::TimeZone[timezone_offset]&.name
end end
def timeseries def timeseries
@ -64,60 +67,58 @@ class V2::ReportBuilder
@team ||= account.teams.find(params[:id]) @team ||= account.teams.find(params[:id])
end 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 def conversations_count
scope.conversations (get_grouped_values scope.conversations).count
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end end
def incoming_messages_count def incoming_messages_count
scope.messages.incoming.unscope(:order) (get_grouped_values scope.messages.incoming.unscope(:order)).count
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end end
def outgoing_messages_count def outgoing_messages_count
scope.messages.outgoing.unscope(:order) (get_grouped_values scope.messages.outgoing.unscope(:order)).count
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end end
def resolutions_count def resolutions_count
scope.conversations (get_grouped_values scope.conversations.resolved).count
.resolved
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end end
def avg_first_response_time def avg_first_response_time
scope.events (get_grouped_values scope.events.where(name: 'first_response')).average(:value)
.where(name: 'first_response')
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
end end
def avg_resolution_time def avg_resolution_time
scope.events.where(name: 'conversation_resolved') (get_grouped_values scope.events.where(name: 'conversation_resolved')).average(:value)
.group_by_day(:created_at, range: range, default_value: 0)
.average(:value)
end 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 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 end
def avg_first_response_time_summary 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
end end

View file

@ -58,7 +58,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
since: params[:since], since: params[:since],
until: params[:until], until: params[:until],
id: params[:id], id: params[:id],
group_by: params[:group_by] group_by: params[:group_by],
timezone_offset: params[:timezone_offset]
} }
end end

View file

@ -1,6 +1,8 @@
/* global axios */ /* global axios */
import ApiClient from './ApiClient'; import ApiClient from './ApiClient';
const getTimeOffset = () => -new Date().getTimezoneOffset() / 60;
class ReportsAPI extends ApiClient { class ReportsAPI extends ApiClient {
constructor() { constructor() {
super('reports', { accountScoped: true, apiVersion: 'v2' }); super('reports', { accountScoped: true, apiVersion: 'v2' });
@ -8,13 +10,27 @@ class ReportsAPI extends ApiClient {
getReports(metric, since, until, type = 'account', id, group_by) { getReports(metric, since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}`, { 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) { getSummary(since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}/summary`, { return axios.get(`${this.url}/summary`, {
params: { since, until, type, id, group_by }, params: {
since,
until,
type,
id,
group_by,
},
}); });
} }

View file

@ -27,6 +27,7 @@ describe('#Reports API', () => {
since: 1621103400, since: 1621103400,
until: 1621621800, until: 1621621800,
type: 'account', type: 'account',
timezone_offset: -0,
}, },
}); });
}); });

View file

@ -50,6 +50,7 @@ import subDays from 'date-fns/subDays';
import startOfDay from 'date-fns/startOfDay'; import startOfDay from 'date-fns/startOfDay';
import getUnixTime from 'date-fns/getUnixTime'; import getUnixTime from 'date-fns/getUnixTime';
import { GROUP_BY_FILTER } from '../constants'; import { GROUP_BY_FILTER } from '../constants';
import endOfDay from 'date-fns/endOfDay';
export default { export default {
components: { components: {
@ -79,9 +80,9 @@ export default {
}, },
to() { to() {
if (this.isDateRangeSelected) { 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() { from() {
if (this.isDateRangeSelected) { if (this.isDateRangeSelected) {
@ -134,6 +135,9 @@ export default {
fromCustomDate(date) { fromCustomDate(date) {
return getUnixTime(startOfDay(date)); return getUnixTime(startOfDay(date));
}, },
toCustomDate(date) {
return getUnixTime(endOfDay(date));
},
changeDateSelection(selectedRange) { changeDateSelection(selectedRange) {
this.currentDateRangeSelection = selectedRange; this.currentDateRangeSelection = selectedRange;
this.onDateRangeChange(); this.onDateRangeChange();

View file

@ -148,13 +148,15 @@
</div> </div>
</template> </template>
<script> <script>
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue'; import endOfDay from 'date-fns/endOfDay';
const CUSTOM_DATE_RANGE_ID = 5;
import subDays from 'date-fns/subDays';
import startOfDay from 'date-fns/startOfDay';
import getUnixTime from 'date-fns/getUnixTime'; 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 Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
import { GROUP_BY_FILTER } from '../constants'; import { GROUP_BY_FILTER } from '../constants';
const CUSTOM_DATE_RANGE_ID = 5;
export default { export default {
components: { components: {
@ -194,9 +196,9 @@ export default {
}, },
to() { to() {
if (this.isDateRangeSelected) { 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() { from() {
if (this.isDateRangeSelected) { if (this.isDateRangeSelected) {
@ -253,6 +255,7 @@ export default {
}, },
methods: { methods: {
onDateRangeChange() { onDateRangeChange() {
console.log(this.from, this.to);
this.$emit('date-range-change', { this.$emit('date-range-change', {
from: this.from, from: this.from,
to: this.to, to: this.to,
@ -262,6 +265,9 @@ export default {
fromCustomDate(date) { fromCustomDate(date) {
return getUnixTime(startOfDay(date)); return getUnixTime(startOfDay(date));
}, },
toCustomDate(date) {
return getUnixTime(endOfDay(date));
},
changeDateSelection(selectedRange) { changeDateSelection(selectedRange) {
this.currentDateRangeSelection = selectedRange; this.currentDateRangeSelection = selectedRange;
this.onDateRangeChange(); this.onDateRangeChange();

View file

@ -1,9 +1,6 @@
/* eslint no-console: 0 */ /* eslint no-console: 0 */
/* eslint no-param-reassign: 0 */ /* eslint no-param-reassign: 0 */
/* eslint no-shadow: 0 */ /* eslint no-shadow: 0 */
import compareAsc from 'date-fns/compareAsc';
import fromUnixTime from 'date-fns/fromUnixTime';
import * as types from '../mutation-types'; import * as types from '../mutation-types';
import Report from '../../api/reports'; import Report from '../../api/reports';
@ -48,7 +45,8 @@ export const actions = {
).then(accountReport => { ).then(accountReport => {
let { data } = accountReport; let { data } = accountReport;
data = data.filter( data = data.filter(
el => compareAsc(new Date(), fromUnixTime(el.timestamp)) > -1 el =>
reportObj.to - el.timestamp > 0 && el.timestamp - reportObj.from >= 0
); );
if ( if (
reportObj.metric === 'avg_first_response_time' || reportObj.metric === 'avg_first_response_time' ||