feat - Add filter for reports by agent, label and inboxes (#3084)
* Adds filter for agents, labels and inboxes * Added Inboxes Reports Feature * Fixed populating of filter dropdown issue * If applied, fixes code climate style-lint warnings * Fixes codeclimate warnings * if applied, Refactors sidebar file to fix codclimate warnings * if applied, fixes the download reports button for filtered report-data * If applied, replaces native img tag with thumbnail component * If applied, replaces hardcoded color string with variable * If applied, adds a11y labels to multiselect dropdowns * If applied, Renames reports methods to generic names * If applied, Adds test cases for Labels and Inboxes * If applied, write a test spec for fileDownload helper * if applied, Moves fileDownload method to a utils folder * If applied, Fixes the report file name type * Test Spec for Reports Store module * Fix specs - add restoreAllMocks Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
@@ -6,15 +6,15 @@ class ReportsAPI extends ApiClient {
|
|||||||
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountReports(metric, since, until) {
|
getReports(metric, since, until, type = 'account', id) {
|
||||||
return axios.get(`${this.url}`, {
|
return axios.get(`${this.url}`, {
|
||||||
params: { metric, since, until, type: 'account' },
|
params: { metric, since, until, type, id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountSummary(since, until) {
|
getSummary(since, until, type = 'account', id) {
|
||||||
return axios.get(`${this.url}/summary`, {
|
return axios.get(`${this.url}/summary`, {
|
||||||
params: { since, until, type: 'account' },
|
params: { since, until, type, id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +23,18 @@ class ReportsAPI extends ApiClient {
|
|||||||
params: { since, until },
|
params: { since, until },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLabelReports(since, until) {
|
||||||
|
return axios.get(`${this.url}/labels`, {
|
||||||
|
params: { since, until },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getInboxReports(since, until) {
|
||||||
|
return axios.get(`${this.url}/inboxes`, {
|
||||||
|
params: { since, until },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ReportsAPI();
|
export default new ReportsAPI();
|
||||||
|
|||||||
@@ -11,39 +11,34 @@ describe('#Reports API', () => {
|
|||||||
expect(reportsAPI).toHaveProperty('create');
|
expect(reportsAPI).toHaveProperty('create');
|
||||||
expect(reportsAPI).toHaveProperty('update');
|
expect(reportsAPI).toHaveProperty('update');
|
||||||
expect(reportsAPI).toHaveProperty('delete');
|
expect(reportsAPI).toHaveProperty('delete');
|
||||||
expect(reportsAPI).toHaveProperty('getAccountReports');
|
expect(reportsAPI).toHaveProperty('getReports');
|
||||||
expect(reportsAPI).toHaveProperty('getAccountSummary');
|
expect(reportsAPI).toHaveProperty('getSummary');
|
||||||
expect(reportsAPI).toHaveProperty('getAgentReports');
|
expect(reportsAPI).toHaveProperty('getAgentReports');
|
||||||
|
expect(reportsAPI).toHaveProperty('getLabelReports');
|
||||||
|
expect(reportsAPI).toHaveProperty('getInboxReports');
|
||||||
});
|
});
|
||||||
describeWithAPIMock('API calls', context => {
|
describeWithAPIMock('API calls', context => {
|
||||||
it('#getAccountReports', () => {
|
it('#getAccountReports', () => {
|
||||||
reportsAPI.getAccountReports(
|
reportsAPI.getReports('conversations_count', 1621103400, 1621621800);
|
||||||
'conversations_count',
|
expect(context.axiosMock.get).toHaveBeenCalledWith('/api/v2/reports', {
|
||||||
1621103400,
|
|
||||||
1621621800
|
|
||||||
);
|
|
||||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
|
||||||
'/api/v2/reports',
|
|
||||||
{
|
|
||||||
params: {
|
params: {
|
||||||
metric: 'conversations_count',
|
metric: 'conversations_count',
|
||||||
since: 1621103400,
|
since: 1621103400,
|
||||||
until: 1621621800,
|
until: 1621621800,
|
||||||
type: 'account'
|
type: 'account',
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#getAccountSummary', () => {
|
it('#getAccountSummary', () => {
|
||||||
reportsAPI.getAccountSummary(1621103400, 1621621800);
|
reportsAPI.getSummary(1621103400, 1621621800);
|
||||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
'/api/v2/reports/summary',
|
'/api/v2/reports/summary',
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
since: 1621103400,
|
since: 1621103400,
|
||||||
until: 1621621800,
|
until: 1621621800,
|
||||||
type: 'account'
|
type: 'account',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -61,5 +56,31 @@ describe('#Reports API', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#getLabelReports', () => {
|
||||||
|
reportsAPI.getLabelReports(1621103400, 1621621800);
|
||||||
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/reports/labels',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
since: 1621103400,
|
||||||
|
until: 1621621800,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#getInboxReports', () => {
|
||||||
|
reportsAPI.getInboxReports(1621103400, 1621621800);
|
||||||
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/reports/inboxes',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
since: 1621103400,
|
||||||
|
until: 1621621800,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.report-bar {
|
.report-bar {
|
||||||
@include margin(-1px $zero);
|
@include margin(-1px $zero);
|
||||||
@include background-white;
|
@include background-white;
|
||||||
|
|||||||
30
app/javascript/dashboard/assets/scss/widgets/_reports.scss
Normal file
30
app/javascript/dashboard/assets/scss/widgets/_reports.scss
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
.date-picker {
|
||||||
|
margin-left: var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-left-small {
|
||||||
|
margin-left: var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reports-option__rounded--item {
|
||||||
|
border-radius: 100%;
|
||||||
|
height: var(--space-two);
|
||||||
|
width: var(--space-two);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reports-option__item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reports-option__label--swatch {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-right-small {
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
@@ -1,245 +1,13 @@
|
|||||||
import { frontendURL } from '../helper/URLHelper';
|
import common from './sidebarItems/common';
|
||||||
|
import contacts from './sidebarItems/contacts';
|
||||||
|
import reports from './sidebarItems/reports';
|
||||||
|
import campaigns from './sidebarItems/campaigns';
|
||||||
|
import settings from './sidebarItems/settings';
|
||||||
|
|
||||||
export const getSidebarItems = accountId => ({
|
export const getSidebarItems = accountId => ({
|
||||||
common: {
|
common: common(accountId),
|
||||||
routes: [
|
contacts: contacts(accountId),
|
||||||
'home',
|
reports: reports(accountId),
|
||||||
'inbox_dashboard',
|
campaigns: campaigns(accountId),
|
||||||
'inbox_conversation',
|
settings: settings(accountId),
|
||||||
'conversation_through_inbox',
|
|
||||||
'notifications_dashboard',
|
|
||||||
'profile_settings',
|
|
||||||
'profile_settings_index',
|
|
||||||
'label_conversations',
|
|
||||||
'conversations_through_label',
|
|
||||||
'team_conversations',
|
|
||||||
'conversations_through_team',
|
|
||||||
'notifications_index',
|
|
||||||
],
|
|
||||||
menuItems: {
|
|
||||||
assignedToMe: {
|
|
||||||
icon: 'ion-chatbox-working',
|
|
||||||
label: 'CONVERSATIONS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
key: '',
|
|
||||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
|
||||||
toolTip: 'Conversation from all subscribed inboxes',
|
|
||||||
toStateName: 'home',
|
|
||||||
},
|
|
||||||
contacts: {
|
|
||||||
icon: 'ion-person',
|
|
||||||
label: 'CONTACTS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
|
||||||
toStateName: 'contacts_dashboard',
|
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
icon: 'ion-ios-bell',
|
|
||||||
label: 'NOTIFICATIONS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/notifications`),
|
|
||||||
toStateName: 'notifications_dashboard',
|
|
||||||
},
|
|
||||||
report: {
|
|
||||||
icon: 'ion-arrow-graph-up-right',
|
|
||||||
label: 'REPORTS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/reports`),
|
|
||||||
toStateName: 'settings_account_reports',
|
|
||||||
},
|
|
||||||
campaigns: {
|
|
||||||
icon: 'ion-speakerphone',
|
|
||||||
label: 'CAMPAIGNS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/campaigns`),
|
|
||||||
toStateName: 'settings_account_campaigns',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
icon: 'ion-settings',
|
|
||||||
label: 'SETTINGS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings`),
|
|
||||||
toStateName: 'settings_home',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contacts: {
|
|
||||||
routes: [
|
|
||||||
'contacts_dashboard',
|
|
||||||
'contacts_dashboard_manage',
|
|
||||||
'contacts_labels_dashboard',
|
|
||||||
],
|
|
||||||
menuItems: {
|
|
||||||
back: {
|
|
||||||
icon: 'ion-ios-arrow-back',
|
|
||||||
label: 'HOME',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toStateName: 'home',
|
|
||||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
|
||||||
},
|
|
||||||
contacts: {
|
|
||||||
icon: 'ion-person',
|
|
||||||
label: 'ALL_CONTACTS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
|
||||||
toStateName: 'contacts_dashboard',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
reports: {
|
|
||||||
routes: ['settings_account_reports', 'csat_reports'],
|
|
||||||
menuItems: {
|
|
||||||
back: {
|
|
||||||
icon: 'ion-ios-arrow-back',
|
|
||||||
label: 'HOME',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toStateName: 'home',
|
|
||||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
|
||||||
},
|
|
||||||
reportOverview: {
|
|
||||||
icon: 'ion-arrow-graph-up-right',
|
|
||||||
label: 'REPORTS_OVERVIEW',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/reports/overview`),
|
|
||||||
toStateName: 'settings_account_reports',
|
|
||||||
},
|
|
||||||
csatReports: {
|
|
||||||
icon: 'ion-happy',
|
|
||||||
label: 'CSAT',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/reports/csat`),
|
|
||||||
toStateName: 'csat_reports',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
campaigns: {
|
|
||||||
routes: ['settings_account_campaigns', 'one_off'],
|
|
||||||
menuItems: {
|
|
||||||
back: {
|
|
||||||
icon: 'ion-ios-arrow-back',
|
|
||||||
label: 'HOME',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toStateName: 'home',
|
|
||||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
|
||||||
},
|
|
||||||
ongoingCampaigns: {
|
|
||||||
icon: 'ion-arrow-swap',
|
|
||||||
label: 'ONGOING',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`),
|
|
||||||
toStateName: 'settings_account_campaigns',
|
|
||||||
},
|
|
||||||
onOffCampaigns: {
|
|
||||||
icon: 'ion-radio-waves',
|
|
||||||
label: 'ONE_OFF',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/campaigns/one_off`),
|
|
||||||
toStateName: 'one_off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
routes: [
|
|
||||||
'agent_list',
|
|
||||||
'canned_list',
|
|
||||||
'labels_list',
|
|
||||||
'settings_inbox',
|
|
||||||
'attributes_list',
|
|
||||||
'settings_inbox_new',
|
|
||||||
'settings_inbox_list',
|
|
||||||
'settings_inbox_show',
|
|
||||||
'settings_inboxes_page_channel',
|
|
||||||
'settings_inboxes_add_agents',
|
|
||||||
'settings_inbox_finish',
|
|
||||||
'settings_integrations',
|
|
||||||
'settings_integrations_webhook',
|
|
||||||
'settings_integrations_integration',
|
|
||||||
'settings_applications',
|
|
||||||
'settings_applications_webhook',
|
|
||||||
'settings_applications_integration',
|
|
||||||
'general_settings',
|
|
||||||
'general_settings_index',
|
|
||||||
'settings_teams_list',
|
|
||||||
'settings_teams_new',
|
|
||||||
'settings_teams_add_agents',
|
|
||||||
'settings_teams_finish',
|
|
||||||
'settings_teams_edit',
|
|
||||||
'settings_teams_edit_members',
|
|
||||||
'settings_teams_edit_finish',
|
|
||||||
],
|
|
||||||
menuItems: {
|
|
||||||
back: {
|
|
||||||
icon: 'ion-ios-arrow-back',
|
|
||||||
label: 'HOME',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toStateName: 'home',
|
|
||||||
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
icon: 'ion-person-stalker',
|
|
||||||
label: 'AGENTS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
|
|
||||||
toStateName: 'agent_list',
|
|
||||||
},
|
|
||||||
teams: {
|
|
||||||
icon: 'ion-ios-people',
|
|
||||||
label: 'TEAMS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
|
|
||||||
toStateName: 'settings_teams_list',
|
|
||||||
},
|
|
||||||
inboxes: {
|
|
||||||
icon: 'ion-archive',
|
|
||||||
label: 'INBOXES',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
|
|
||||||
toStateName: 'settings_inbox_list',
|
|
||||||
},
|
|
||||||
labels: {
|
|
||||||
icon: 'ion-pricetags',
|
|
||||||
label: 'LABELS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
|
|
||||||
toStateName: 'labels_list',
|
|
||||||
},
|
|
||||||
attributes: {
|
|
||||||
icon: 'ion-code',
|
|
||||||
label: 'ATTRIBUTES',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/attributes/list`),
|
|
||||||
toStateName: 'attributes_list',
|
|
||||||
},
|
|
||||||
cannedResponses: {
|
|
||||||
icon: 'ion-chatbox-working',
|
|
||||||
label: 'CANNED_RESPONSES',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(
|
|
||||||
`accounts/${accountId}/settings/canned-response/list`
|
|
||||||
),
|
|
||||||
toStateName: 'canned_list',
|
|
||||||
},
|
|
||||||
settings_integrations: {
|
|
||||||
icon: 'ion-flash',
|
|
||||||
label: 'INTEGRATIONS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
|
||||||
toStateName: 'settings_integrations',
|
|
||||||
},
|
|
||||||
settings_applications: {
|
|
||||||
icon: 'ion-asterisk',
|
|
||||||
label: 'APPLICATIONS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/applications`),
|
|
||||||
toStateName: 'settings_applications',
|
|
||||||
},
|
|
||||||
general_settings_index: {
|
|
||||||
icon: 'ion-gear-a',
|
|
||||||
label: 'ACCOUNT_SETTINGS',
|
|
||||||
hasSubMenu: false,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/general`),
|
|
||||||
toStateName: 'general_settings_index',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,6 +61,195 @@
|
|||||||
"PLACEHOLDER": "Select date range"
|
"PLACEHOLDER": "Select date range"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AGENT_REPORTS": {
|
||||||
|
"HEADER": "Agents Overview",
|
||||||
|
"LOADING_CHART": "Loading chart data...",
|
||||||
|
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||||
|
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
||||||
|
"FILTER_DROPDOWN_LABEL": "Select Agent",
|
||||||
|
"METRICS": {
|
||||||
|
"CONVERSATIONS": {
|
||||||
|
"NAME": "Conversations",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"INCOMING_MESSAGES": {
|
||||||
|
"NAME": "Incoming Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"OUTGOING_MESSAGES": {
|
||||||
|
"NAME": "Outgoing Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"FIRST_RESPONSE_TIME": {
|
||||||
|
"NAME": "First response time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_TIME": {
|
||||||
|
"NAME": "Resolution Time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_COUNT": {
|
||||||
|
"NAME": "Resolution Count",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DATE_RANGE": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "Last 7 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Last 30 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Last 3 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Last 6 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Last year"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Custom date range"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CUSTOM_DATE_RANGE": {
|
||||||
|
"CONFIRM": "Apply",
|
||||||
|
"PLACEHOLDER": "Select date range"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LABEL_REPORTS": {
|
||||||
|
"HEADER": "Labels Overview",
|
||||||
|
"LOADING_CHART": "Loading chart data...",
|
||||||
|
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||||
|
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
|
||||||
|
"FILTER_DROPDOWN_LABEL": "Select Label",
|
||||||
|
"METRICS": {
|
||||||
|
"CONVERSATIONS": {
|
||||||
|
"NAME": "Conversations",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"INCOMING_MESSAGES": {
|
||||||
|
"NAME": "Incoming Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"OUTGOING_MESSAGES": {
|
||||||
|
"NAME": "Outgoing Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"FIRST_RESPONSE_TIME": {
|
||||||
|
"NAME": "First response time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_TIME": {
|
||||||
|
"NAME": "Resolution Time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_COUNT": {
|
||||||
|
"NAME": "Resolution Count",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DATE_RANGE": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "Last 7 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Last 30 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Last 3 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Last 6 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Last year"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Custom date range"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CUSTOM_DATE_RANGE": {
|
||||||
|
"CONFIRM": "Apply",
|
||||||
|
"PLACEHOLDER": "Select date range"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"INBOX_REPORTS": {
|
||||||
|
"HEADER": "Inbox Overview",
|
||||||
|
"LOADING_CHART": "Loading chart data...",
|
||||||
|
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||||
|
"DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
|
||||||
|
"FILTER_DROPDOWN_LABEL": "Select Inbox",
|
||||||
|
"METRICS": {
|
||||||
|
"CONVERSATIONS": {
|
||||||
|
"NAME": "Conversations",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"INCOMING_MESSAGES": {
|
||||||
|
"NAME": "Incoming Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"OUTGOING_MESSAGES": {
|
||||||
|
"NAME": "Outgoing Messages",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
},
|
||||||
|
"FIRST_RESPONSE_TIME": {
|
||||||
|
"NAME": "First response time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_TIME": {
|
||||||
|
"NAME": "Resolution Time",
|
||||||
|
"DESC": "( Avg )"
|
||||||
|
},
|
||||||
|
"RESOLUTION_COUNT": {
|
||||||
|
"NAME": "Resolution Count",
|
||||||
|
"DESC": "( Total )"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DATE_RANGE": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "Last 7 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Last 30 days"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Last 3 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Last 6 months"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Last year"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Custom date range"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CUSTOM_DATE_RANGE": {
|
||||||
|
"CONFIRM": "Apply",
|
||||||
|
"PLACEHOLDER": "Select date range"
|
||||||
|
}
|
||||||
|
},
|
||||||
"CSAT_REPORTS": {
|
"CSAT_REPORTS": {
|
||||||
"HEADER": "CSAT Reports",
|
"HEADER": "CSAT Reports",
|
||||||
"NO_RECORDS": "There are no CSAT survey responses available.",
|
"NO_RECORDS": "There are no CSAT survey responses available.",
|
||||||
|
|||||||
@@ -146,7 +146,10 @@
|
|||||||
"CSAT": "CSAT",
|
"CSAT": "CSAT",
|
||||||
"CAMPAIGNS": "Campaigns",
|
"CAMPAIGNS": "Campaigns",
|
||||||
"ONGOING": "Ongoing",
|
"ONGOING": "Ongoing",
|
||||||
"ONE_OFF": "One off"
|
"ONE_OFF": "One off",
|
||||||
|
"REPORTS_AGENT": "Agents",
|
||||||
|
"REPORTS_LABEL": "Labels",
|
||||||
|
"REPORTS_INBOX": "Inbox"
|
||||||
},
|
},
|
||||||
"CREATE_ACCOUNT": {
|
"CREATE_ACCOUNT": {
|
||||||
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
|
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
|
||||||
|
|||||||
30
app/javascript/dashboard/i18n/sidebarItems/campaigns.js
Normal file
30
app/javascript/dashboard/i18n/sidebarItems/campaigns.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
|
|
||||||
|
const campaigns = accountId => ({
|
||||||
|
routes: ['settings_account_campaigns', 'one_off'],
|
||||||
|
menuItems: {
|
||||||
|
back: {
|
||||||
|
icon: 'ion-ios-arrow-back',
|
||||||
|
label: 'HOME',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toStateName: 'home',
|
||||||
|
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||||
|
},
|
||||||
|
ongoingCampaigns: {
|
||||||
|
icon: 'ion-arrow-swap',
|
||||||
|
label: 'ONGOING',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`),
|
||||||
|
toStateName: 'settings_account_campaigns',
|
||||||
|
},
|
||||||
|
onOffCampaigns: {
|
||||||
|
icon: 'ion-radio-waves',
|
||||||
|
label: 'ONE_OFF',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/campaigns/one_off`),
|
||||||
|
toStateName: 'one_off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default campaigns;
|
||||||
66
app/javascript/dashboard/i18n/sidebarItems/common.js
Normal file
66
app/javascript/dashboard/i18n/sidebarItems/common.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
|
|
||||||
|
const common = accountId => ({
|
||||||
|
routes: [
|
||||||
|
'home',
|
||||||
|
'inbox_dashboard',
|
||||||
|
'inbox_conversation',
|
||||||
|
'conversation_through_inbox',
|
||||||
|
'notifications_dashboard',
|
||||||
|
'profile_settings',
|
||||||
|
'profile_settings_index',
|
||||||
|
'label_conversations',
|
||||||
|
'conversations_through_label',
|
||||||
|
'team_conversations',
|
||||||
|
'conversations_through_team',
|
||||||
|
'notifications_index',
|
||||||
|
],
|
||||||
|
menuItems: {
|
||||||
|
assignedToMe: {
|
||||||
|
icon: 'ion-chatbox-working',
|
||||||
|
label: 'CONVERSATIONS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
key: '',
|
||||||
|
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||||
|
toolTip: 'Conversation from all subscribed inboxes',
|
||||||
|
toStateName: 'home',
|
||||||
|
},
|
||||||
|
contacts: {
|
||||||
|
icon: 'ion-person',
|
||||||
|
label: 'CONTACTS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||||
|
toStateName: 'contacts_dashboard',
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
icon: 'ion-ios-bell',
|
||||||
|
label: 'NOTIFICATIONS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/notifications`),
|
||||||
|
toStateName: 'notifications_dashboard',
|
||||||
|
},
|
||||||
|
report: {
|
||||||
|
icon: 'ion-arrow-graph-up-right',
|
||||||
|
label: 'REPORTS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports`),
|
||||||
|
toStateName: 'settings_account_reports',
|
||||||
|
},
|
||||||
|
campaigns: {
|
||||||
|
icon: 'ion-speakerphone',
|
||||||
|
label: 'CAMPAIGNS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/campaigns`),
|
||||||
|
toStateName: 'settings_account_campaigns',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
icon: 'ion-settings',
|
||||||
|
label: 'SETTINGS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings`),
|
||||||
|
toStateName: 'settings_home',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default common;
|
||||||
27
app/javascript/dashboard/i18n/sidebarItems/contacts.js
Normal file
27
app/javascript/dashboard/i18n/sidebarItems/contacts.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
|
|
||||||
|
const contacts = accountId => ({
|
||||||
|
routes: [
|
||||||
|
'contacts_dashboard',
|
||||||
|
'contacts_dashboard_manage',
|
||||||
|
'contacts_labels_dashboard',
|
||||||
|
],
|
||||||
|
menuItems: {
|
||||||
|
back: {
|
||||||
|
icon: 'ion-ios-arrow-back',
|
||||||
|
label: 'HOME',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toStateName: 'home',
|
||||||
|
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||||
|
},
|
||||||
|
contacts: {
|
||||||
|
icon: 'ion-person',
|
||||||
|
label: 'ALL_CONTACTS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||||
|
toStateName: 'contacts_dashboard',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default contacts;
|
||||||
57
app/javascript/dashboard/i18n/sidebarItems/reports.js
Normal file
57
app/javascript/dashboard/i18n/sidebarItems/reports.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
|
|
||||||
|
const reports = accountId => ({
|
||||||
|
routes: [
|
||||||
|
'settings_account_reports',
|
||||||
|
'csat_reports',
|
||||||
|
'agent_reports',
|
||||||
|
'label_reports',
|
||||||
|
'inbox_reports',
|
||||||
|
],
|
||||||
|
menuItems: {
|
||||||
|
back: {
|
||||||
|
icon: 'ion-ios-arrow-back',
|
||||||
|
label: 'HOME',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toStateName: 'home',
|
||||||
|
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||||
|
},
|
||||||
|
reportOverview: {
|
||||||
|
icon: 'ion-arrow-graph-up-right',
|
||||||
|
label: 'REPORTS_OVERVIEW',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/overview`),
|
||||||
|
toStateName: 'settings_account_reports',
|
||||||
|
},
|
||||||
|
csatReports: {
|
||||||
|
icon: 'ion-happy',
|
||||||
|
label: 'CSAT',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/csat`),
|
||||||
|
toStateName: 'csat_reports',
|
||||||
|
},
|
||||||
|
agentReports: {
|
||||||
|
icon: 'ion-ios-people',
|
||||||
|
label: 'REPORTS_AGENT',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/agent`),
|
||||||
|
toStateName: 'agent_reports',
|
||||||
|
},
|
||||||
|
labelReports: {
|
||||||
|
icon: 'ion-pricetags',
|
||||||
|
label: 'REPORTS_LABEL',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/label`),
|
||||||
|
toStateName: 'label_reports',
|
||||||
|
},
|
||||||
|
inboxReports: {
|
||||||
|
icon: 'ion-archive',
|
||||||
|
label: 'REPORTS_INBOX',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/inboxes`),
|
||||||
|
toStateName: 'inbox_reports',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reports;
|
||||||
108
app/javascript/dashboard/i18n/sidebarItems/settings.js
Normal file
108
app/javascript/dashboard/i18n/sidebarItems/settings.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
|
|
||||||
|
const settings = accountId => ({
|
||||||
|
routes: [
|
||||||
|
'agent_list',
|
||||||
|
'canned_list',
|
||||||
|
'labels_list',
|
||||||
|
'settings_inbox',
|
||||||
|
'attributes_list',
|
||||||
|
'settings_inbox_new',
|
||||||
|
'settings_inbox_list',
|
||||||
|
'settings_inbox_show',
|
||||||
|
'settings_inboxes_page_channel',
|
||||||
|
'settings_inboxes_add_agents',
|
||||||
|
'settings_inbox_finish',
|
||||||
|
'settings_integrations',
|
||||||
|
'settings_integrations_webhook',
|
||||||
|
'settings_integrations_integration',
|
||||||
|
'settings_applications',
|
||||||
|
'settings_applications_webhook',
|
||||||
|
'settings_applications_integration',
|
||||||
|
'general_settings',
|
||||||
|
'general_settings_index',
|
||||||
|
'settings_teams_list',
|
||||||
|
'settings_teams_new',
|
||||||
|
'settings_teams_add_agents',
|
||||||
|
'settings_teams_finish',
|
||||||
|
'settings_teams_edit',
|
||||||
|
'settings_teams_edit_members',
|
||||||
|
'settings_teams_edit_finish',
|
||||||
|
],
|
||||||
|
menuItems: {
|
||||||
|
back: {
|
||||||
|
icon: 'ion-ios-arrow-back',
|
||||||
|
label: 'HOME',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toStateName: 'home',
|
||||||
|
toState: frontendURL(`accounts/${accountId}/dashboard`),
|
||||||
|
},
|
||||||
|
agents: {
|
||||||
|
icon: 'ion-person-stalker',
|
||||||
|
label: 'AGENTS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
|
||||||
|
toStateName: 'agent_list',
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
icon: 'ion-ios-people',
|
||||||
|
label: 'TEAMS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
|
||||||
|
toStateName: 'settings_teams_list',
|
||||||
|
},
|
||||||
|
inboxes: {
|
||||||
|
icon: 'ion-archive',
|
||||||
|
label: 'INBOXES',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`),
|
||||||
|
toStateName: 'settings_inbox_list',
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
icon: 'ion-pricetags',
|
||||||
|
label: 'LABELS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
|
||||||
|
toStateName: 'labels_list',
|
||||||
|
},
|
||||||
|
attributes: {
|
||||||
|
icon: 'ion-code',
|
||||||
|
label: 'ATTRIBUTES',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/attributes/list`),
|
||||||
|
toStateName: 'attributes_list',
|
||||||
|
},
|
||||||
|
cannedResponses: {
|
||||||
|
icon: 'ion-chatbox-working',
|
||||||
|
label: 'CANNED_RESPONSES',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(
|
||||||
|
`accounts/${accountId}/settings/canned-response/list`
|
||||||
|
),
|
||||||
|
toStateName: 'canned_list',
|
||||||
|
},
|
||||||
|
settings_integrations: {
|
||||||
|
icon: 'ion-flash',
|
||||||
|
label: 'INTEGRATIONS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
||||||
|
toStateName: 'settings_integrations',
|
||||||
|
},
|
||||||
|
settings_applications: {
|
||||||
|
icon: 'ion-asterisk',
|
||||||
|
label: 'APPLICATIONS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/applications`),
|
||||||
|
toStateName: 'settings_applications',
|
||||||
|
},
|
||||||
|
general_settings_index: {
|
||||||
|
icon: 'ion-gear-a',
|
||||||
|
label: 'ACCOUNT_SETTINGS',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/settings/general`),
|
||||||
|
toStateName: 'general_settings_index',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default settings;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="row app-wrapper">
|
<div class="row app-wrapper">
|
||||||
<sidebar :route="currentRoute" :class="sidebarClassName"></sidebar>
|
<sidebar :route="currentRoute" :class="sidebarClassName"></sidebar>
|
||||||
<section class="app-content columns" :class="contentClassName">
|
<section class="app-content columns" :class="contentClassName">
|
||||||
<router-view></router-view>
|
<router-view :key="$route.path"></router-view>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<woot-reports
|
||||||
|
key="agent-reports"
|
||||||
|
type="agent"
|
||||||
|
getter-key="agents/getAgents"
|
||||||
|
action-key="agents/get"
|
||||||
|
:download-button-label="$t('REPORT.DOWNLOAD_AGENT_REPORTS')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WootReports from './components/WootReports';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WootReports,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<woot-reports
|
||||||
|
key="inbox-reports"
|
||||||
|
type="inbox"
|
||||||
|
getter-key="inboxes/getInboxes"
|
||||||
|
action-key="inboxes/get"
|
||||||
|
:download-button-label="$t('INBOX_REPORTS.DOWNLOAD_INBOX_REPORTS')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WootReports from './components/WootReports';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WootReports,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<woot-reports
|
||||||
|
key="label-reports"
|
||||||
|
type="label"
|
||||||
|
getter-key="labels/getLabels"
|
||||||
|
action-key="labels/get"
|
||||||
|
:download-button-label="$t('LABEL_REPORTS.DOWNLOAD_LABEL_REPORTS')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import WootReports from './components/WootReports';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WootReports,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex-container flex-dir-column medium-flex-dir-row">
|
||||||
|
<div v-if="type === 'agent'" class="small-12 medium-3 pull-right">
|
||||||
|
<p aria-hidden="true" class="hide">
|
||||||
|
{{ $t('AGENT_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||||
|
</p>
|
||||||
|
<multiselect
|
||||||
|
v-model="currentSelectedFilter"
|
||||||
|
:placeholder="$t('AGENT_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
:options="filterItemsList"
|
||||||
|
:option-height="24"
|
||||||
|
:show-labels="false"
|
||||||
|
@input="changeFilterSelection"
|
||||||
|
>
|
||||||
|
<template slot="singleLabel" slot-scope="props">
|
||||||
|
<div class="display-flex">
|
||||||
|
<thumbnail
|
||||||
|
src="props.option.thumbnail"
|
||||||
|
:username="props.option.name"
|
||||||
|
size="22px"
|
||||||
|
class="margin-right-small"
|
||||||
|
/>
|
||||||
|
<span class="reports-option__desc">
|
||||||
|
<span class="reports-option__title">{{ props.option.name }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="option" slot-scope="props">
|
||||||
|
<div class="display-flex">
|
||||||
|
<thumbnail
|
||||||
|
src="props.option.thumbnail"
|
||||||
|
:username="props.option.name"
|
||||||
|
size="22px"
|
||||||
|
class="margin-right-small"
|
||||||
|
/>
|
||||||
|
<p>{{ props.option.name }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-if="type === 'label'" class="small-12 medium-3 pull-right">
|
||||||
|
<p aria-hidden="true" class="hide">
|
||||||
|
{{ $t('LABEL_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||||
|
</p>
|
||||||
|
<multiselect
|
||||||
|
v-model="currentSelectedFilter"
|
||||||
|
:placeholder="$t('LABEL_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||||
|
label="title"
|
||||||
|
track-by="id"
|
||||||
|
:options="filterItemsList"
|
||||||
|
:option-height="24"
|
||||||
|
:show-labels="false"
|
||||||
|
@input="changeFilterSelection"
|
||||||
|
>
|
||||||
|
<template slot="singleLabel" slot-scope="props">
|
||||||
|
<div class="display-flex">
|
||||||
|
<div
|
||||||
|
:style="{ backgroundColor: props.option.color }"
|
||||||
|
class="reports-option__rounded--item margin-right-small"
|
||||||
|
></div>
|
||||||
|
<span class="reports-option__desc">
|
||||||
|
<span class="reports-option__title">{{
|
||||||
|
props.option.title
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="option" slot-scope="props">
|
||||||
|
<div class="display-flex">
|
||||||
|
<div
|
||||||
|
:style="{ backgroundColor: props.option.color }"
|
||||||
|
class="
|
||||||
|
reports-option__rounded--item
|
||||||
|
reports-option__item
|
||||||
|
reports-option__label--swatch
|
||||||
|
"
|
||||||
|
></div>
|
||||||
|
<span class="reports-option__desc">
|
||||||
|
<span class="reports-option__title">{{
|
||||||
|
props.option.title
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-if="type === 'inbox'" class="small-12 medium-3 pull-right">
|
||||||
|
<p aria-hidden="true" class="hide">
|
||||||
|
{{ $t('INBOX_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||||
|
</p>
|
||||||
|
<multiselect
|
||||||
|
v-model="currentSelectedFilter"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:placeholder="$t('INBOX_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:options="filterItemsList"
|
||||||
|
:searchable="false"
|
||||||
|
:allow-empty="false"
|
||||||
|
@input="changeFilterSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="small-12 medium-3 pull-right margin-left-small">
|
||||||
|
<multiselect
|
||||||
|
v-model="currentDateRangeSelection"
|
||||||
|
track-by="name"
|
||||||
|
label="name"
|
||||||
|
:placeholder="$t('FORMS.MULTISELECT.SELECT_ONE')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:options="dateRange"
|
||||||
|
:searchable="false"
|
||||||
|
:allow-empty="false"
|
||||||
|
@select="changeDateSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<woot-date-range-picker
|
||||||
|
v-if="isDateRangeSelected"
|
||||||
|
show-range
|
||||||
|
:value="customDateRange"
|
||||||
|
:confirm-text="$t('REPORT.CUSTOM_DATE_RANGE.CONFIRM')"
|
||||||
|
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
|
||||||
|
@change="onChange"
|
||||||
|
/>
|
||||||
|
</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 getUnixTime from 'date-fns/getUnixTime';
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
WootDateRangePicker,
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filterItemsList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'agent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentSelectedFilter: null,
|
||||||
|
currentDateRangeSelection: this.$t('REPORT.DATE_RANGE')[0],
|
||||||
|
dateRange: this.$t('REPORT.DATE_RANGE'),
|
||||||
|
customDateRange: [new Date(), new Date()],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isDateRangeSelected() {
|
||||||
|
return this.currentDateRangeSelection.id === CUSTOM_DATE_RANGE_ID;
|
||||||
|
},
|
||||||
|
to() {
|
||||||
|
if (this.isDateRangeSelected) {
|
||||||
|
return this.fromCustomDate(this.customDateRange[1]);
|
||||||
|
}
|
||||||
|
return this.fromCustomDate(new Date());
|
||||||
|
},
|
||||||
|
from() {
|
||||||
|
if (this.isDateRangeSelected) {
|
||||||
|
return this.fromCustomDate(this.customDateRange[0]);
|
||||||
|
}
|
||||||
|
const dateRange = {
|
||||||
|
0: 6,
|
||||||
|
1: 29,
|
||||||
|
2: 89,
|
||||||
|
3: 179,
|
||||||
|
4: 364,
|
||||||
|
};
|
||||||
|
const diff = dateRange[this.currentDateRangeSelection.id];
|
||||||
|
const fromDate = subDays(new Date(), diff);
|
||||||
|
return this.fromCustomDate(fromDate);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
filterItemsList(val) {
|
||||||
|
this.currentSelectedFilter = val[0];
|
||||||
|
},
|
||||||
|
currentSelectedFilter() {
|
||||||
|
this.changeFilterSelection();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.onDateRangeChange();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDateRangeChange() {
|
||||||
|
this.$emit('date-range-change', { from: this.from, to: this.to });
|
||||||
|
},
|
||||||
|
fromCustomDate(date) {
|
||||||
|
return getUnixTime(startOfDay(date));
|
||||||
|
},
|
||||||
|
changeDateSelection(selectedRange) {
|
||||||
|
this.currentDateRangeSelection = selectedRange;
|
||||||
|
this.onDateRangeChange();
|
||||||
|
},
|
||||||
|
changeFilterSelection() {
|
||||||
|
this.$emit('filter-change', this.currentSelectedFilter);
|
||||||
|
},
|
||||||
|
onChange(value) {
|
||||||
|
this.customDateRange = value;
|
||||||
|
this.onDateRangeChange();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '~dashboard/assets/scss/widgets/_reports';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<div class="column content-box">
|
||||||
|
<woot-button
|
||||||
|
color-scheme="success"
|
||||||
|
class-names="button--fixed-right-top"
|
||||||
|
icon="ion-android-download"
|
||||||
|
@click="downloadReports"
|
||||||
|
>
|
||||||
|
{{ downloadButtonLabel }}
|
||||||
|
</woot-button>
|
||||||
|
<report-filters
|
||||||
|
v-if="filterItemsList"
|
||||||
|
:type="type"
|
||||||
|
:filter-items-list="filterItemsList"
|
||||||
|
@date-range-change="onDateRangeChange"
|
||||||
|
@filter-change="onFilterChange"
|
||||||
|
/>
|
||||||
|
<div v-if="selectedFilter">
|
||||||
|
<div class="row">
|
||||||
|
<woot-report-stats-card
|
||||||
|
v-for="(metric, index) in metrics"
|
||||||
|
:key="metric.NAME"
|
||||||
|
:desc="metric.DESC"
|
||||||
|
:heading="metric.NAME"
|
||||||
|
:index="index"
|
||||||
|
:on-click="changeSelection"
|
||||||
|
:point="accountSummary[metric.KEY]"
|
||||||
|
:selected="index === currentSelection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="report-bar">
|
||||||
|
<woot-loading-state
|
||||||
|
v-if="accountReport.isFetching"
|
||||||
|
:message="$t('REPORT.LOADING_CHART')"
|
||||||
|
/>
|
||||||
|
<div v-else class="chart-container">
|
||||||
|
<woot-bar v-if="accountReport.data.length" :collection="collection" />
|
||||||
|
<span v-else class="empty-state">
|
||||||
|
{{ $t('REPORT.NO_ENOUGH_DATA') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ReportFilters from './ReportFilters';
|
||||||
|
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
|
const REPORTS_KEYS = {
|
||||||
|
CONVERSATIONS: 'conversations_count',
|
||||||
|
INCOMING_MESSAGES: 'incoming_messages_count',
|
||||||
|
OUTGOING_MESSAGES: 'outgoing_messages_count',
|
||||||
|
FIRST_RESPONSE_TIME: 'avg_first_response_time',
|
||||||
|
RESOLUTION_TIME: 'avg_resolution_time',
|
||||||
|
RESOLUTION_COUNT: 'resolutions_count',
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ReportFilters,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'account',
|
||||||
|
},
|
||||||
|
getterKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
actionKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
downloadButtonLabel: {
|
||||||
|
type: String,
|
||||||
|
default: 'Download Reports',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
from: 0,
|
||||||
|
to: 0,
|
||||||
|
currentSelection: 0,
|
||||||
|
selectedFilter: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filterItemsList() {
|
||||||
|
return this.$store.getters[this.getterKey] || [];
|
||||||
|
},
|
||||||
|
accountSummary() {
|
||||||
|
return this.$store.getters.getAccountSummary || [];
|
||||||
|
},
|
||||||
|
accountReport() {
|
||||||
|
return this.$store.getters.getAccountReports || [];
|
||||||
|
},
|
||||||
|
collection() {
|
||||||
|
if (this.accountReport.isFetching) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!this.accountReport.data.length) return {};
|
||||||
|
const labels = this.accountReport.data.map(element =>
|
||||||
|
format(fromUnixTime(element.timestamp), 'dd/MMM')
|
||||||
|
);
|
||||||
|
const data = this.accountReport.data.map(element => element.value);
|
||||||
|
return {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: this.metrics[this.currentSelection].NAME,
|
||||||
|
backgroundColor: '#1f93ff',
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
metrics() {
|
||||||
|
const reportKeys = [
|
||||||
|
'CONVERSATIONS',
|
||||||
|
'INCOMING_MESSAGES',
|
||||||
|
'OUTGOING_MESSAGES',
|
||||||
|
'FIRST_RESPONSE_TIME',
|
||||||
|
'RESOLUTION_TIME',
|
||||||
|
'RESOLUTION_COUNT',
|
||||||
|
];
|
||||||
|
return reportKeys.map(key => ({
|
||||||
|
NAME: this.$t(`REPORT.METRICS.${key}.NAME`),
|
||||||
|
KEY: REPORTS_KEYS[key],
|
||||||
|
DESC: this.$t(`REPORT.METRICS.${key}.DESC`),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch(this.actionKey);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchAllData() {
|
||||||
|
if (this.selectedFilter) {
|
||||||
|
const { from, to } = this;
|
||||||
|
this.$store.dispatch('fetchAccountSummary', {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
type: this.type,
|
||||||
|
id: this.selectedFilter.id,
|
||||||
|
});
|
||||||
|
this.fetchChartData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchChartData() {
|
||||||
|
const { from, to } = this;
|
||||||
|
this.$store.dispatch('fetchAccountReport', {
|
||||||
|
metric: this.metrics[this.currentSelection].KEY,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
type: this.type,
|
||||||
|
id: this.selectedFilter.id,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
downloadReports() {
|
||||||
|
const { from, to } = this;
|
||||||
|
const fileName = `${this.type}-report-${format(
|
||||||
|
fromUnixTime(to),
|
||||||
|
'dd-MM-yyyy'
|
||||||
|
)}.csv`;
|
||||||
|
switch (this.type) {
|
||||||
|
case 'agent':
|
||||||
|
this.$store.dispatch('downloadAgentReports', { from, to, fileName });
|
||||||
|
break;
|
||||||
|
case 'label':
|
||||||
|
this.$store.dispatch('downloadLabelReports', { from, to, fileName });
|
||||||
|
break;
|
||||||
|
case 'inbox':
|
||||||
|
this.$store.dispatch('downloadInboxReports', { from, to, fileName });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeSelection(index) {
|
||||||
|
this.currentSelection = index;
|
||||||
|
this.fetchChartData();
|
||||||
|
},
|
||||||
|
onDateRangeChange({ from, to }) {
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.fetchAllData();
|
||||||
|
},
|
||||||
|
onFilterChange(payload) {
|
||||||
|
if (payload) {
|
||||||
|
this.selectedFilter = payload;
|
||||||
|
this.fetchAllData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import Index from './Index';
|
import Index from './Index';
|
||||||
|
import AgentReports from './AgentReports';
|
||||||
|
import LabelReports from './LabelReports';
|
||||||
|
import InboxReports from './InboxReports';
|
||||||
import CsatResponses from './CsatResponses';
|
import CsatResponses from './CsatResponses';
|
||||||
import SettingsContent from '../Wrapper';
|
import SettingsContent from '../Wrapper';
|
||||||
import { frontendURL } from '../../../../helper/URLHelper';
|
import { frontendURL } from '../../../../helper/URLHelper';
|
||||||
@@ -41,5 +44,53 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
|
component: SettingsContent,
|
||||||
|
props: {
|
||||||
|
headerTitle: 'AGENT_REPORTS.HEADER',
|
||||||
|
icon: 'ion-people',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'agent',
|
||||||
|
name: 'agent_reports',
|
||||||
|
roles: ['administrator'],
|
||||||
|
component: AgentReports,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
|
component: SettingsContent,
|
||||||
|
props: {
|
||||||
|
headerTitle: 'LABEL_REPORTS.HEADER',
|
||||||
|
icon: 'ion-pricetags',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'label',
|
||||||
|
name: 'label_reports',
|
||||||
|
roles: ['administrator'],
|
||||||
|
component: LabelReports,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
|
component: SettingsContent,
|
||||||
|
props: {
|
||||||
|
headerTitle: 'INBOX_REPORTS.HEADER',
|
||||||
|
icon: 'ion-archive',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'inboxes',
|
||||||
|
name: 'inbox_reports',
|
||||||
|
roles: ['administrator'],
|
||||||
|
component: InboxReports,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ const getters = {
|
|||||||
export const actions = {
|
export const actions = {
|
||||||
fetchAccountReport({ commit }, reportObj) {
|
fetchAccountReport({ commit }, reportObj) {
|
||||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, true);
|
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, true);
|
||||||
Report.getAccountReports(
|
Report.getReports(
|
||||||
reportObj.metric,
|
reportObj.metric,
|
||||||
reportObj.from,
|
reportObj.from,
|
||||||
reportObj.to
|
reportObj.to,
|
||||||
|
reportObj.type,
|
||||||
|
reportObj.id
|
||||||
).then(accountReport => {
|
).then(accountReport => {
|
||||||
let { data } = accountReport;
|
let { data } = accountReport;
|
||||||
data = data.filter(
|
data = data.filter(
|
||||||
@@ -60,7 +62,12 @@ export const actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchAccountSummary({ commit }, reportObj) {
|
fetchAccountSummary({ commit }, reportObj) {
|
||||||
Report.getAccountSummary(reportObj.from, reportObj.to)
|
Report.getSummary(
|
||||||
|
reportObj.from,
|
||||||
|
reportObj.to,
|
||||||
|
reportObj.type,
|
||||||
|
reportObj.id
|
||||||
|
)
|
||||||
.then(accountSummary => {
|
.then(accountSummary => {
|
||||||
commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data);
|
commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data);
|
||||||
})
|
})
|
||||||
@@ -85,6 +92,40 @@ export const actions = {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
downloadLabelReports(_, reportObj) {
|
||||||
|
return Report.getLabelReports(reportObj.from, reportObj.to)
|
||||||
|
.then(response => {
|
||||||
|
let csvContent = 'data:text/csv;charset=utf-8,' + response.data;
|
||||||
|
var encodedUri = encodeURI(csvContent);
|
||||||
|
var downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = encodedUri;
|
||||||
|
downloadLink.download = reportObj.fileName;
|
||||||
|
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
downloadInboxReports(_, reportObj) {
|
||||||
|
return Report.getInboxReports(reportObj.from, reportObj.to)
|
||||||
|
.then(response => {
|
||||||
|
let csvContent = 'data:text/csv;charset=utf-8,' + response.data;
|
||||||
|
var encodedUri = encodeURI(csvContent);
|
||||||
|
var downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = encodedUri;
|
||||||
|
downloadLink.download = reportObj.fileName;
|
||||||
|
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
// document.body.removeChild(downloadLink);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
|||||||
@@ -5,7 +5,17 @@ global.open = jest.fn();
|
|||||||
global.axios = axios;
|
global.axios = axios;
|
||||||
jest.mock('axios');
|
jest.mock('axios');
|
||||||
|
|
||||||
|
const createElementSpy = () => {
|
||||||
|
const element = document.createElement('a');
|
||||||
|
jest.spyOn(document, 'createElement').mockImplementation(() => element);
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
describe('#actions', () => {
|
describe('#actions', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
describe('#downloadAgentReports', () => {
|
describe('#downloadAgentReports', () => {
|
||||||
it('open CSV download prompt if API is success', async () => {
|
it('open CSV download prompt if API is success', async () => {
|
||||||
axios.get.mockResolvedValue({
|
axios.get.mockResolvedValue({
|
||||||
@@ -17,15 +27,55 @@ describe('#actions', () => {
|
|||||||
to: 1630504922510,
|
to: 1630504922510,
|
||||||
fileName: 'agent-report-01-09-2021.csv',
|
fileName: 'agent-report-01-09-2021.csv',
|
||||||
};
|
};
|
||||||
const mockDownloadElement = document.createElement('a');
|
const mockAgentDownloadElement = createElementSpy();
|
||||||
jest
|
|
||||||
.spyOn(document, 'createElement')
|
|
||||||
.mockImplementation(() => mockDownloadElement);
|
|
||||||
await actions.downloadAgentReports(1, param);
|
await actions.downloadAgentReports(1, param);
|
||||||
expect(mockDownloadElement.href).toEqual(
|
expect(mockAgentDownloadElement.href).toEqual(
|
||||||
'data:text/csv;charset=utf-8,Agent%20name,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20Pranav,36,114,28411'
|
'data:text/csv;charset=utf-8,Agent%20name,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20Pranav,36,114,28411'
|
||||||
);
|
);
|
||||||
expect(mockDownloadElement.download).toEqual(param.fileName);
|
expect(mockAgentDownloadElement.download).toEqual(param.fileName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#downloadLabelReports', () => {
|
||||||
|
it('open CSV download prompt if API is success', async () => {
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: `Label Title,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
|
||||||
|
website,0,0,0`,
|
||||||
|
});
|
||||||
|
const param = {
|
||||||
|
from: 1632335400,
|
||||||
|
to: 1632853800,
|
||||||
|
type: 'label',
|
||||||
|
fileName: 'label-report-01-09-2021.csv',
|
||||||
|
};
|
||||||
|
const mockLabelDownloadElement = createElementSpy();
|
||||||
|
await actions.downloadLabelReports(1, param);
|
||||||
|
expect(mockLabelDownloadElement.href).toEqual(
|
||||||
|
'data:text/csv;charset=utf-8,Label%20Title,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20website,0,0,0'
|
||||||
|
);
|
||||||
|
expect(mockLabelDownloadElement.download).toEqual(param.fileName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#downloadInboxReports', () => {
|
||||||
|
it('open CSV download prompt if API is success', async () => {
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: `Inbox name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
|
||||||
|
Fayaz,2,127,0
|
||||||
|
EMa,0,0,0
|
||||||
|
Twillio WA,0,0,0`,
|
||||||
|
});
|
||||||
|
const param = {
|
||||||
|
from: 1631039400,
|
||||||
|
to: 1635013800,
|
||||||
|
fileName: 'inbox-report-24-10-2021.csv',
|
||||||
|
};
|
||||||
|
const mockInboxDownloadElement = createElementSpy();
|
||||||
|
await actions.downloadInboxReports(1, param);
|
||||||
|
expect(mockInboxDownloadElement.href).toEqual(
|
||||||
|
'data:text/csv;charset=utf-8,Inbox%20name,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20Fayaz,2,127,0%0A%20%20%20%20%20%20%20%20EMa,0,0,0%0A%20%20%20%20%20%20%20%20Twillio%20WA,0,0,0'
|
||||||
|
);
|
||||||
|
expect(mockInboxDownloadElement.download).toEqual(param.fileName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user