feat: Add reports about live agent load (#4537)
* feat: Add reports about live agent load
This commit is contained in:
parent
899176a793
commit
676796ddc7
@ -4,6 +4,7 @@ class V2::ReportBuilder
|
|||||||
attr_reader :account, :params
|
attr_reader :account, :params
|
||||||
|
|
||||||
DEFAULT_GROUP_BY = 'day'.freeze
|
DEFAULT_GROUP_BY = 'day'.freeze
|
||||||
|
AGENT_RESULTS_PER_PAGE = 10
|
||||||
|
|
||||||
def initialize(account, params)
|
def initialize(account, params)
|
||||||
@account = account
|
@account = account
|
||||||
@ -79,12 +80,15 @@ class V2::ReportBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def agent_metrics
|
def agent_metrics
|
||||||
users = @account.users
|
account_users = @account.account_users.page(params[:page]).per(AGENT_RESULTS_PER_PAGE)
|
||||||
users = users.where(id: params[:user_id]) if params[:user_id].present?
|
account_users.each_with_object([]) do |account_user, arr|
|
||||||
users.each_with_object([]) do |user, arr|
|
@user = account_user.user
|
||||||
@user = user
|
|
||||||
arr << {
|
arr << {
|
||||||
user: { id: user.id, name: user.name, thumbnail: user.avatar_url },
|
id: @user.id,
|
||||||
|
name: @user.name,
|
||||||
|
email: @user.email,
|
||||||
|
thumbnail: @user.avatar_url,
|
||||||
|
availability: account_user.availability_status,
|
||||||
metric: conversations
|
metric: conversations
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@ -94,7 +98,7 @@ class V2::ReportBuilder
|
|||||||
@open_conversations = scope.conversations.open
|
@open_conversations = scope.conversations.open
|
||||||
first_response_count = scope.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
|
first_response_count = scope.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
|
||||||
metric = {
|
metric = {
|
||||||
open: @open_conversations.count,
|
total: @open_conversations.count,
|
||||||
unattended: @open_conversations.count - first_response_count
|
unattended: @open_conversations.count - first_response_count
|
||||||
}
|
}
|
||||||
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
||||||
|
|||||||
@ -82,7 +82,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
def conversation_params
|
def conversation_params
|
||||||
{
|
{
|
||||||
type: params[:type].to_sym,
|
type: params[:type].to_sym,
|
||||||
user_id: params[:user_id]
|
user_id: params[:user_id],
|
||||||
|
page: params[:page].presence || 1
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -44,6 +44,15 @@ class ReportsAPI extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConversationMetric(type = 'account', page = 1) {
|
||||||
|
return axios.get(`${this.url}/conversations`, {
|
||||||
|
params: {
|
||||||
|
type,
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getAgentReports(since, until) {
|
getAgentReports(since, until) {
|
||||||
return axios.get(`${this.url}/agents`, {
|
return axios.get(`${this.url}/agents`, {
|
||||||
params: { since, until },
|
params: { since, until },
|
||||||
|
|||||||
@ -97,5 +97,18 @@ describe('#Reports API', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#getConversationMetric', () => {
|
||||||
|
reportsAPI.getConversationMetric('account');
|
||||||
|
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||||
|
'/api/v2/reports/conversations',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
type: 'account',
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { frontendURL } from '../../../../helper/URLHelper';
|
|||||||
const reports = accountId => ({
|
const reports = accountId => ({
|
||||||
parentNav: 'reports',
|
parentNav: 'reports',
|
||||||
routes: [
|
routes: [
|
||||||
'settings_account_reports',
|
'account_overview_reports',
|
||||||
|
'conversation_reports',
|
||||||
'csat_reports',
|
'csat_reports',
|
||||||
'agent_reports',
|
'agent_reports',
|
||||||
'label_reports',
|
'label_reports',
|
||||||
@ -16,7 +17,14 @@ const reports = accountId => ({
|
|||||||
label: 'REPORTS_OVERVIEW',
|
label: 'REPORTS_OVERVIEW',
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/reports/overview`),
|
toState: frontendURL(`accounts/${accountId}/reports/overview`),
|
||||||
toStateName: 'settings_account_reports',
|
toStateName: 'account_overview_reports',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'chat',
|
||||||
|
label: 'REPORTS_CONVERSATION',
|
||||||
|
hasSubMenu: false,
|
||||||
|
toState: frontendURL(`accounts/${accountId}/reports/conversation`),
|
||||||
|
toStateName: 'conversation_reports',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'emoji',
|
icon: 'emoji',
|
||||||
|
|||||||
@ -23,6 +23,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
'contact.updated': this.onContactUpdate,
|
'contact.updated': this.onContactUpdate,
|
||||||
'conversation.mentioned': this.onConversationMentioned,
|
'conversation.mentioned': this.onConversationMentioned,
|
||||||
'notification.created': this.onNotificationCreated,
|
'notification.created': this.onNotificationCreated,
|
||||||
|
'first.reply.created': this.onFirstReplyCreated,
|
||||||
'conversation.read': this.onConversationRead,
|
'conversation.read': this.onConversationRead,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -128,6 +129,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
|
|
||||||
fetchConversationStats = () => {
|
fetchConversationStats = () => {
|
||||||
bus.$emit('fetch_conversation_stats');
|
bus.$emit('fetch_conversation_stats');
|
||||||
|
bus.$emit('fetch_overview_reports');
|
||||||
};
|
};
|
||||||
|
|
||||||
onContactDelete = data => {
|
onContactDelete = data => {
|
||||||
@ -145,6 +147,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
onNotificationCreated = data => {
|
onNotificationCreated = data => {
|
||||||
this.app.$store.dispatch('notifications/addNotification', data);
|
this.app.$store.dispatch('notifications/addNotification', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onFirstReplyCreated = () => {
|
||||||
|
bus.$emit('fetch_overview_reports');
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -108,6 +108,7 @@
|
|||||||
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
|
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
|
||||||
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
|
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
|
||||||
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
|
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
|
||||||
|
"GO_TO_CONVERSATION_REPORTS": "Go to Conversation Reports",
|
||||||
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
|
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
|
||||||
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
|
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
|
||||||
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",
|
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"REPORT": {
|
"REPORT": {
|
||||||
"HEADER": "Overview",
|
"HEADER": "Conversations",
|
||||||
"LOADING_CHART": "Loading chart data...",
|
"LOADING_CHART": "Loading chart data...",
|
||||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||||
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
||||||
@ -381,5 +381,33 @@
|
|||||||
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
|
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"OVERVIEW_REPORTS": {
|
||||||
|
"HEADER": "Overview",
|
||||||
|
"LIVE": "Live",
|
||||||
|
"ACCOUNT_CONVERSATIONS": {
|
||||||
|
"HEADER": "Open Conversations",
|
||||||
|
"LOADING_MESSAGE": "Conversations Loading...",
|
||||||
|
"TOTAL" : "Total",
|
||||||
|
"UNATTENDED": "Unattended",
|
||||||
|
"UNASSIGNED": "Unassigned"
|
||||||
|
},
|
||||||
|
"AGENT_CONVERSATIONS": {
|
||||||
|
"HEADER": "Conversations by agents",
|
||||||
|
"LOADING_MESSAGE": "Agents Loading...",
|
||||||
|
"NO_AGENTS": "There are no conversations by agents",
|
||||||
|
"TABLE_HEADER": {
|
||||||
|
"AGENT": "Agent",
|
||||||
|
"TOTAL": "Total",
|
||||||
|
"UNATTENDED": "Unattended",
|
||||||
|
"STATUS": "Status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AGENT_STATUS": {
|
||||||
|
"HEADER": "Agent status",
|
||||||
|
"ONLINE": "Online",
|
||||||
|
"BUSY": "Busy",
|
||||||
|
"OFFLINE": "Offline"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,7 +173,7 @@
|
|||||||
"NEW_LABEL": "New label",
|
"NEW_LABEL": "New label",
|
||||||
"NEW_TEAM": "New team",
|
"NEW_TEAM": "New team",
|
||||||
"NEW_INBOX": "New inbox",
|
"NEW_INBOX": "New inbox",
|
||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_CONVERSATION": "Conversations",
|
||||||
"CSAT": "CSAT",
|
"CSAT": "CSAT",
|
||||||
"CAMPAIGNS": "Campaigns",
|
"CAMPAIGNS": "Campaigns",
|
||||||
"ONGOING": "Ongoing",
|
"ONGOING": "Ongoing",
|
||||||
@ -183,7 +183,8 @@
|
|||||||
"REPORTS_INBOX": "Inbox",
|
"REPORTS_INBOX": "Inbox",
|
||||||
"REPORTS_TEAM": "Team",
|
"REPORTS_TEAM": "Team",
|
||||||
"SET_AVAILABILITY_TITLE": "Set yourself as",
|
"SET_AVAILABILITY_TITLE": "Set yourself as",
|
||||||
"BETA": "Beta"
|
"BETA": "Beta",
|
||||||
|
"REPORTS_OVERVIEW": "Overview"
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const ICON_SNOOZE_UNTIL_TOMORRROW = `<svg role="img" class="ninja-icon ni
|
|||||||
export const ICON_CONVERSATION_DASHBOARD = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M10.55 2.532a2.25 2.25 0 0 1 2.9 0l6.75 5.692c.507.428.8 1.057.8 1.72v9.803a1.75 1.75 0 0 1-1.75 1.75h-3.5a1.75 1.75 0 0 1-1.75-1.75v-5.5a.25.25 0 0 0-.25-.25h-3.5a.25.25 0 0 0-.25.25v5.5a1.75 1.75 0 0 1-1.75 1.75h-3.5A1.75 1.75 0 0 1 3 19.747V9.944c0-.663.293-1.292.8-1.72l6.75-5.692zm1.933 1.147a.75.75 0 0 0-.966 0L4.767 9.37a.75.75 0 0 0-.267.573v9.803c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-5.5c0-.967.784-1.75 1.75-1.75h3.5c.966 0 1.75.783 1.75 1.75v5.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25V9.944a.75.75 0 0 0-.267-.573l-6.75-5.692z" fill="currentColor"></path></g></svg>`;
|
export const ICON_CONVERSATION_DASHBOARD = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M10.55 2.532a2.25 2.25 0 0 1 2.9 0l6.75 5.692c.507.428.8 1.057.8 1.72v9.803a1.75 1.75 0 0 1-1.75 1.75h-3.5a1.75 1.75 0 0 1-1.75-1.75v-5.5a.25.25 0 0 0-.25-.25h-3.5a.25.25 0 0 0-.25.25v5.5a1.75 1.75 0 0 1-1.75 1.75h-3.5A1.75 1.75 0 0 1 3 19.747V9.944c0-.663.293-1.292.8-1.72l6.75-5.692zm1.933 1.147a.75.75 0 0 0-.966 0L4.767 9.37a.75.75 0 0 0-.267.573v9.803c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-5.5c0-.967.784-1.75 1.75-1.75h3.5c.966 0 1.75.783 1.75 1.75v5.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25V9.944a.75.75 0 0 0-.267-.573l-6.75-5.692z" fill="currentColor"></path></g></svg>`;
|
||||||
export const ICON_CONTACT_DASHBOARD = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438c-1.57 1.834-3.957 2.739-7.102 2.739c-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461c1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75zM12 2.004a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z" fill="currentColor"></path></g></svg>`;
|
export const ICON_CONTACT_DASHBOARD = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438c-1.57 1.834-3.957 2.739-7.102 2.739c-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461c1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75zM12 2.004a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z" fill="currentColor"></path></g></svg>`;
|
||||||
export const ICON_REPORTS_OVERVIEW = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M16.749 2h4.554l.1.014l.099.028l.06.026c.08.034.153.085.219.15l.04.044l.044.057l.054.09l.039.09l.019.064l.014.064l.009.095v4.532a.75.75 0 0 1-1.493.102l-.007-.102V4.559l-6.44 6.44a.75.75 0 0 1-.976.073L13 11L9.97 8.09l-5.69 5.689a.75.75 0 0 1-1.133-.977l.073-.084l6.22-6.22a.75.75 0 0 1 .976-.072l.084.072l3.03 2.91L19.438 3.5h-2.69a.75.75 0 0 1-.742-.648l-.007-.102a.75.75 0 0 1 .648-.743L16.75 2zM3.75 17a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5a.75.75 0 0 1 .75-.75zm5.75-3.25a.75.75 0 0 0-1.5 0v7.5a.75.75 0 0 0 1.5 0v-7.5zM13.75 15a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5a.75.75 0 0 1 .75-.75zm5.75-4.25a.75.75 0 0 0-1.5 0v10.5a.75.75 0 0 0 1.5 0v-10.5z" fill="currentColor"></path></g></svg>`;
|
export const ICON_REPORTS_OVERVIEW = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M16.749 2h4.554l.1.014l.099.028l.06.026c.08.034.153.085.219.15l.04.044l.044.057l.054.09l.039.09l.019.064l.014.064l.009.095v4.532a.75.75 0 0 1-1.493.102l-.007-.102V4.559l-6.44 6.44a.75.75 0 0 1-.976.073L13 11L9.97 8.09l-5.69 5.689a.75.75 0 0 1-1.133-.977l.073-.084l6.22-6.22a.75.75 0 0 1 .976-.072l.084.072l3.03 2.91L19.438 3.5h-2.69a.75.75 0 0 1-.742-.648l-.007-.102a.75.75 0 0 1 .648-.743L16.75 2zM3.75 17a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5a.75.75 0 0 1 .75-.75zm5.75-3.25a.75.75 0 0 0-1.5 0v7.5a.75.75 0 0 0 1.5 0v-7.5zM13.75 15a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0v-5.5a.75.75 0 0 1 .75-.75zm5.75-4.25a.75.75 0 0 0-1.5 0v10.5a.75.75 0 0 0 1.5 0v-10.5z" fill="currentColor"></path></g></svg>`;
|
||||||
|
export const ICON_CONVERSATION_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg>`;
|
||||||
export const ICON_AGENT_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M4 13.999L13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14zM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78zM4 15.499l-.1.01a.51.51 0 0 0-.254.136a.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242c.826.445 2.003.714 3.266.753l.317.005l.317-.005c1.263-.039 2.439-.308 3.266-.753c.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001zM8.5 3a4.5 4.5 0 1 1 0 9a4.5 4.5 0 0 1 0-9zm9 2a3.5 3.5 0 1 1 0 7a3.5 3.5 0 0 1 0-7zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3s3-1.346 3-3s-1.346-3-3-3zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2s2-.897 2-2s-.897-2-2-2z" fill="currentColor"></path></g></svg>`;
|
export const ICON_AGENT_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M4 13.999L13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14zM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78zM4 15.499l-.1.01a.51.51 0 0 0-.254.136a.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242c.826.445 2.003.714 3.266.753l.317.005l.317-.005c1.263-.039 2.439-.308 3.266-.753c.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001zM8.5 3a4.5 4.5 0 1 1 0 9a4.5 4.5 0 0 1 0-9zm9 2a3.5 3.5 0 1 1 0 7a3.5 3.5 0 0 1 0-7zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3s3-1.346 3-3s-1.346-3-3-3zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2s2-.897 2-2s-.897-2-2-2z" fill="currentColor"></path></g></svg>`;
|
||||||
export const ICON_LABEL_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465zm0 1.5h-5.465c-.465 0-.91.185-1.239.513l-8.512 8.523a1.75 1.75 0 0 0 .015 2.462l4.461 4.454a1.755 1.755 0 0 0 2.477 0l8.5-8.503a1.75 1.75 0 0 0 .513-1.237V4.25a.75.75 0 0 0-.75-.75zM17 5.502a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3z" fill="currentColor"></path></g></svg>`;
|
export const ICON_LABEL_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M19.75 2A2.25 2.25 0 0 1 22 4.25v5.462a3.25 3.25 0 0 1-.952 2.298l-8.5 8.503a3.255 3.255 0 0 1-4.597.001L3.489 16.06a3.25 3.25 0 0 1-.003-4.596l8.5-8.51A3.25 3.25 0 0 1 14.284 2h5.465zm0 1.5h-5.465c-.465 0-.91.185-1.239.513l-8.512 8.523a1.75 1.75 0 0 0 .015 2.462l4.461 4.454a1.755 1.755 0 0 0 2.477 0l8.5-8.503a1.75 1.75 0 0 0 .513-1.237V4.25a.75.75 0 0 0-.75-.75zM17 5.502a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3z" fill="currentColor"></path></g></svg>`;
|
||||||
export const ICON_INBOX_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M6.25 3h11.5a3.25 3.25 0 0 1 3.245 3.066L21 6.25v11.5a3.25 3.25 0 0 1-3.066 3.245L17.75 21H6.25a3.25 3.25 0 0 1-3.245-3.066L3 17.75V6.25a3.25 3.25 0 0 1 3.066-3.245L6.25 3h11.5h-11.5zM4.5 14.5v3.25a1.75 1.75 0 0 0 1.606 1.744l.144.006h11.5a1.75 1.75 0 0 0 1.744-1.607l.006-.143V14.5h-3.825a3.752 3.752 0 0 1-3.475 2.995l-.2.005a3.752 3.752 0 0 1-3.632-2.812l-.043-.188H4.5v3.25v-3.25zm13.25-10H6.25a1.75 1.75 0 0 0-1.744 1.606L4.5 6.25V13H9a.75.75 0 0 1 .743.648l.007.102a2.25 2.25 0 0 0 4.495.154l.005-.154a.75.75 0 0 1 .648-.743L15 13h4.5V6.25a1.75 1.75 0 0 0-1.607-1.744L17.75 4.5z" fill="currentColor"></path></g></svg>`;
|
export const ICON_INBOX_REPORTS = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="ninja-icon ninja-icon--fluent" width="18" height="18" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><g fill="none"><path d="M6.25 3h11.5a3.25 3.25 0 0 1 3.245 3.066L21 6.25v11.5a3.25 3.25 0 0 1-3.066 3.245L17.75 21H6.25a3.25 3.25 0 0 1-3.245-3.066L3 17.75V6.25a3.25 3.25 0 0 1 3.066-3.245L6.25 3h11.5h-11.5zM4.5 14.5v3.25a1.75 1.75 0 0 0 1.606 1.744l.144.006h11.5a1.75 1.75 0 0 0 1.744-1.607l.006-.143V14.5h-3.825a3.752 3.752 0 0 1-3.475 2.995l-.2.005a3.752 3.752 0 0 1-3.632-2.812l-.043-.188H4.5v3.25v-3.25zm13.25-10H6.25a1.75 1.75 0 0 0-1.744 1.606L4.5 6.25V13H9a.75.75 0 0 1 .743.648l.007.102a2.25 2.25 0 0 0 4.495.154l.005-.154a.75.75 0 0 1 .648-.743L15 13h4.5V6.25a1.75 1.75 0 0 0-1.607-1.744L17.75 4.5z" fill="currentColor"></path></g></svg>`;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
ICON_REPORTS_OVERVIEW,
|
ICON_REPORTS_OVERVIEW,
|
||||||
ICON_TEAM_REPORTS,
|
ICON_TEAM_REPORTS,
|
||||||
ICON_USER_PROFILE,
|
ICON_USER_PROFILE,
|
||||||
|
ICON_CONVERSATION_REPORTS,
|
||||||
} from './CommandBarIcons';
|
} from './CommandBarIcons';
|
||||||
import { frontendURL } from '../../../helper/URLHelper';
|
import { frontendURL } from '../../../helper/URLHelper';
|
||||||
|
|
||||||
@ -41,6 +42,14 @@ const GO_TO_COMMANDS = [
|
|||||||
path: accountId => `accounts/${accountId}/reports/overview`,
|
path: accountId => `accounts/${accountId}/reports/overview`,
|
||||||
role: ['administrator'],
|
role: ['administrator'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'open_conversation_reports',
|
||||||
|
section: 'COMMAND_BAR.SECTIONS.REPORTS',
|
||||||
|
title: 'COMMAND_BAR.COMMANDS.GO_TO_CONVERSATION_REPORTS',
|
||||||
|
icon: ICON_CONVERSATION_REPORTS,
|
||||||
|
path: accountId => `accounts/${accountId}/reports/conversation`,
|
||||||
|
role: ['administrator'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'open_agent_reports',
|
id: 'open_agent_reports',
|
||||||
section: 'COMMAND_BAR.SECTIONS.REPORTS',
|
section: 'COMMAND_BAR.SECTIONS.REPORTS',
|
||||||
|
|||||||
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<div class="column content-box">
|
||||||
|
<div class="row">
|
||||||
|
<div class="column small-12 medium-8 conversation-metric">
|
||||||
|
<metric-card
|
||||||
|
:header="this.$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.HEADER')"
|
||||||
|
:is-loading="uiFlags.isFetchingAccountConversationMetric"
|
||||||
|
:loading-message="
|
||||||
|
$t('OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.LOADING_MESSAGE')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(metric, name, index) in conversationMetrics"
|
||||||
|
:key="index"
|
||||||
|
class="metric-content column"
|
||||||
|
>
|
||||||
|
<h3 class="heading">
|
||||||
|
{{ name }}
|
||||||
|
</h3>
|
||||||
|
<p class="metric">{{ metric }}</p>
|
||||||
|
</div>
|
||||||
|
</metric-card>
|
||||||
|
</div>
|
||||||
|
<div class="column small-12 medium-4">
|
||||||
|
<metric-card :header="this.$t('OVERVIEW_REPORTS.AGENT_STATUS.HEADER')">
|
||||||
|
<div
|
||||||
|
v-for="(metric, name, index) in agentStatusMetrics"
|
||||||
|
:key="index"
|
||||||
|
class="metric-content column"
|
||||||
|
>
|
||||||
|
<h3 class="heading">
|
||||||
|
{{ name }}
|
||||||
|
</h3>
|
||||||
|
<p class="metric">{{ metric }}</p>
|
||||||
|
</div>
|
||||||
|
</metric-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<metric-card
|
||||||
|
:header="this.$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.HEADER')"
|
||||||
|
>
|
||||||
|
<agent-table
|
||||||
|
:total-agents="agentsCount"
|
||||||
|
:agent-metrics="agentConversationMetric"
|
||||||
|
:page-index="pageIndex"
|
||||||
|
:is-loading="uiFlags.isFetchingAgentConversationMetric"
|
||||||
|
@page-change="onPageNumberChange"
|
||||||
|
/>
|
||||||
|
</metric-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import AgentTable from './components/overview/AgentTable';
|
||||||
|
import MetricCard from './components/overview/MetricCard';
|
||||||
|
import { OVERVIEW_METRICS } from './constants';
|
||||||
|
export default {
|
||||||
|
name: 'LiveReports',
|
||||||
|
components: {
|
||||||
|
AgentTable,
|
||||||
|
MetricCard,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
pageIndex: 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
agentStatus: 'agents/getAgentStatus',
|
||||||
|
agentsCount: 'agents/getAgentsCount',
|
||||||
|
accountConversationMetric: 'getAccountConversationMetric',
|
||||||
|
agentConversationMetric: 'getAgentConversationMetric',
|
||||||
|
uiFlags: 'getOverviewUIFlags',
|
||||||
|
}),
|
||||||
|
agentStatusMetrics() {
|
||||||
|
let metric = {};
|
||||||
|
Object.keys(this.agentStatus).forEach(key => {
|
||||||
|
const metricName = this.$t(
|
||||||
|
`OVERVIEW_REPORTS.AGENT_STATUS.${OVERVIEW_METRICS[key]}`
|
||||||
|
);
|
||||||
|
metric[metricName] = this.agentStatus[key];
|
||||||
|
});
|
||||||
|
return metric;
|
||||||
|
},
|
||||||
|
conversationMetrics() {
|
||||||
|
let metric = {};
|
||||||
|
Object.keys(this.accountConversationMetric).forEach(key => {
|
||||||
|
const metricName = this.$t(
|
||||||
|
`OVERVIEW_REPORTS.ACCOUNT_CONVERSATIONS.${OVERVIEW_METRICS[key]}`
|
||||||
|
);
|
||||||
|
metric[metricName] = this.accountConversationMetric[key];
|
||||||
|
});
|
||||||
|
return metric;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
|
this.fetchAllData();
|
||||||
|
|
||||||
|
bus.$on('fetch_overview_reports', () => {
|
||||||
|
this.fetchAllData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchAllData() {
|
||||||
|
this.fetchAccountConversationMetric();
|
||||||
|
this.fetchAgentConversationMetric();
|
||||||
|
},
|
||||||
|
fetchAccountConversationMetric() {
|
||||||
|
this.$store.dispatch('fetchAccountConversationMetric', {
|
||||||
|
type: 'account',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchAgentConversationMetric() {
|
||||||
|
this.$store.dispatch('fetchAgentConversationMetric', {
|
||||||
|
type: 'agent',
|
||||||
|
page: this.pageIndex,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPageNumberChange(pageIndex) {
|
||||||
|
this.pageIndex = pageIndex;
|
||||||
|
this.fetchAgentConversationMetric();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<div class="agent-table-container">
|
||||||
|
<ve-table
|
||||||
|
max-height="calc(100vh - 35rem)"
|
||||||
|
:fixed-header="true"
|
||||||
|
:columns="columns"
|
||||||
|
:table-data="tableData"
|
||||||
|
/>
|
||||||
|
<div v-if="isLoading" class="agents-loader">
|
||||||
|
<spinner />
|
||||||
|
<span>{{
|
||||||
|
$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.LOADING_MESSAGE')
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<empty-state
|
||||||
|
v-else-if="!isLoading && !agentMetrics.length"
|
||||||
|
:title="$t('OVERVIEW_REPORTS.AGENT_CONVERSATIONS.NO_AGENTS')"
|
||||||
|
/>
|
||||||
|
<div v-if="agentMetrics.length > 0" class="table-pagination">
|
||||||
|
<ve-pagination
|
||||||
|
:total="totalAgents"
|
||||||
|
:page-index="pageIndex"
|
||||||
|
:page-size="10"
|
||||||
|
:page-size-option="[10]"
|
||||||
|
@on-page-number-change="onPageNumberChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { VeTable, VePagination } from 'vue-easytable';
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AgentTable',
|
||||||
|
components: {
|
||||||
|
EmptyState,
|
||||||
|
Spinner,
|
||||||
|
VeTable,
|
||||||
|
VePagination,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
totalAgents: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
agentMetrics: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
pageIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tableData() {
|
||||||
|
return this.agentMetrics.map(agent => {
|
||||||
|
return {
|
||||||
|
agent: agent.name,
|
||||||
|
email: agent.email,
|
||||||
|
thumbnail: agent.thumbnail,
|
||||||
|
total: agent.metric.total || 0,
|
||||||
|
unattended: agent.metric.unattended || 0,
|
||||||
|
status: agent.availability,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
columns() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'agent',
|
||||||
|
key: 'agent',
|
||||||
|
title: this.$t(
|
||||||
|
'OVERVIEW_REPORTS.AGENT_CONVERSATIONS.TABLE_HEADER.AGENT'
|
||||||
|
),
|
||||||
|
fixed: 'left',
|
||||||
|
align: 'left',
|
||||||
|
width: 25,
|
||||||
|
renderBodyCell: ({ row }) => (
|
||||||
|
<div class="row-user-block">
|
||||||
|
<Thumbnail
|
||||||
|
src={row.thumbnail}
|
||||||
|
size="32px"
|
||||||
|
username={row.agent}
|
||||||
|
status={row.status}
|
||||||
|
/>
|
||||||
|
<div class="user-block">
|
||||||
|
<h6 class="title text-truncate">{row.agent}</h6>
|
||||||
|
<span class="sub-title">{row.email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'total',
|
||||||
|
key: 'total',
|
||||||
|
title: this.$t(
|
||||||
|
'OVERVIEW_REPORTS.AGENT_CONVERSATIONS.TABLE_HEADER.TOTAL'
|
||||||
|
),
|
||||||
|
align: 'left',
|
||||||
|
width: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'unattended',
|
||||||
|
key: 'unattended',
|
||||||
|
title: this.$t(
|
||||||
|
'OVERVIEW_REPORTS.AGENT_CONVERSATIONS.TABLE_HEADER.UNATTENDED'
|
||||||
|
),
|
||||||
|
align: 'left',
|
||||||
|
width: 10,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {},
|
||||||
|
methods: {
|
||||||
|
onPageNumberChange(pageIndex) {
|
||||||
|
this.$emit('page-change', pageIndex);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.agent-table-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.ve-table {
|
||||||
|
&::v-deep {
|
||||||
|
th.ve-table-header-th {
|
||||||
|
font-size: var(--font-size-mini) !important;
|
||||||
|
padding: var(--space-small) var(--space-two) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.ve-table-body-td {
|
||||||
|
padding: var(--space-one) var(--space-two) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::v-deep .ve-pagination {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::v-deep .ve-pagination-select {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-user-block {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.user-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
.title {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
margin: var(--zero);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-thumbnail-box {
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-pagination {
|
||||||
|
margin-top: var(--space-normal);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agents-loader {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-large);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>{{ header }}</h5>
|
||||||
|
<span class="live">
|
||||||
|
<span class="ellipse" /><span>{{ $t('OVERVIEW_REPORTS.LIVE') }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isLoading" class="card-body row">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isLoading" class="conversation-metric-loader">
|
||||||
|
<spinner />
|
||||||
|
<span>{{ loadingMessage }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MetricCard',
|
||||||
|
components: {
|
||||||
|
Spinner,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
header: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
isLoading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loadingMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
margin: var(--space-small) !important;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-medium);
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-bottom: var(--zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
.live {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: var(--space-small);
|
||||||
|
padding-left: var(--space-small);
|
||||||
|
margin: var(--space-smaller);
|
||||||
|
background: rgba(37, 211, 102, 0.1);
|
||||||
|
color: var(--g-400);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
|
||||||
|
.ellipse {
|
||||||
|
background-color: var(--g-400);
|
||||||
|
height: var(--space-smaller);
|
||||||
|
width: var(--space-smaller);
|
||||||
|
border-radius: var(--border-radius-rounded);
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card-body {
|
||||||
|
.metric-content {
|
||||||
|
padding-bottom: var(--space-small);
|
||||||
|
.heading {
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
}
|
||||||
|
.metric {
|
||||||
|
color: var(--w-800);
|
||||||
|
font-size: var(--font-size-bigger);
|
||||||
|
margin-bottom: var(--zero);
|
||||||
|
margin-top: var(--space-smaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-metric-loader {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-large);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -121,3 +121,12 @@ export const METRIC_CHART = {
|
|||||||
},
|
},
|
||||||
resolutions_count: DEFAULT_CHART,
|
resolutions_count: DEFAULT_CHART,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const OVERVIEW_METRICS = {
|
||||||
|
total: 'TOTAL',
|
||||||
|
unattended: 'UNATTENDED',
|
||||||
|
unassigned: 'UNASSIGNED',
|
||||||
|
online: 'ONLINE',
|
||||||
|
busy: 'BUSY',
|
||||||
|
offline: 'OFFLINE',
|
||||||
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import LabelReports from './LabelReports';
|
|||||||
import InboxReports from './InboxReports';
|
import InboxReports from './InboxReports';
|
||||||
import TeamReports from './TeamReports';
|
import TeamReports from './TeamReports';
|
||||||
import CsatResponses from './CsatResponses';
|
import CsatResponses from './CsatResponses';
|
||||||
|
import LiveReports from './LiveReports';
|
||||||
import SettingsContent from '../Wrapper';
|
import SettingsContent from '../Wrapper';
|
||||||
import { frontendURL } from '../../../../helper/URLHelper';
|
import { frontendURL } from '../../../../helper/URLHelper';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ export default {
|
|||||||
path: frontendURL('accounts/:accountId/reports'),
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
component: SettingsContent,
|
component: SettingsContent,
|
||||||
props: {
|
props: {
|
||||||
headerTitle: 'REPORT.HEADER',
|
headerTitle: 'OVERVIEW_REPORTS.HEADER',
|
||||||
icon: 'arrow-trending-lines',
|
icon: 'arrow-trending-lines',
|
||||||
keepAlive: false,
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
@ -24,7 +25,24 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'overview',
|
path: 'overview',
|
||||||
name: 'settings_account_reports',
|
name: 'account_overview_reports',
|
||||||
|
roles: ['administrator'],
|
||||||
|
component: LiveReports,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: frontendURL('accounts/:accountId/reports'),
|
||||||
|
component: SettingsContent,
|
||||||
|
props: {
|
||||||
|
headerTitle: 'REPORT.HEADER',
|
||||||
|
icon: 'chat',
|
||||||
|
keepAlive: false,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'conversation',
|
||||||
|
name: 'conversation_reports',
|
||||||
roles: ['administrator'],
|
roles: ['administrator'],
|
||||||
component: Index,
|
component: Index,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,22 @@ export const getters = {
|
|||||||
getUIFlags($state) {
|
getUIFlags($state) {
|
||||||
return $state.uiFlags;
|
return $state.uiFlags;
|
||||||
},
|
},
|
||||||
|
getAgentStatus($state) {
|
||||||
|
let status = {
|
||||||
|
online: $state.records.filter(
|
||||||
|
agent => agent.availability_status === 'online'
|
||||||
|
).length,
|
||||||
|
busy: $state.records.filter(agent => agent.availability_status === 'busy')
|
||||||
|
.length,
|
||||||
|
offline: $state.records.filter(
|
||||||
|
agent => agent.availability_status === 'offline'
|
||||||
|
).length,
|
||||||
|
};
|
||||||
|
return status;
|
||||||
|
},
|
||||||
|
getAgentsCount($state) {
|
||||||
|
return $state.records.length;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@ -58,9 +74,10 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePresence: async ({ commit }, data) => {
|
updatePresence: async ({ commit, dispatch }, data) => {
|
||||||
commit(types.default.SET_AGENT_UPDATING_STATUS, true);
|
commit(types.default.SET_AGENT_UPDATING_STATUS, true);
|
||||||
commit(types.default.UPDATE_AGENTS_PRESENCE, data);
|
commit(types.default.UPDATE_AGENTS_PRESENCE, data);
|
||||||
|
dispatch('updateReportAgentStatus', data, { root: true });
|
||||||
commit(types.default.SET_AGENT_UPDATING_STATUS, false);
|
commit(types.default.SET_AGENT_UPDATING_STATUS, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
/* eslint no-shadow: 0 */
|
/* eslint no-shadow: 0 */
|
||||||
import * as types from '../mutation-types';
|
import * as types from '../mutation-types';
|
||||||
import Report from '../../api/reports';
|
import Report from '../../api/reports';
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
import { downloadCsvFile } from '../../helper/downloadCsvFile';
|
import { downloadCsvFile } from '../../helper/downloadCsvFile';
|
||||||
|
|
||||||
@ -22,6 +23,14 @@ const state = {
|
|||||||
resolutions_count: 0,
|
resolutions_count: 0,
|
||||||
previous: {},
|
previous: {},
|
||||||
},
|
},
|
||||||
|
overview: {
|
||||||
|
uiFlags: {
|
||||||
|
isFetchingAccountConversationMetric: false,
|
||||||
|
isFetchingAgentConversationMetric: false,
|
||||||
|
},
|
||||||
|
accountConversationMetric: {},
|
||||||
|
agentConversationMetric: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
@ -31,6 +40,15 @@ const getters = {
|
|||||||
getAccountSummary(_state) {
|
getAccountSummary(_state) {
|
||||||
return _state.accountSummary;
|
return _state.accountSummary;
|
||||||
},
|
},
|
||||||
|
getAccountConversationMetric(_state) {
|
||||||
|
return _state.overview.accountConversationMetric;
|
||||||
|
},
|
||||||
|
getAgentConversationMetric(_state) {
|
||||||
|
return _state.overview.agentConversationMetric;
|
||||||
|
},
|
||||||
|
getOverviewUIFlags($state) {
|
||||||
|
return $state.overview.uiFlags;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
@ -70,6 +88,37 @@ export const actions = {
|
|||||||
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
fetchAccountConversationMetric({ commit }, reportObj) {
|
||||||
|
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, true);
|
||||||
|
Report.getConversationMetric(reportObj.type)
|
||||||
|
.then(accountConversationMetric => {
|
||||||
|
commit(
|
||||||
|
types.default.SET_ACCOUNT_CONVERSATION_METRIC,
|
||||||
|
accountConversationMetric.data
|
||||||
|
);
|
||||||
|
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
commit(types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchAgentConversationMetric({ commit }, reportObj) {
|
||||||
|
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, true);
|
||||||
|
Report.getConversationMetric(reportObj.type, reportObj.page)
|
||||||
|
.then(agentConversationMetric => {
|
||||||
|
commit(
|
||||||
|
types.default.SET_AGENT_CONVERSATION_METRIC,
|
||||||
|
agentConversationMetric.data
|
||||||
|
);
|
||||||
|
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
commit(types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateReportAgentStatus({ commit }, data) {
|
||||||
|
commit(types.default.UPDATE_REPORT_AGENTS_STATUS, data);
|
||||||
|
},
|
||||||
downloadAgentReports(_, reportObj) {
|
downloadAgentReports(_, reportObj) {
|
||||||
return Report.getAgentReports(reportObj.from, reportObj.to)
|
return Report.getAgentReports(reportObj.from, reportObj.to)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -118,6 +167,28 @@ const mutations = {
|
|||||||
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
|
[types.default.SET_ACCOUNT_SUMMARY](_state, summaryData) {
|
||||||
_state.accountSummary = summaryData;
|
_state.accountSummary = summaryData;
|
||||||
},
|
},
|
||||||
|
[types.default.SET_ACCOUNT_CONVERSATION_METRIC](_state, metricData) {
|
||||||
|
_state.overview.accountConversationMetric = metricData;
|
||||||
|
},
|
||||||
|
[types.default.TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING](_state, flag) {
|
||||||
|
_state.overview.uiFlags.isFetchingAccountConversationMetric = flag;
|
||||||
|
},
|
||||||
|
[types.default.SET_AGENT_CONVERSATION_METRIC](_state, metricData) {
|
||||||
|
_state.overview.agentConversationMetric = metricData;
|
||||||
|
},
|
||||||
|
[types.default.TOGGLE_AGENT_CONVERSATION_METRIC_LOADING](_state, flag) {
|
||||||
|
_state.overview.uiFlags.isFetchingAgentConversationMetric = flag;
|
||||||
|
},
|
||||||
|
[types.default.UPDATE_REPORT_AGENTS_STATUS](_state, data) {
|
||||||
|
_state.overview.agentConversationMetric.forEach((element, index) => {
|
||||||
|
const availabilityStatus = data[element.id];
|
||||||
|
Vue.set(
|
||||||
|
_state.overview.agentConversationMetric[index],
|
||||||
|
'availability',
|
||||||
|
availabilityStatus || 'offline'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import * as types from '../../../mutation-types';
|
|||||||
import agentList from './fixtures';
|
import agentList from './fixtures';
|
||||||
|
|
||||||
const commit = jest.fn();
|
const commit = jest.fn();
|
||||||
|
const dispatch = jest.fn();
|
||||||
global.axios = axios;
|
global.axios = axios;
|
||||||
jest.mock('axios');
|
jest.mock('axios');
|
||||||
|
|
||||||
@ -97,7 +98,7 @@ describe('#actions', () => {
|
|||||||
describe('#updatePresence', () => {
|
describe('#updatePresence', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
const data = { users: { 1: 'online' }, contacts: { 2: 'online' } };
|
const data = { users: { 1: 'online' }, contacts: { 2: 'online' } };
|
||||||
actions.updatePresence({ commit }, data);
|
actions.updatePresence({ commit, dispatch }, data);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_AGENT_UPDATING_STATUS, true],
|
[types.default.SET_AGENT_UPDATING_STATUS, true],
|
||||||
[types.default.UPDATE_AGENTS_PRESENCE, data],
|
[types.default.UPDATE_AGENTS_PRESENCE, data],
|
||||||
|
|||||||
@ -77,4 +77,71 @@ describe('#getters', () => {
|
|||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getAgentStatus', () => {
|
||||||
|
const state = {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Agent 1',
|
||||||
|
email: 'agent1@chatwoot.com',
|
||||||
|
confirmed: true,
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Agent 2',
|
||||||
|
email: 'agent2@chatwoot.com',
|
||||||
|
confirmed: false,
|
||||||
|
availability_status: 'offline',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(getters.getAgentStatus(state)).toEqual({
|
||||||
|
online: 1,
|
||||||
|
busy: 0,
|
||||||
|
offline: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAgentStatus', () => {
|
||||||
|
const state = {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Agent 1',
|
||||||
|
email: 'agent1@chatwoot.com',
|
||||||
|
confirmed: true,
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Agent 2',
|
||||||
|
email: 'agent2@chatwoot.com',
|
||||||
|
confirmed: false,
|
||||||
|
availability_status: 'offline',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(getters.getAgentStatus(state)).toEqual({
|
||||||
|
online: 1,
|
||||||
|
busy: 0,
|
||||||
|
offline: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getAgentStatus', () => {
|
||||||
|
const state = {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Agent 1',
|
||||||
|
email: 'agent1@chatwoot.com',
|
||||||
|
confirmed: true,
|
||||||
|
availability_status: 'online',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(getters.getAgentsCount(state)).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -92,6 +92,7 @@ describe('#mutations', () => {
|
|||||||
id: 2,
|
id: 2,
|
||||||
name: 'Agent1',
|
name: 'Agent1',
|
||||||
email: 'agent1@chatwoot.com',
|
email: 'agent1@chatwoot.com',
|
||||||
|
availability_status: 'offline',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -144,6 +144,13 @@ export default {
|
|||||||
SET_ACCOUNT_REPORTS: 'SET_ACCOUNT_REPORTS',
|
SET_ACCOUNT_REPORTS: 'SET_ACCOUNT_REPORTS',
|
||||||
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
SET_ACCOUNT_SUMMARY: 'SET_ACCOUNT_SUMMARY',
|
||||||
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
TOGGLE_ACCOUNT_REPORT_LOADING: 'TOGGLE_ACCOUNT_REPORT_LOADING',
|
||||||
|
SET_ACCOUNT_CONVERSATION_METRIC: 'SET_ACCOUNT_CONVERSATION_METRIC',
|
||||||
|
TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING:
|
||||||
|
'TOGGLE_ACCOUNT_CONVERSATION_METRIC_LOADING',
|
||||||
|
SET_AGENT_CONVERSATION_METRIC: 'SET_AGENT_CONVERSATION_METRIC',
|
||||||
|
TOGGLE_AGENT_CONVERSATION_METRIC_LOADING:
|
||||||
|
'TOGGLE_AGENT_CONVERSATION_METRIC_LOADING',
|
||||||
|
UPDATE_REPORT_AGENTS_STATUS: 'UPDATE_AGENTS_STATUS',
|
||||||
|
|
||||||
// Conversation Metadata
|
// Conversation Metadata
|
||||||
SET_CONVERSATION_METADATA: 'SET_CONVERSATION_METADATA',
|
SET_CONVERSATION_METADATA: 'SET_CONVERSATION_METADATA',
|
||||||
|
|||||||
@ -37,11 +37,11 @@ export const updateAttributes = (state, data) => {
|
|||||||
export const updatePresence = (state, data) => {
|
export const updatePresence = (state, data) => {
|
||||||
state.records.forEach((element, index) => {
|
state.records.forEach((element, index) => {
|
||||||
const availabilityStatus = data[element.id];
|
const availabilityStatus = data[element.id];
|
||||||
if (availabilityStatus) {
|
Vue.set(
|
||||||
Vue.set(state.records[index], 'availability_status', availabilityStatus);
|
state.records[index],
|
||||||
} else {
|
'availability_status',
|
||||||
Vue.delete(state.records[index], 'availability_status');
|
availabilityStatus || 'offline'
|
||||||
}
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -47,11 +47,13 @@ describe('#mutations', () => {
|
|||||||
id: 3,
|
id: 3,
|
||||||
name: 'Pranav',
|
name: 'Pranav',
|
||||||
avatar_url: '',
|
avatar_url: '',
|
||||||
|
availability_status: 'offline',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: 'Nithin',
|
name: 'Nithin',
|
||||||
avatar_url: '',
|
avatar_url: '',
|
||||||
|
availability_status: 'offline',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,6 +23,14 @@ class ActionCableListener < BaseListener
|
|||||||
broadcast(account, tokens, MESSAGE_UPDATED, message.push_event_data)
|
broadcast(account, tokens, MESSAGE_UPDATED, message.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def first_reply_created(event)
|
||||||
|
message, account = extract_message_and_account(event)
|
||||||
|
conversation = message.conversation
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members)
|
||||||
|
|
||||||
|
broadcast(account, tokens, FIRST_REPLY_CREATED, message.push_event_data)
|
||||||
|
end
|
||||||
|
|
||||||
def conversation_created(event)
|
def conversation_created(event)
|
||||||
conversation, account = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
|
tokens = user_tokens(account, conversation.inbox.members) + contact_inbox_tokens(conversation.contact_inbox)
|
||||||
|
|||||||
@ -73,7 +73,7 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
|
|
||||||
expect(json_response['open']).to eq(11)
|
expect(json_response['total']).to eq(11)
|
||||||
expect(json_response['unattended']).to eq(11)
|
expect(json_response['unattended']).to eq(11)
|
||||||
expect(json_response['unassigned']).to eq(1)
|
expect(json_response['unassigned']).to eq(1)
|
||||||
end
|
end
|
||||||
@ -93,10 +93,10 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
|
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
expect(json_response.blank?).to be false
|
expect(json_response.blank?).to be false
|
||||||
user_metrics = json_response.find { |item| item['user']['name'] == admin[:name] }
|
user_metrics = json_response.find { |item| item['name'] == admin[:name] }
|
||||||
expect(user_metrics.present?).to be true
|
expect(user_metrics.present?).to be true
|
||||||
|
|
||||||
expect(user_metrics['metric']['open']).to eq(2)
|
expect(user_metrics['metric']['total']).to eq(2)
|
||||||
expect(user_metrics['metric']['unattended']).to eq(2)
|
expect(user_metrics['metric']['unattended']).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ RSpec.describe 'Reports API', type: :request do
|
|||||||
|
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
expect(json_response.blank?).to be false
|
expect(json_response.blank?).to be false
|
||||||
expect(json_response[0]['metric']['open']).to eq(10)
|
expect(json_response[0]['metric']['total']).to eq(10)
|
||||||
expect(json_response[0]['metric']['unattended']).to eq(10)
|
expect(json_response[0]['metric']['unattended']).to eq(10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
type: object
|
|
||||||
properties:
|
|
||||||
user:
|
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: number
|
type: number
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
thumbnail:
|
thumbnail:
|
||||||
type: string
|
type: string
|
||||||
|
availability:
|
||||||
|
type: string
|
||||||
metric:
|
metric:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@ -5627,9 +5627,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"agent_conversation_metrics": {
|
"agent_conversation_metrics": {
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"user": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -5638,10 +5635,14 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"thumbnail": {
|
"thumbnail": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
},
|
||||||
}
|
"availability": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user