diff --git a/.changeset/fresh-deers-march.md b/.changeset/fresh-deers-march.md new file mode 100644 index 0000000000000..2e0ec5d1bb39f --- /dev/null +++ b/.changeset/fresh-deers-march.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Adds deprecation warning to `livechat:getAnalyticsChartData`, as well as it adds a new endpoint to replace it; `livechat/analytics/dashboards/charts-data` diff --git a/apps/meteor/app/livechat/imports/server/rest/dashboards.ts b/apps/meteor/app/livechat/imports/server/rest/dashboards.ts index 7f04c668f6648..1e4b6850b45b8 100644 --- a/apps/meteor/app/livechat/imports/server/rest/dashboards.ts +++ b/apps/meteor/app/livechat/imports/server/rest/dashboards.ts @@ -1,7 +1,16 @@ +import { OmnichannelAnalytics } from '@rocket.chat/core-services'; import { Users } from '@rocket.chat/models'; -import { isGETDashboardTotalizerParams, isGETDashboardsAgentStatusParams } from '@rocket.chat/rest-typings'; +import { + isGETDashboardTotalizerParams, + isGETDashboardsAgentStatusParams, + isGETLivechatAnalyticsDashboardsChartDataParams, + validateUnauthorizedErrorResponse, + validateForbiddenErrorResponse, + GETLivechatAnalyticsDashboardsChartDataSuccessSchema, +} from '@rocket.chat/rest-typings'; import { API } from '../../../../api/server'; +import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass'; import { getProductivityMetricsAsyncCached, getConversationsMetricsAsyncCached, @@ -238,3 +247,50 @@ API.v1.addRoute( }, }, ); + +const livechatAnalyticsEndpoints = API.v1.get( + 'livechat/analytics/dashboards/charts-data', + { + response: { + 200: GETLivechatAnalyticsDashboardsChartDataSuccessSchema, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + }, + authRequired: true, + permissionsRequired: ['view-livechat-manager'], + query: isGETLivechatAnalyticsDashboardsChartDataParams, + }, + async function action() { + const { chartName, start, end, departmentId } = this.queryParams; + + const chartData = await OmnichannelAnalytics.getAnalyticsChartData({ + daterange: { + from: start, + to: end, + }, + chartOptions: { + name: chartName, + }, + utcOffset: this.user.utcOffset, + executedBy: this.user._id, + departmentId, + }); + + if (!chartData) { + return API.v1.success({ + chartLabel: chartName, + dataLabels: [], + dataPoints: [], + }); + } + + return API.v1.success(chartData); + }, +); + +type LivechatAnalyticsEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends LivechatAnalyticsEndpoints {} +} diff --git a/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts b/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts index aed8799943ef7..92469a614ddb4 100644 --- a/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts +++ b/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts @@ -5,6 +5,7 @@ import { Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -15,6 +16,7 @@ declare module '@rocket.chat/ddp-client' { Meteor.methods({ async 'livechat:getAnalyticsChartData'(options) { + methodDeprecationLogger.method('livechat:getAnalyticsChartData', '8.0.0', '/v1/livechat/analytics/dashboards/charts-data'); const userId = Meteor.userId(); if (!userId || !(await hasPermissionAsync(userId, 'view-livechat-manager'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { diff --git a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx index 8daa8481aa8a9..de6b3fe3f400a 100644 --- a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx +++ b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx @@ -1,7 +1,7 @@ import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useMethod } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useEndpoint } from '@rocket.chat/ui-contexts'; import type * as chartjs from 'chart.js'; -import { useRef, useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { drawLineChart } from '../../../../app/livechat/client/lib/chartHandler'; @@ -55,7 +55,7 @@ const InterchangeableChart = ({ const { start, end } = dateRange; - const loadData = useMethod('livechat:getAnalyticsChartData'); + const loadData = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts-data'); const draw = useEffectEvent( async (params: { @@ -66,15 +66,23 @@ const InterchangeableChart = ({ chartOptions: { name: string; }; + departmentId?: string; }) => { try { const tooltipCallbacks = getChartTooltips(chartName); if (!params?.daterange?.from || !params?.daterange?.to) { return; } - const result = await loadData(params); - if (!result?.chartLabel || !result?.dataLabels || !result?.dataPoints) { - throw new Error('Error! fetching chart data. Details: livechat:getAnalyticsChartData => Missing Data'); + + const result = await loadData({ + chartName, + start, + end, + ...(departmentId && { departmentId }), + }); + + if (!result?.dataLabels || !result?.dataPoints) { + throw new Error('Error! fetching chart data.'); } (context.current || typeof context.current === 'undefined') && canvas.current && diff --git a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts index 48c8516af5add..377f914715b9a 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/04-dashboards.ts @@ -714,6 +714,28 @@ describe('LIVECHAT - dashboards', function () { }); }); + describe('livechat/analytics/dashboards/charts-data', () => { + it('should return the correct data structure for the charts', async () => { + const yesterday = moment().subtract(1, 'days').format('YYYY-MM-DD'); + const today = moment().startOf('day').format('YYYY-MM-DD'); + + const result = await request + .get(api('livechat/analytics/dashboards/charts-data')) + .query({ chartName: 'Total_conversations', start: yesterday, end: today }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(result.body).to.have.property('success', true); + + expect(result.body).to.have.property('chartLabel', 'Total_conversations'); + expect(result.body).to.have.property('dataLabels').to.be.an('array'); + expect(result.body).to.have.property('dataPoints').to.be.an('array'); + }); + + // it('should return the correct data structure but empty for unavailable data'); + }); + describe('livechat/analytics/agent-overview', () => { it('should return an "unauthorized error" when the user does not have the necessary permission', async () => { await removePermissionFromAllRoles('view-livechat-manager'); diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 08b687c157df2..692085c7098db 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -3438,6 +3438,69 @@ export const isGETDashboardsAgentStatusParams = ajv.compile( + GETLivechatAnalyticsDashboardsChartDataParamsSchema, +); + +const GETLivechatAnalyticsDashboardsChartDataSuccess = { + type: 'object', + properties: { + chartLabel: { + type: 'string', + }, + dataLabels: { + type: 'array', + items: { + type: 'string', + }, + }, + dataPoints: { + type: 'array', + items: { + type: 'number', + }, + }, + success: { + type: 'boolean', + enum: [true], + }, + }, + additionalProperties: false, +}; + +export const GETLivechatAnalyticsDashboardsChartDataSuccessSchema = ajv.compile<{ + chartLabel: string; + dataLabels: string[]; + dataPoints: number[]; +}>(GETLivechatAnalyticsDashboardsChartDataSuccess); + type PUTLivechatPriority = { name: string } | { reset: boolean }; const PUTLivechatPrioritySchema = { oneOf: [