feat: Group by filter in reports (#3973)

This commit is contained in:
Aswin Dev P.S 2022-02-15 03:40:49 -08:00 committed by GitHub
parent a703ef2de6
commit e6f8895c1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 343 additions and 35 deletions

View file

@ -49,3 +49,4 @@ exclude_patterns:
- 'app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js'
- 'app/javascript/dashboard/routes/dashboard/settings/automation/constants.js'
- 'app/javascript/dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'
- 'app/javascript/dashboard/routes/dashboard/settings/reports/constants.js'

View file

@ -2,6 +2,8 @@ class V2::ReportBuilder
include DateRangeHelper
attr_reader :account, :params
DEFAULT_GROUP_BY = 'day'.freeze
def initialize(account, params)
@account = account
@params = params
@ -64,26 +66,30 @@ class V2::ReportBuilder
def conversations_count
scope.conversations
.group_by_day(:created_at, range: range, default_value: 0)
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end
def incoming_messages_count
scope.messages.incoming.unscope(:order)
.group_by_day(:created_at, range: range, default_value: 0)
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end
def outgoing_messages_count
scope.messages.outgoing.unscope(:order)
.group_by_day(:created_at, range: range, default_value: 0)
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end
def resolutions_count
scope.conversations
.resolved
.group_by_day(:created_at, range: range, default_value: 0)
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
:created_at, range: range, default_value: 0, permit: %w[day week month year])
.count
end

View file

@ -46,7 +46,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
type: params[:type].to_sym,
since: params[:since],
until: params[:until],
id: params[:id]
id: params[:id],
group_by: params[:group_by]
}
end
@ -56,7 +57,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
type: params[:type].to_sym,
since: params[:since],
until: params[:until],
id: params[:id]
id: params[:id],
group_by: params[:group_by]
}
end

View file

