-
Notifications
You must be signed in to change notification settings - Fork 10k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Martin Schoeler <20868078+MartinSchoeler@users.noreply.github.com> Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com> Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com>
- Loading branch information
1 parent
ba18325
commit ebab8c4
Showing
67 changed files
with
2,689 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
'@rocket.chat/core-typings': minor | ||
'@rocket.chat/rest-typings': minor | ||
'@rocket.chat/ui-client': minor | ||
'@rocket.chat/meteor': minor | ||
--- | ||
|
||
Added Reports Metrics Dashboard to Omnichannel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ import './units'; | |
import './business-hours'; | ||
import './rooms'; | ||
import './transcript'; | ||
import './reports'; |
62 changes: 62 additions & 0 deletions
62
apps/meteor/ee/app/livechat-enterprise/server/api/lib/dashboards.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import type { ReportResult, ReportWithUnmatchingElements, IOmnichannelRoom } from '@rocket.chat/core-typings'; | ||
import { LivechatRooms } from '@rocket.chat/models'; | ||
import mem from 'mem'; | ||
import type { Filter } from 'mongodb'; | ||
|
||
type AggParams = { start: Date; end: Date; sort: Record<string, 1 | -1>; extraQuery: Filter<IOmnichannelRoom> }; | ||
|
||
const defaultValue = { data: [], total: 0 }; | ||
export const findAllConversationsBySource = async ({ start, end, extraQuery }: Omit<AggParams, 'sort'>): Promise<ReportResult> => { | ||
return (await LivechatRooms.getConversationsBySource(start, end, extraQuery).toArray())[0] || defaultValue; | ||
}; | ||
|
||
export const findAllConversationsByStatus = async ({ start, end, extraQuery }: Omit<AggParams, 'sort'>): Promise<ReportResult> => { | ||
return (await LivechatRooms.getConversationsByStatus(start, end, extraQuery).toArray())[0] || defaultValue; | ||
}; | ||
|
||
export const findAllConversationsByDepartment = async ({ | ||
start, | ||
end, | ||
sort, | ||
extraQuery, | ||
}: AggParams): Promise<ReportWithUnmatchingElements> => { | ||
const [result, total] = await Promise.all([ | ||
LivechatRooms.getConversationsByDepartment(start, end, sort, extraQuery).toArray(), | ||
LivechatRooms.getTotalConversationsWithoutDepartmentBetweenDates(start, end, extraQuery), | ||
]); | ||
|
||
return { | ||
...(result?.[0] || defaultValue), | ||
unspecified: total || 0, | ||
}; | ||
}; | ||
|
||
export const findAllConversationsByTags = async ({ start, end, sort, extraQuery }: AggParams): Promise<ReportWithUnmatchingElements> => { | ||
const [result, total] = await Promise.all([ | ||
LivechatRooms.getConversationsByTags(start, end, sort, extraQuery).toArray(), | ||
LivechatRooms.getConversationsWithoutTagsBetweenDate(start, end, extraQuery), | ||
]); | ||
|
||
return { | ||
...(result?.[0] || defaultValue), | ||
unspecified: total || 0, | ||
}; | ||
}; | ||
|
||
export const findAllConversationsByAgents = async ({ start, end, sort, extraQuery }: AggParams): Promise<ReportWithUnmatchingElements> => { | ||
const [result, total] = await Promise.all([ | ||
LivechatRooms.getConversationsByAgents(start, end, sort, extraQuery).toArray(), | ||
LivechatRooms.getTotalConversationsWithoutAgentsBetweenDate(start, end, extraQuery), | ||
]); | ||
|
||
return { | ||
...(result?.[0] || defaultValue), | ||
unspecified: total || 0, | ||
}; | ||
}; | ||
|
||
export const findAllConversationsBySourceCached = mem(findAllConversationsBySource, { maxAge: 60000, cacheKey: JSON.stringify }); | ||
export const findAllConversationsByStatusCached = mem(findAllConversationsByStatus, { maxAge: 60000, cacheKey: JSON.stringify }); | ||
export const findAllConversationsByDepartmentCached = mem(findAllConversationsByDepartment, { maxAge: 60000, cacheKey: JSON.stringify }); | ||
export const findAllConversationsByTagsCached = mem(findAllConversationsByTags, { maxAge: 60000, cacheKey: JSON.stringify }); | ||
export const findAllConversationsByAgentsCached = mem(findAllConversationsByAgents, { maxAge: 60000, cacheKey: JSON.stringify }); |
130 changes: 130 additions & 0 deletions
130
apps/meteor/ee/app/livechat-enterprise/server/api/reports.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { isGETDashboardConversationsByType } from '@rocket.chat/rest-typings'; | ||
import type { Moment } from 'moment'; | ||
import moment from 'moment'; | ||
|
||
import { API } from '../../../../../app/api/server'; | ||
import { restrictQuery } from '../hooks/applyRoomRestrictions'; | ||
import { | ||
findAllConversationsBySourceCached, | ||
findAllConversationsByStatusCached, | ||
findAllConversationsByDepartmentCached, | ||
findAllConversationsByTagsCached, | ||
findAllConversationsByAgentsCached, | ||
} from './lib/dashboards'; | ||
|
||
const checkDates = (start: Moment, end: Moment) => { | ||
if (!start.isValid()) { | ||
throw new Error('The "start" query parameter must be a valid date.'); | ||
} | ||
if (!end.isValid()) { | ||
throw new Error('The "end" query parameter must be a valid date.'); | ||
} | ||
// Check dates are no more than 1 year apart using moment | ||
// 1.01 === "we allow to pass year by some hours/days" | ||
if (moment(end).startOf('day').diff(moment(start).startOf('day'), 'year', true) > 1.01) { | ||
throw new Error('The "start" and "end" query parameters must be less than 1 year apart.'); | ||
} | ||
|
||
if (start.isAfter(end)) { | ||
throw new Error('The "start" query parameter must be before the "end" query parameter.'); | ||
} | ||
}; | ||
|
||
API.v1.addRoute( | ||
'livechat/analytics/dashboards/conversations-by-source', | ||
{ authRequired: true, permissionsRequired: ['view-livechat-reports'], validateParams: isGETDashboardConversationsByType }, | ||
{ | ||
async get() { | ||
const { start, end } = this.queryParams; | ||
|
||
const startDate = moment(start); | ||
const endDate = moment(end); | ||
|
||
checkDates(startDate, endDate); | ||
|
||
const extraQuery = await restrictQuery(); | ||
const result = await findAllConversationsBySourceCached({ start: startDate.toDate(), end: endDate.toDate(), extraQuery }); | ||
|
||
return API.v1.success(result); | ||
}, | ||
}, | ||
); | ||
|
||
API.v1.addRoute( | ||
'livechat/analytics/dashboards/conversations-by-status', | ||
{ authRequired: true, permissionsRequired: ['view-livechat-reports'], validateParams: isGETDashboardConversationsByType }, | ||
{ | ||
async get() { | ||
const { start, end } = this.queryParams; | ||
|
||
const startDate = moment(start); | ||
const endDate = moment(end); | ||
|
||
checkDates(startDate, endDate); | ||
const extraQuery = await restrictQuery(); | ||
const result = await findAllConversationsByStatusCached({ start: startDate.toDate(), end: endDate.toDate(), extraQuery }); | ||
|
||
return API.v1.success(result); | ||
}, | ||
}, | ||
); | ||
|
||
API.v1.addRoute( | ||
'livechat/analytics/dashboards/conversations-by-department', | ||
{ authRequired: true, permissionsRequired: ['view-livechat-reports'], validateParams: isGETDashboardConversationsByType }, | ||
{ | ||
async get() { | ||
const { start, end } = this.queryParams; | ||
const { sort } = await this.parseJsonQuery(); | ||
|
||
const startDate = moment(start); | ||
const endDate = moment(end); | ||
|
||
checkDates(startDate, endDate); | ||
const extraQuery = await restrictQuery(); | ||
const result = await findAllConversationsByDepartmentCached({ start: startDate.toDate(), end: endDate.toDate(), sort, extraQuery }); | ||
|
||
return API.v1.success(result); | ||
}, | ||
}, | ||
); | ||
|
||
API.v1.addRoute( | ||
'livechat/analytics/dashboards/conversations-by-tags', | ||
{ authRequired: true, permissionsRequired: ['view-livechat-reports'], validateParams: isGETDashboardConversationsByType }, | ||
{ | ||
async get() { | ||
const { start, end } = this.queryParams; | ||
const { sort } = await this.parseJsonQuery(); | ||
|
||
const startDate = moment(start); | ||
const endDate = moment(end); | ||
|
||
checkDates(startDate, endDate); | ||
const extraQuery = await restrictQuery(); | ||
const result = await findAllConversationsByTagsCached({ start: startDate.toDate(), end: endDate.toDate(), sort, extraQuery }); | ||
|
||
return API.v1.success(result); | ||
}, | ||
}, | ||
); | ||
|
||
API.v1.addRoute( | ||
'livechat/analytics/dashboards/conversations-by-agent', | ||
{ authRequired: true, permissionsRequired: ['view-livechat-reports'], validateParams: isGETDashboardConversationsByType }, | ||
{ | ||
async get() { | ||
const { start, end } = this.queryParams; | ||
const { sort } = await this.parseJsonQuery(); | ||
|
||
const startDate = moment(start); | ||
const endDate = moment(end); | ||
|
||
checkDates(startDate, endDate); | ||
const extraQuery = await restrictQuery(); | ||
const result = await findAllConversationsByAgentsCached({ start: startDate.toDate(), end: endDate.toDate(), sort, extraQuery }); | ||
|
||
return API.v1.success(result); | ||
}, | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
26 changes: 26 additions & 0 deletions
26
apps/meteor/ee/client/components/dashboards/usePeriodSelectorStorage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; | ||
|
||
import type { Period } from './periods'; | ||
|
||
export const usePeriodSelectorStorage = <TPeriod extends Period['key']>( | ||
storageKey: string, | ||
periods: TPeriod[], | ||
): [ | ||
period: TPeriod, | ||
periodSelectorProps: { | ||
periods: TPeriod[]; | ||
value: TPeriod; | ||
onChange: (value: TPeriod) => void; | ||
}, | ||
] => { | ||
const [period, setPeriod] = useLocalStorage<TPeriod>(storageKey, periods[0]); | ||
|
||
return [ | ||
period, | ||
{ | ||
periods, | ||
value: period, | ||
onChange: (value): void => setPeriod(value), | ||
}, | ||
]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Box } from '@rocket.chat/fuselage'; | ||
import { useTranslation } from '@rocket.chat/ui-contexts'; | ||
import React from 'react'; | ||
|
||
import Page from '../../../../client/components/Page'; | ||
import { ResizeObserver } from './components/ResizeObserver'; | ||
import { AgentsSection, ChannelsSection, DepartmentsSection, StatusSection, TagsSection } from './sections'; | ||
|
||
const ReportsPage = () => { | ||
const t = useTranslation(); | ||
|
||
return ( | ||
<Page background='tint'> | ||
<Page.Header title={t('Reports')} /> | ||
<Box is='p' color='hint' fontScale='p2' mi={24}> | ||
{t('Omnichannel_Reports_Summary')} | ||
</Box> | ||
<Page.ScrollableContentWithShadow alignItems='center'> | ||
<ResizeObserver> | ||
<Box display='flex' flexWrap='wrap' width='100rem' maxWidth='100%' m={-8}> | ||
<StatusSection /> | ||
|
||
<ChannelsSection /> | ||
|
||
<DepartmentsSection /> | ||
|
||
<TagsSection /> | ||
|
||
<AgentsSection /> | ||
</Box> | ||
</ResizeObserver> | ||
</Page.ScrollableContentWithShadow> | ||
</Page> | ||
); | ||
}; | ||
|
||
export default ReportsPage; |
Oops, something went wrong.