Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/migrate-livechat-analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Migrated `livechat/analytics/agent-overview` and `livechat/analytics/overview` GET endpoints from `API.v1.addRoute` to chained `.get()` pattern with AJV response schema validation.
118 changes: 94 additions & 24 deletions apps/meteor/app/livechat/server/api/v1/statistics.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
import { Users } from '@rocket.chat/models';
import { isLivechatAnalyticsAgentOverviewProps, isLivechatAnalyticsOverviewProps } from '@rocket.chat/rest-typings';
import {
ajv,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
isLivechatAnalyticsAgentOverviewProps,
isLivechatAnalyticsOverviewProps,
} from '@rocket.chat/rest-typings';

import { API } from '../../../../api/server';
import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass';
import { settings } from '../../../../settings/server';
import { getAgentOverviewDataCached, getAnalyticsOverviewDataCached } from '../../lib/AnalyticsTyped';

API.v1.addRoute(
'livechat/analytics/agent-overview',
{
authRequired: true,
permissionsRequired: ['view-livechat-manager'],
validateParams: isLivechatAnalyticsAgentOverviewProps,
},
{
async get() {
const livechatAnalyticsEndpoints = API.v1
.get(
'livechat/analytics/agent-overview',
{
authRequired: true,
permissionsRequired: ['view-livechat-manager'],
query: isLivechatAnalyticsAgentOverviewProps,
response: {
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
200: ajv.compile<{
head: { name: string }[];
data: { name: string; value: number }[];
success: boolean;
}>({
type: 'object',
properties: {
head: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
},
required: ['name'],
additionalProperties: false,
},
},
data: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'number' },
},
required: ['name', 'value'],
additionalProperties: false,
},
},
success: { type: 'boolean', enum: [true] },
},
required: ['head', 'data', 'success'],
additionalProperties: false,
}),
},
},
async function action() {
const { name, departmentId, from, to } = this.queryParams;

if (!name) {
Expand All @@ -31,18 +77,36 @@ API.v1.addRoute(
}),
);
},
},
);

API.v1.addRoute(
'livechat/analytics/overview',
{
authRequired: true,
permissionsRequired: ['view-livechat-manager'],
validateParams: isLivechatAnalyticsOverviewProps,
},
{
async get() {
)
.get(
'livechat/analytics/overview',
{
authRequired: true,
permissionsRequired: ['view-livechat-manager'],
query: isLivechatAnalyticsOverviewProps,
response: {
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
200: ajv.compile<
{
title: string;
value: string | number;
}[] & { success: boolean }
>({
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
value: { anyOf: [{ type: 'string' }, { type: 'number' }] },
},
required: ['title', 'value'],
additionalProperties: false,
},
}),
},
},
async function action() {
const { name, departmentId, from, to } = this.queryParams;

if (!name) {
Expand All @@ -63,5 +127,11 @@ API.v1.addRoute(
}),
);
},
},
);
);

type LivechatAnalyticsEndpoints = ExtractRoutesFromAPI<typeof livechatAnalyticsEndpoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type
interface Endpoints extends LivechatAnalyticsEndpoints { }
}
32 changes: 10 additions & 22 deletions packages/rest-typings/src/v1/omnichannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2847,14 +2847,14 @@ type POSTLivechatRoomCloseByUserParams = {
generateTranscriptPdf?: boolean;
forceClose?: boolean;
transcriptEmail?:
| {
// Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled
sendToVisitor: false;
}
| {
sendToVisitor: true;
requestData: Pick<NonNullable<IOmnichannelRoom['transcriptRequest']>, 'email' | 'subject'>;
};
| {
// Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled
sendToVisitor: false;
}
| {
sendToVisitor: true;
requestData: Pick<NonNullable<IOmnichannelRoom['transcriptRequest']>, 'email' | 'subject'>;
};
};

const POSTLivechatRoomCloseByUserParamsSchema = {
Expand Down Expand Up @@ -4359,8 +4359,8 @@ export const isLivechatTriggerWebhookCallParams = ajv.compile<LivechatTriggerWeb

type POSTLivechatRoomsCloseAll =
| {
departmentIds?: string[];
}
departmentIds?: string[];
}
| undefined;

const POSTLivechatRoomsCloseAllSchema = {
Expand Down Expand Up @@ -5064,18 +5064,6 @@ export type OmnichannelEndpoints = {
'/v1/livechat/rooms/filters': {
GET: () => { filters: IOmnichannelRoom['source'][] };
};
'/v1/livechat/analytics/agent-overview': {
GET: (params: LivechatAnalyticsAgentOverviewProps) => {
head: { name: string }[];
data: { name: string; value: number }[];
};
};
'/v1/livechat/analytics/overview': {
GET: (params: LivechatAnalyticsOverviewProps) => {
title: string;
value: string | number;
}[];
};
'/v1/livechat/sms-incoming/:service': {
POST: (params: unknown) => SMSProviderResponse;
};
Expand Down