chore: adds a deprecation warning for `livechat:saveBusinessHour` (#37690)

chore/fake-provider-subscription
Lucas Pelegrino 1 week ago committed by GitHub
parent ddf671785a
commit 872da49986
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/brown-llamas-worry.md
  2. 39
      apps/meteor/app/livechat/imports/server/rest/businessHours.ts
  3. 2
      apps/meteor/app/livechat/server/methods/saveBusinessHour.ts
  4. 6
      apps/meteor/client/views/omnichannel/businessHours/EditBusinessHours.tsx
  5. 122
      apps/meteor/tests/data/livechat/businessHours.ts
  6. 4
      apps/meteor/tests/e2e/omnichannel/omnichannel-business-hours.spec.ts
  7. 57
      apps/meteor/tests/e2e/utils/omnichannel/businessHours.ts
  8. 99
      apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts
  9. 126
      packages/rest-typings/src/v1/omnichannel.ts

@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/rest-typings": patch
---
Adds a deprecation warning for `livechat:saveBusinessHour` and new endpoint replacing it; `livechat/business-hours.save`

@ -1,7 +1,16 @@
import { isGETBusinessHourParams } from '@rocket.chat/rest-typings';
import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';
import {
isGETBusinessHourParams,
isPOSTLivechatBusinessHoursSaveParams,
POSTLivechatBusinessHoursSaveSuccessResponse,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { API } from '../../../../api/server';
import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass';
import { findLivechatBusinessHour } from '../../../server/api/lib/businessHours';
import { businessHourManager } from '../../../server/business-hour';
API.v1.addRoute(
'livechat/business-hour',
@ -16,3 +25,31 @@ API.v1.addRoute(
},
},
);
const livechatBusinessHoursEndpoints = API.v1.post(
'livechat/business-hours.save',
{
response: {
200: POSTLivechatBusinessHoursSaveSuccessResponse,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
authRequired: true,
body: isPOSTLivechatBusinessHoursSaveParams,
},
async function action() {
const params = this.bodyParams;
// TODO: Remove typecasting after refactoring saveBusinessHour logic with proper type logic. See: CORE-1552
const result = await businessHourManager.saveBusinessHour(params as unknown as ILivechatBusinessHour);
return API.v1.success(result);
}
);
type LivechatBusinessHoursEndpoints = ExtractRoutesFromAPI<typeof livechatBusinessHoursEndpoints>;
declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends LivechatBusinessHoursEndpoints {}
}

@ -2,6 +2,7 @@ import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Meteor } from 'meteor/meteor';
import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { businessHourManager } from '../business-hour';
declare module '@rocket.chat/ddp-client' {
@ -13,6 +14,7 @@ declare module '@rocket.chat/ddp-client' {
Meteor.methods<ServerMethods>({
async 'livechat:saveBusinessHour'(businessHourData) {
methodDeprecationLogger.method('livechat:saveBusinessHour', '8.0.0', '/v1/livechat/business-hours.save');
try {
await businessHourManager.saveBusinessHour(businessHourData);
} catch (e) {

@ -1,7 +1,7 @@
import type { ILivechatBusinessHour, LivechatBusinessHourTypes, Serialized } from '@rocket.chat/core-typings';
import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useMethod, useTranslation, useRouter } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useTranslation, useRouter, useEndpoint } from '@rocket.chat/ui-contexts';
import { useId } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@ -39,7 +39,7 @@ const EditBusinessHours = ({ businessHourData, type }: EditBusinessHoursProps) =
const dispatchToastMessage = useToastMessageDispatch();
const isSingleBH = useIsSingleBusinessHours();
const saveBusinessHour = useMethod('livechat:saveBusinessHour');
const saveBusinessHour = useEndpoint('POST', '/v1/livechat/business-hours.save');
const handleRemove = useRemoveBusinessHour();
const router = useRouter();
@ -69,7 +69,7 @@ const EditBusinessHours = ({ businessHourData, type }: EditBusinessHoursProps) =
})),
};
await saveBusinessHour(payload as any);
await saveBusinessHour(payload);
dispatchToastMessage({ type: 'success', message: t('Business_hours_updated') });
router.navigate('/omnichannel/businessHours');
} catch (error) {

@ -1,5 +1,6 @@
import type { ILivechatBusinessHour } from '@rocket.chat/core-typings';
import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings';
import type { POSTLivechatBusinessHoursSaveParams } from '@rocket.chat/rest-typings';
import moment from 'moment';
import { api, credentials, methodCall, request } from '../api-data';
@ -9,26 +10,30 @@ type ISaveBhApiWorkHour = Omit<ILivechatBusinessHour, '_id' | 'ts' | 'timezone'>
workHours: { day: string; start: string; finish: string; open: boolean }[];
} & { departmentsToApplyBusinessHour?: string } & { timezoneName: string };
// TODO: Migrate to an API call and return the business hour updated/created
export const saveBusinessHour = async (businessHour: ISaveBhApiWorkHour) => {
const { body } = await request
.post(methodCall('livechat:saveBusinessHour'))
.set(credentials)
.send({ message: JSON.stringify({ params: [businessHour], msg: 'method', method: 'livechat:saveBusinessHour', id: '101' }) })
.expect(200);
export const saveBusinessHour = async (businessHour: POSTLivechatBusinessHoursSaveParams) => {
const { body } = await request.post(api('livechat/business-hours.save')).set(credentials).send(businessHour);
return JSON.parse(body.message);
return body;
};
export const createCustomBusinessHour = async (departments: string[], open = true): Promise<ILivechatBusinessHour> => {
const name = `business-hour-${Date.now()}`;
const businessHour: ISaveBhApiWorkHour = {
const businessHour: POSTLivechatBusinessHoursSaveParams = {
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: getWorkHours(open),
timezoneName: 'Asia/Calcutta',
timezone: 'Asia/Calcutta',
departmentsToApplyBusinessHour: '',
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
daysTime: [
{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
{ day: 'Tuesday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
{ day: 'Wednesday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
{ day: 'Thursday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
{ day: 'Friday', start: { time: '08:00' }, finish: { time: '18:00' }, open },
],
};
if (departments.length) {
@ -56,31 +61,35 @@ export const makeDefaultBusinessHourActiveAndClosed = async () => {
body: { businessHour },
} = await request.get(api('livechat/business-hour')).query({ type: 'default' }).set(credentials).send();
// TODO: Refactor this to use openOrCloseBusinessHour() instead
const workHours = businessHour.workHours as { start: string; finish: string; day: string; open: boolean }[];
const allEnabledWorkHours = workHours.map((workHour) => {
workHour.open = true;
workHour.start = '00:00';
workHour.finish = '00:01'; // if a job runs between 00:00 and 00:01, then this test will fail :P
return workHour;
});
const { workHours } = businessHour;
// Remove properties not accepted by the endpoint schema
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
const enabledBusinessHour = {
...businessHour,
workHours: allEnabledWorkHours,
...cleanedBusinessHour,
timezoneName: 'America/Sao_Paulo',
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
daysTime: workHours.map((workHour: { open: boolean; start: { time: string }; finish: { time: string }; day: string }) => {
return {
open: true,
start: { time: '00:00' },
finish: { time: '00:01' },
day: workHour.day,
};
}),
workHours: workHours.map((workHour: { open: boolean; start: string; finish: string; day: string; code?: number }) => {
workHour.open = true;
workHour.start = '00:00';
workHour.finish = '00:01'; // if a job runs between 00:00 and 00:01, then this test will fail :P
delete workHour.code;
return workHour;
}),
};
await request
.post(methodCall('livechat:saveBusinessHour'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:saveBusinessHour',
params: [enabledBusinessHour],
id: 'id',
msg: 'method',
}),
});
return request.post(api('livechat/business-hours.save')).set(credentials).send(enabledBusinessHour).expect(200);
};
export const disableDefaultBusinessHour = async () => {
@ -93,31 +102,33 @@ export const disableDefaultBusinessHour = async () => {
body: { businessHour },
} = await request.get(api('livechat/business-hour')).query({ type: 'default' }).set(credentials).send();
// TODO: Refactor this to use openOrCloseBusinessHour() instead
const workHours = businessHour.workHours as { start: string; finish: string; day: string; open: boolean }[];
const allDisabledWorkHours = workHours.map((workHour) => {
workHour.open = false;
workHour.start = '00:00';
workHour.finish = '23:59';
return workHour;
});
const { workHours } = businessHour;
// Remove properties not accepted by the endpoint schema
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
const disabledBusinessHour = {
...businessHour,
workHours: allDisabledWorkHours,
...cleanedBusinessHour,
timezoneName: 'America/Sao_Paulo',
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
daysTime: workHours.map((workHour: { open: boolean; start: { time: string }; finish: { time: string }; day: string }) => {
return {
open: false,
start: { time: '00:00' },
finish: { time: '23:59' },
day: workHour.day,
};
}),
workHours: workHours.map((workHour: { open: boolean; start: string; finish: string; day: string }) => {
workHour.open = false;
workHour.start = '00:00';
workHour.finish = '23:59';
return workHour;
}),
};
await request
.post(methodCall('livechat:saveBusinessHour'))
.set(credentials)
.send({
message: JSON.stringify({
method: 'livechat:saveBusinessHour',
params: [disabledBusinessHour],
id: 'id',
msg: 'method',
}),
});
return request.post(api('livechat/business-hours.save')).set(credentials).send(disabledBusinessHour).expect(200);
};
const removeCustomBusinessHour = async (businessHourId: string) => {
@ -167,10 +178,15 @@ export const getCustomBusinessHourById = async (businessHourId: string): Promise
return response.body.businessHour;
};
// TODO: Refactor logic so object passed is of the correct type for POST /livechat/business-hours.save. See: CORE-1552
export const openOrCloseBusinessHour = async (businessHour: ILivechatBusinessHour, open: boolean) => {
const { _updatedAt, ts, ...cleanedBusinessHour } = businessHour;
const timezoneName = businessHour.timezone.name;
const enabledBusinessHour = {
...businessHour,
timezoneName: businessHour.timezone.name,
...cleanedBusinessHour,
timezoneName,
timezone: timezoneName,
workHours: getWorkHours().map((workHour) => {
return {
...workHour,

@ -1,4 +1,3 @@
import { faker } from '@faker-js/faker';
import type { Page } from '@playwright/test';
import { IS_EE } from '../config/constants';
@ -19,7 +18,6 @@ test.describe('OC - Business Hours', () => {
let department2: Awaited<ReturnType<typeof createDepartment>>;
let agent: Awaited<ReturnType<typeof createAgent>>;
const BHid = faker.string.uuid();
const BHName = 'TEST Business Hours';
test.beforeAll(async ({ api }) => {
@ -89,7 +87,6 @@ test.describe('OC - Business Hours', () => {
test('OC - Business hours - Edit BH departments', async ({ api, page }) => {
await test.step('expect to create new businessHours', async () => {
const createBH = await createBusinessHour(api, {
id: BHid,
name: BHName,
departments: [department.data._id],
});
@ -139,7 +136,6 @@ test.describe('OC - Business Hours', () => {
test('OC - Business hours - Toggle BH active status', async ({ api, page }) => {
await test.step('expect to create new businessHours', async () => {
const createBH = await createBusinessHour(api, {
id: BHid,
name: BHName,
departments: [department.data._id],
});

@ -7,41 +7,30 @@ type CreateBusinessHoursParams = {
departments?: { departmentId: string }[];
};
export const createBusinessHour = async (api: BaseTest['api'], { id = null, name, departments = [] }: CreateBusinessHoursParams = {}) => {
export const createBusinessHour = async (api: BaseTest['api'], { name, departments = [] }: CreateBusinessHoursParams = {}) => {
const departmentIds = departments.join(',');
const response = await api.post('/method.call/livechat:saveBusinessHour', {
message: JSON.stringify({
msg: 'method',
id: id || '33',
method: 'livechat:saveBusinessHour',
params: [
{
name,
timezoneName: 'America/Sao_Paulo',
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
daysTime: [
{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Tuesday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Wednesday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Thursday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Friday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
],
departmentsToApplyBusinessHour: departmentIds,
active: true,
type: 'custom',
timezone: 'America/Sao_Paulo',
workHours: [
{ day: 'Monday', start: '08:00', finish: '18:00', open: true },
{ day: 'Tuesday', start: '08:00', finish: '18:00', open: true },
{ day: 'Wednesday', start: '08:00', finish: '18:00', open: true },
{ day: 'Thursday', start: '08:00', finish: '18:00', open: true },
{ day: 'Friday', start: '08:00', finish: '18:00', open: true },
],
},
],
}),
return api.post('/livechat/business-hours.save', {
name,
timezoneName: 'America/Sao_Paulo',
daysOpen: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
daysTime: [
{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Tuesday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Wednesday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Thursday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
{ day: 'Friday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true },
],
departmentsToApplyBusinessHour: departmentIds,
active: true,
type: 'custom',
timezone: 'America/Sao_Paulo',
workHours: [
{ day: 'Monday', start: '08:00', finish: '18:00', open: true },
{ day: 'Tuesday', start: '08:00', finish: '18:00', open: true },
{ day: 'Wednesday', start: '08:00', finish: '18:00', open: true },
{ day: 'Thursday', start: '08:00', finish: '18:00', open: true },
{ day: 'Friday', start: '08:00', finish: '18:00', open: true },
],
});
return response;
};

@ -49,12 +49,11 @@ describe('LIVECHAT - business hours', () => {
let defaultBhId: any;
describe('[CE] livechat/business-hour', () => {
after(async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...defaultBhId,
timezone: {
name: 'America/Sao_Paulo',
utc: '-03:00',
},
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: getWorkHours(true),
});
});
@ -89,8 +88,11 @@ describe('LIVECHAT - business hours', () => {
defaultBhId = response.body.businessHour;
});
it('should not allow a user to be available if BH are closed', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...defaultBhId,
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: [
{
day: 'Monday',
@ -107,8 +109,11 @@ describe('LIVECHAT - business hours', () => {
expect(body.error).to.be.equal('error-business-hours-are-closed');
});
it('should allow a user to be available if BH are open', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...defaultBhId,
...cleanedBusinessHour,
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
workHours: getWorkHours(true),
});
@ -117,12 +122,10 @@ describe('LIVECHAT - business hours', () => {
expect(body).to.have.property('success', true);
});
it('should save a default business hour with proper timezone settings', async () => {
const { _updatedAt, ts, ...cleanedBusinessHour } = defaultBhId;
await saveBusinessHour({
...defaultBhId,
timezone: {
name: 'Asia/Kolkata',
utc: '+05:30',
},
...cleanedBusinessHour,
timezone: 'Asia/Kolkata',
workHours: getWorkHours(true),
timezoneName: 'Asia/Kolkata',
});
@ -183,21 +186,11 @@ describe('LIVECHAT - business hours', () => {
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [
{
day: 'Monday',
open: true,
// @ts-expect-error - this is valid for endpoint, actual type converts this into an object
start: '08:00',
// @ts-expect-error - same as previous one
finish: '18:00',
},
],
timezone: {
name: 'America/Sao_Paulo',
utc: '-03:00',
},
workHours: [{ day: 'Monday', start: '08:00', finish: '18:00', open: true }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '08:00' }, finish: { time: '18:00' }, open: true }],
departmentsToApplyBusinessHour: '',
timezone: 'America/Sao_Paulo',
timezoneName: 'America/Sao_Paulo',
});
@ -222,20 +215,10 @@ describe('LIVECHAT - business hours', () => {
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [
{
day: 'Monday',
open: true,
// @ts-expect-error - this is valid for endpoint, actual type converts this into an object
start: '08:00',
// @ts-expect-error - same as previous one
finish: '08:00',
},
],
timezone: {
name: 'America/Sao_Paulo',
utc: '-03:00',
},
workHours: [{ day: 'Monday', open: true, start: '08:00', finish: '08:00' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '08:00' }, finish: { time: '08:00' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});
@ -249,20 +232,10 @@ describe('LIVECHAT - business hours', () => {
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [
{
day: 'Monday',
open: true,
// @ts-expect-error - this is valid for endpoint, actual type converts this into an object
start: '10:00',
// @ts-expect-error - same as previous one
finish: '08:00',
},
],
timezone: {
name: 'America/Sao_Paulo',
utc: '-03:00',
},
workHours: [{ day: 'Monday', open: true, start: '10:00', finish: '08:00' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '10:00' }, finish: { time: '08:00' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});
@ -276,20 +249,10 @@ describe('LIVECHAT - business hours', () => {
name,
active: true,
type: LivechatBusinessHourTypes.CUSTOM,
workHours: [
{
day: 'Monday',
open: true,
// @ts-expect-error - this is valid for endpoint, actual type converts this into an object
start: '20000',
// @ts-expect-error - same as previous one
finish: 'xxxxx',
},
],
timezone: {
name: 'America/Sao_Paulo',
utc: '-03:00',
},
workHours: [{ day: 'Monday', open: true, start: '20000', finish: 'xxxxx' }],
daysOpen: ['Monday'],
daysTime: [{ day: 'Monday', start: { time: '20000' }, finish: { time: 'xxxxx' }, open: true }],
timezone: 'America/Sao_Paulo',
departmentsToApplyBusinessHour: '',
timezoneName: 'America/Sao_Paulo',
});

@ -3285,6 +3285,132 @@ const GETLivechatTriggersParamsSchema = {
additionalProperties: false,
};
// TODO: Remove these types after business hour endpoint refactor, see CORE-1552
type BusinessHourDaysTime = {
day: string;
start: { time: string };
finish: { time: string };
open: boolean;
};
type BusinessHourWorkHours = {
day: string;
start: string;
finish: string;
open: boolean;
};
export type POSTLivechatBusinessHoursSaveParams = {
name: string;
timezoneName: string;
daysOpen: string[];
daysTime?: BusinessHourDaysTime[];
departmentsToApplyBusinessHour: string;
active: boolean;
_id?: string;
type: string;
timezone?: string;
workHours: BusinessHourWorkHours[];
};
const POSTLivechatBusinessHoursSaveParamsSchema = {
type: 'object',
properties: {
name: {
type: 'string',
},
timezoneName: {
type: 'string',
},
daysOpen: {
type: 'array',
items: {
type: 'string',
},
},
daysTime: {
type: 'array',
items: {
type: 'object',
properties: {
day: { type: 'string' },
start: {
type: 'object',
properties: {
time: { type: 'string' },
},
required: ['time'],
additionalProperties: false,
},
finish: {
type: 'object',
properties: {
time: { type: 'string' },
},
required: ['time'],
additionalProperties: false,
},
open: { type: 'boolean' },
},
required: ['day', 'start', 'finish', 'open'],
additionalProperties: false,
},
minItems: 1,
},
departmentsToApplyBusinessHour: {
type: 'string',
},
active: {
type: 'boolean',
},
_id: {
type: 'string',
nullable: true,
},
type: {
type: 'string',
},
timezone: {
type: 'string',
},
workHours: {
type: 'array',
items: {
type: 'object',
properties: {
day: { type: 'string' },
start: { type: 'string' },
finish: { type: 'string' },
open: { type: 'boolean' },
},
required: ['day', 'start', 'finish', 'open'],
additionalProperties: false,
},
minItems: 1,
},
},
required: ['name', 'timezoneName', 'daysOpen', 'departmentsToApplyBusinessHour', 'active', 'type', 'workHours'],
additionalProperties: false,
};
export const isPOSTLivechatBusinessHoursSaveParams = ajv.compile<POSTLivechatBusinessHoursSaveParams>(
POSTLivechatBusinessHoursSaveParamsSchema,
);
export const POSTLivechatBusinessHoursSaveSuccessSchema = {
type: 'object',
properties: {
success: {
type: 'boolean',
enum: [true],
},
},
required: ['success'],
additionalProperties: false,
};
export const POSTLivechatBusinessHoursSaveSuccessResponse = ajv.compile<void>(POSTLivechatBusinessHoursSaveSuccessSchema);
export const isGETLivechatTriggersParams = ajv.compile<GETLivechatTriggersParams>(GETLivechatTriggersParamsSchema);
export type GETLivechatRoomsParams = PaginatedRequest<{

Loading…
Cancel
Save