@ -6,15 +6,15 @@ class ReportsAPI extends ApiClient {
super('reports', { accountScoped: true, apiVersion: 'v2' });
}
getReports(metric, since, until, type = 'account', id) {
getReports(metric, since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}`, {
params: { metric, since, until, type, id },
params: { metric, since, until, type, id, group_by },
});
}
getSummary(since, until, type = 'account', id) {
getSummary(since, until, type = 'account', id, group_by) {
return axios.get(`${this.url}/summary`, {
params: { since, until, type, id },
params: { since, until, type, id, group_by },
});
}

View file

@ -59,7 +59,24 @@
"CUSTOM_DATE_RANGE": {
"CONFIRM": "Apply",
"PLACEHOLDER": "Select date range"
}
},
"GROUP_BY_FILTER_DROPDOWN_LABEL": "Group By",
"GROUP_BY_DAY_OPTIONS": [{ "id": 1, "groupBy": "Day" }],
"GROUP_BY_WEEK_OPTIONS": [
{ "id": 1, "groupBy": "Day" },
{ "id": 2, "groupBy": "Week" }
],
"GROUP_BY_MONTH_OPTIONS": [
{ "id": 1, "groupBy": "Day" },
{ "id": 2, "groupBy": "Week" },
{ "id": 3, "groupBy": "Month" }
],
"GROUP_BY_YEAR_OPTIONS": [
{ "id": 1, "groupBy": "Day" },
{ "id": 2, "groupBy": "Week" },
{ "id": 3, "groupBy": "Month" },
{ "id": 4, "groupBy": "Year" }
]
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",

View file

@ -1,6 +1,6 @@
<template>
<div class="column content-box">
<report-date-range-selector @date-range-change="onDateRangeChange" />
<report-filter-selector @date-range-change="onDateRangeChange" />
<csat-metrics />
<csat-table :page-index="pageIndex" @page-change="onPageNumberChange" />
</div>
@ -8,14 +8,14 @@
<script>
import CsatMetrics from './components/CsatMetrics';
import CsatTable from './components/CsatTable';
import ReportDateRangeSelector from './components/DateRangeSelector';
import ReportFilterSelector from './components/FilterSelector';
export default {
name: 'CsatResponses',
components: {
CsatMetrics,
CsatTable,
ReportDateRangeSelector,
ReportFilterSelector,
},
data() {
return { pageIndex: 1, from: 0, to: 0 };

View file

@ -9,7 +9,12 @@
{{ $t('REPORT.DOWNLOAD_AGENT_REPORTS') }}
</woot-button>
<report-date-range-selector @date-range-change="onDateRangeChange" />
<report-filter-selector
:selected-group-by-filter="selectedGroupByFilter"
:filter-items-list="filterItemsList"
@date-range-change="onDateRangeChange"
@filter-change="onFilterChange"
/>
<div class="row">
<woot-report-stats-card
v-for="(metric, index) in metrics"
@ -41,7 +46,8 @@
import { mapGetters } from 'vuex';
import fromUnixTime from 'date-fns/fromUnixTime';
import format from 'date-fns/format';
import ReportDateRangeSelector from './components/DateRangeSelector';
import ReportFilterSelector from './components/FilterSelector';
import { GROUP_BY_FILTER } from './constants';
const REPORTS_KEYS = {
CONVERSATIONS: 'conversations_count',
@ -54,10 +60,17 @@ const REPORTS_KEYS = {
export default {
components: {
ReportDateRangeSelector,
ReportFilterSelector,
},
data() {
return { from: 0, to: 0, currentSelection: 0 };
return {
from: 0,
to: 0,
currentSelection: 0,
groupBy: GROUP_BY_FILTER[1],
filterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'),
selectedGroupByFilter: {},
};
},
computed: {
...mapGetters({
@ -69,9 +82,28 @@ export default {
return {};
}
if (!this.accountReport.data.length) return {};
const labels = this.accountReport.data.map(element =>
format(fromUnixTime(element.timestamp), 'dd/MMM')
);
const labels = this.accountReport.data.map(element => {
if (this.groupBy.period === GROUP_BY_FILTER[2].period) {
let week_date = new Date(fromUnixTime(element.timestamp));
const first_day = week_date.getDate() - week_date.getDay();
const last_day = first_day + 6;
const week_first_date = new Date(week_date.setDate(first_day));
const week_last_date = new Date(week_date.setDate(last_day));
return `${format(week_first_date, 'dd/MM/yy')} - ${format(
week_last_date,
'dd/MM/yy'
)}`;
}
if (this.groupBy.period === GROUP_BY_FILTER[3].period) {
return format(fromUnixTime(element.timestamp), 'MMM-yyyy');
}
if (this.groupBy.period === GROUP_BY_FILTER[4].period) {
return format(fromUnixTime(element.timestamp), 'yyyy');
}
return format(fromUnixTime(element.timestamp), 'dd-MMM-yyyy');
});
const data = this.accountReport.data.map(element => element.value);
return {
labels,
@ -102,16 +134,21 @@ export default {
},
methods: {
fetchAllData() {
const { from, to } = this;
this.$store.dispatch('fetchAccountSummary', { from, to });
const { from, to, groupBy } = this;
this.$store.dispatch('fetchAccountSummary', {
from,
to,
groupBy: groupBy.period,
});
this.fetchChartData();
},
fetchChartData() {
const { from, to } = this;
const { from, to, groupBy } = this;
this.$store.dispatch('fetchAccountReport', {
metric: this.metrics[this.currentSelection].KEY,
from,
to,
groupBy: groupBy.period,
});
},
downloadAgentReports() {
@ -126,11 +163,37 @@ export default {
this.currentSelection = index;
this.fetchChartData();
},
onDateRangeChange({ from, to }) {
onDateRangeChange({ from, to, groupBy }) {
this.from = from;
this.to = to;
this.filterItemsList = this.fetchFilterItems(groupBy);
const filterItems = this.filterItemsList.filter(
item => item.id === this.groupBy.id
);
if (filterItems.length > 0) {
this.selectedGroupByFilter = filterItems[0];
} else {
this.selectedGroupByFilter = this.filterItemsList[0];
this.groupBy = GROUP_BY_FILTER[this.selectedGroupByFilter.id];
}
this.fetchAllData();
},
onFilterChange(payload) {
this.groupBy = GROUP_BY_FILTER[payload.id];
this.fetchAllData();
},
fetchFilterItems(group_by) {
switch (group_by) {
case GROUP_BY_FILTER[2].period:
return this.$t('REPORT.GROUP_BY_WEEK_OPTIONS');
case GROUP_BY_FILTER[3].period:
return this.$t('REPORT.GROUP_BY_MONTH_OPTIONS');
case GROUP_BY_FILTER[4].period:
return this.$t('REPORT.GROUP_BY_YEAR_OPTIONS');
default:
return this.$t('REPORT.GROUP_BY_DAY_OPTIONS');
}
},
},
};
</script>

View file

@ -23,6 +23,24 @@
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
@change="onChange"
/>
<div
v-if="notLast7Days"
class="small-12 medium-3 pull-right margin-left-small"
>
<p aria-hidden="true" class="hide">
{{ $t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL') }}
</p>
<multiselect
v-model="currentSelectedFilter"
track-by="id"
label="groupBy"
:placeholder="$t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL')"
:options="filterItemsList"
:allow-empty="false"
:show-labels="false"
@input="changeFilterSelection"
/>
</div>
</div>
</template>
<script>
@ -31,16 +49,28 @@ 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 { GROUP_BY_FILTER } from '../constants';
export default {
components: {
WootDateRangePicker,
},
props: {
filterItemsList: {
type: Array,
default: () => [],
},
selectedGroupByFilter: {
type: Object,
default: () => {},
},
},
data() {
return {
currentDateRangeSelection: this.$t('REPORT.DATE_RANGE')[0],
dateRange: this.$t('REPORT.DATE_RANGE'),
customDateRange: [new Date(), new Date()],
currentSelectedFilter: null,
};
},
computed: {
@ -68,13 +98,38 @@ export default {
const fromDate = subDays(new Date(), diff);
return this.fromCustomDate(fromDate);
},
groupBy() {
if (this.isDateRangeSelected) {
return GROUP_BY_FILTER[4].period;
}
const groupRange = {
0: GROUP_BY_FILTER[1].period,
1: GROUP_BY_FILTER[2].period,
2: GROUP_BY_FILTER[3].period,
3: GROUP_BY_FILTER[3].period,
4: GROUP_BY_FILTER[3].period,
};
return groupRange[this.currentDateRangeSelection.id];
},
notLast7Days() {
return this.groupBy !== GROUP_BY_FILTER[1].period;
},
},
watch: {
filterItemsList() {
this.currentSelectedFilter = this.selectedGroupByFilter;
},
},
mounted() {
this.onDateRangeChange();
},
methods: {
onDateRangeChange() {
this.$emit('date-range-change', { from: this.from, to: this.to });
this.$emit('date-range-change', {
from: this.from,
to: this.to,
groupBy: this.groupBy,
});
},
fromCustomDate(date) {
return getUnixTime(startOfDay(date));
@ -87,6 +142,9 @@ export default {
this.customDateRange = value;
this.onDateRangeChange();
},
changeFilterSelection() {
this.$emit('filter-change', this.currentSelectedFilter);
},
},
};
</script>

View file

@ -127,6 +127,24 @@
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
@change="onChange"
/>
<div
v-if="notLast7Days"
class="small-12 medium-3 pull-right margin-left-small"
>
<p aria-hidden="true" class="hide">
{{ $t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL') }}
</p>
<multiselect
v-model="currentSelectedGroupByFilter"
track-by="id"
label="groupBy"
:placeholder="$t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL')"
:options="groupByFilterItemsList"
:allow-empty="false"
:show-labels="false"
@input="changeGroupByFilterSelection"
/>
</div>
</div>
</template>
<script>
@ -136,6 +154,7 @@ import subDays from 'date-fns/subDays';
import startOfDay from 'date-fns/startOfDay';
import getUnixTime from 'date-fns/getUnixTime';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import { GROUP_BY_FILTER } from '../constants';
export default {
components: {
@ -147,10 +166,18 @@ export default {
type: Array,
default: () => [],
},
groupByFilterItemsList: {
type: Array,
default: () => [],
},
type: {
type: String,
default: 'agent',
},
selectedGroupByFilter: {
type: Object,
default: () => {},
},
},
data() {
return {
@ -158,6 +185,7 @@ export default {
currentDateRangeSelection: this.$t('REPORT.DATE_RANGE')[0],
dateRange: this.$t('REPORT.DATE_RANGE'),
customDateRange: [new Date(), new Date()],
currentSelectedGroupByFilter: null,
};
},
computed: {
@ -194,19 +222,42 @@ export default {
};
return typeLabels[this.type] || this.$t('FORMS.MULTISELECT.SELECT_ONE');
},
groupBy() {
if (this.isDateRangeSelected) {
return GROUP_BY_FILTER[4].period;
}
const groupRange = {
0: GROUP_BY_FILTER[1].period,
1: GROUP_BY_FILTER[2].period,
2: GROUP_BY_FILTER[3].period,
3: GROUP_BY_FILTER[3].period,
4: GROUP_BY_FILTER[3].period,
};
return groupRange[this.currentDateRangeSelection.id];
},
notLast7Days() {
return this.groupBy !== GROUP_BY_FILTER[1].period;
},
},
watch: {
filterItemsList(val) {
this.currentSelectedFilter = val[0];
this.changeFilterSelection();
},
groupByFilterItemsList() {
this.currentSelectedGroupByFilter = this.selectedGroupByFilter;
},
},
mounted() {
this.onDateRangeChange();
},
methods: {
onDateRangeChange() {
this.$emit('date-range-change', { from: this.from, to: this.to });
this.$emit('date-range-change', {
from: this.from,
to: this.to,
groupBy: this.groupBy,
});
},
fromCustomDate(date) {
return getUnixTime(startOfDay(date));
@ -222,6 +273,9 @@ export default {
this.customDateRange = value;
this.onDateRangeChange();
},
changeGroupByFilterSelection() {
this.$emit('group-by-filter-change', this.currentSelectedGroupByFilter);
},
},
};
</script>

View file

@ -12,8 +12,11 @@
v-if="filterItemsList"
:type="type"
:filter-items-list="filterItemsList"
:group-by-filter-items-list="groupByfilterItemsList"
:selected-group-by-filter="selectedGroupByFilter"
@date-range-change="onDateRangeChange"
@filter-change="onFilterChange"
@group-by-filter-change="onGroupByFilterChange"
/>
<div>
<div v-if="filterItemsList.length" class="row">
@ -51,6 +54,7 @@
import ReportFilters from './ReportFilters';
import fromUnixTime from 'date-fns/fromUnixTime';
import format from 'date-fns/format';
import { GROUP_BY_FILTER } from '../constants';
const REPORTS_KEYS = {
CONVERSATIONS: 'conversations_count',
@ -88,6 +92,9 @@ export default {
to: 0,
currentSelection: 0,
selectedFilter: null,
groupBy: GROUP_BY_FILTER[1],
groupByfilterItemsList: this.$t('REPORT.GROUP_BY_DAY_OPTIONS'),
selectedGroupByFilter: null,
};
},
computed: {
@ -105,9 +112,28 @@ export default {
return {};
}
if (!this.accountReport.data.length) return {};
const labels = this.accountReport.data.map(element =>
format(fromUnixTime(element.timestamp), 'dd/MMM')
);
const labels = this.accountReport.data.map(element => {
if (this.groupBy.period === GROUP_BY_FILTER[2].period) {
let week_date = new Date(fromUnixTime(element.timestamp));
const first_day = week_date.getDate() - week_date.getDay();
const last_day = first_day + 6;
const week_first_date = new Date(week_date.setDate(first_day));
const week_last_date = new Date(week_date.setDate(last_day));
return `${format(week_first_date, 'dd/MM/yy')} - ${format(
week_last_date,
'dd/MM/yy'
)}`;
}
if (this.groupBy.period === GROUP_BY_FILTER[3].period) {
return format(fromUnixTime(element.timestamp), 'MMM-yyyy');
}
if (this.groupBy.period === GROUP_BY_FILTER[4].period) {
return format(fromUnixTime(element.timestamp), 'yyyy');
}
return format(fromUnixTime(element.timestamp), 'dd-MMM-yyyy');
});
const data = this.accountReport.data.map(element => element.value);
return {
labels,
@ -148,24 +174,26 @@ export default {
methods: {
fetchAllData() {
if (this.selectedFilter) {
const { from, to } = this;
const { from, to, groupBy } = this;
this.$store.dispatch('fetchAccountSummary', {
from,
to,
type: this.type,
id: this.selectedFilter.id,
groupBy: groupBy.period,
});
this.fetchChartData();
}
},
fetchChartData() {
const { from, to } = this;
const { from, to, groupBy } = this;
this.$store.dispatch('fetchAccountReport', {
metric: this.metrics[this.currentSelection].KEY,
from,
to,
type: this.type,
id: this.selectedFilter.id,
groupBy: groupBy.period,
});
},
downloadReports() {
@ -195,9 +223,19 @@ export default {
this.currentSelection = index;
this.fetchChartData();
},
onDateRangeChange({ from, to }) {
onDateRangeChange({ from, to, groupBy }) {
this.from = from;
this.to = to;
this.groupByfilterItemsList = this.fetchFilterItems(groupBy);
const filterItems = this.groupByfilterItemsList.filter(
item => item.id === this.groupBy.id
);
if (filterItems.length > 0) {
this.selectedGroupByFilter = filterItems[0];
} else {
this.selectedGroupByFilter = this.groupByfilterItemsList[0];
this.groupBy = GROUP_BY_FILTER[this.selectedGroupByFilter.id];
}
this.fetchAllData();
},
onFilterChange(payload) {
@ -206,6 +244,22 @@ export default {
this.fetchAllData();
}
},
onGroupByFilterChange(payload) {
this.groupBy = GROUP_BY_FILTER[payload.id];
this.fetchAllData();
},
fetchFilterItems(group_by) {
switch (group_by) {
case GROUP_BY_FILTER[2].period:
return this.$t('REPORT.GROUP_BY_WEEK_OPTIONS');
case GROUP_BY_FILTER[3].period:
return this.$t('REPORT.GROUP_BY_MONTH_OPTIONS');
case GROUP_BY_FILTER[4].period:
return this.$t('REPORT.GROUP_BY_YEAR_OPTIONS');
default:
return this.$t('REPORT.GROUP_BY_DAY_OPTIONS');
}
},
},
};
</script>

View file

@ -0,0 +1,6 @@
export const GROUP_BY_FILTER = {
1: { id: 1, period: 'day' },
2: { id: 2, period: 'week' },
3: { id: 3, period: 'month' },
4: { id: 4, period: 'year' },
};

View file

@ -43,7 +43,8 @@ export const actions = {
reportObj.from,
reportObj.to,
reportObj.type,
reportObj.id
reportObj.id,
reportObj.groupBy
).then(accountReport => {
let { data } = accountReport;
data = data.filter(
@ -68,7 +69,8 @@ export const actions = {
reportObj.from,
reportObj.to,
reportObj.type,
reportObj.id
reportObj.id,
reportObj.groupBy
)
.then(accountSummary => {
commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data);

View file

@ -57,6 +57,7 @@ en:
conversations_count: Conversations count
avg_first_response_time: Avg first response time (Minutes)
avg_resolution_time: Avg resolution time (Minutes)
default_group_by: day
notifications:
notification_title:

View file

@ -147,6 +147,18 @@ describe ::V2::ReportBuilder do
expect(metrics[:avg_resolution_time]).to be 0
expect(metrics[:resolutions_count]).to be 0
end
it 'returns argument error for incorrect group by' do
params = {
type: :account,
since: (Time.zone.today - 3.days).to_time.to_i.to_s,
until: Time.zone.today.to_time.to_i.to_s,
group_by: 'test'.to_s
}
builder = V2::ReportBuilder.new(account, params)
expect { builder.summary }.to raise_error(ArgumentError)
end
end
context 'when report type is label' do
@ -247,6 +259,38 @@ describe ::V2::ReportBuilder do
expect(metrics[:avg_resolution_time]).to be 0
expect(metrics[:resolutions_count]).to be 0
end
it 'returns summary for correct group by' do
params = {
type: :label,
id: label_2.id,
since: (Time.zone.today - 3.days).to_time.to_i.to_s,
until: Time.zone.today.to_time.to_i.to_s,
group_by: 'week'.to_s
}
builder = V2::ReportBuilder.new(account, params)
metrics = builder.summary
expect(metrics[:conversations_count]).to be 5
expect(metrics[:incoming_messages_count]).to be 5
expect(metrics[:outgoing_messages_count]).to be 15
expect(metrics[:avg_resolution_time]).to be 0
expect(metrics[:resolutions_count]).to be 0
end
it 'returns argument error for incorrect group by' do
params = {
type: :label,
id: label_2.id,
since: (Time.zone.today - 3.days).to_time.to_i.to_s,
until: Time.zone.today.to_time.to_i.to_s,
group_by: 'test'.to_s
}
builder = V2::ReportBuilder.new(account, params)
expect { builder.summary }.to raise_error(ArgumentError)
end
end
end
end