Merge remote-tracking branch 'origin/develop' into release-candidate

This commit is contained in:
Diego Sampaio 2023-03-07 21:11:57 -03:00
commit 44a9a912ba
No known key found for this signature in database
GPG Key ID: B71D302EB7F5183C
26 changed files with 135 additions and 163 deletions

View File

@ -50,8 +50,6 @@ on:
required: true
type: string
secrets:
TURBO_TEAM:
required: true
CR_USER:
required: true
CR_PAT:

View File

@ -223,7 +223,9 @@ jobs:
rc-dockerfile-alpine: ${{ needs.release-versions.outputs.rc-dockerfile-alpine }}
rc-docker-tag-alpine: ${{ needs.release-versions.outputs.rc-docker-tag-alpine }}
gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
secrets: inherit
secrets:
CR_USER: ${{ secrets.CR_USER }}
CR_PAT: ${{ secrets.CR_PAT }}
test-ui:
name: 🔨 Test UI (CE)
@ -244,7 +246,9 @@ jobs:
rc-dockerfile-alpine: ${{ needs.release-versions.outputs.rc-dockerfile-alpine }}
rc-docker-tag-alpine: ${{ needs.release-versions.outputs.rc-docker-tag-alpine }}
gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
secrets: inherit
secrets:
CR_USER: ${{ secrets.CR_USER }}
CR_PAT: ${{ secrets.CR_PAT }}
test-api-ee:
name: 🔨 Test API (EE)
@ -265,7 +269,9 @@ jobs:
rc-dockerfile-alpine: ${{ needs.release-versions.outputs.rc-dockerfile-alpine }}
rc-docker-tag-alpine: ${{ needs.release-versions.outputs.rc-docker-tag-alpine }}
gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
secrets: inherit
secrets:
CR_USER: ${{ secrets.CR_USER }}
CR_PAT: ${{ secrets.CR_PAT }}
test-ui-ee:
name: 🔨 Test UI (EE)
@ -287,7 +293,9 @@ jobs:
rc-dockerfile-alpine: ${{ needs.release-versions.outputs.rc-dockerfile-alpine }}
rc-docker-tag-alpine: ${{ needs.release-versions.outputs.rc-docker-tag-alpine }}
gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }}
secrets: inherit
secrets:
CR_USER: ${{ secrets.CR_USER }}
CR_PAT: ${{ secrets.CR_PAT }}
tests-done:
name: ✅ Tests Done

View File

@ -32,23 +32,22 @@ export async function findAdminRooms({
const showTypes = Array.isArray(types) ? types.filter((type) => !typesToRemove.includes(type)) : [];
const options = {
projection: adminFields,
sort: sort || { default: -1, name: 1 },
skip: offset,
limit: count,
};
let result;
if (name && showTypes.length) {
result = Rooms.findByNameContainingAndTypes(name, showTypes, discussion, includeTeams, showOnlyTeams, options);
result = Rooms.findByNameOrFnameContainingAndTypes(name, showTypes, discussion, includeTeams, showOnlyTeams, options);
} else if (showTypes.length) {
result = Rooms.findByTypes(showTypes, discussion, includeTeams, showOnlyTeams, options);
} else {
result = Rooms.findByNameContaining(name, discussion, includeTeams, showOnlyTeams, options);
result = Rooms.findByNameOrFnameContaining(name, discussion, includeTeams, showOnlyTeams, options);
}
const { cursor, totalCount } = result;
const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
const [rooms, total] = await Promise.all([cursor.sort(sort || { default: -1, name: 1 }).toArray(), totalCount]);
return {
rooms,

View File

@ -9,7 +9,7 @@ import { AsyncStatePhase } from '../hooks/useAsyncState';
import { useDepartmentsList } from './Omnichannel/hooks/useDepartmentsList';
type AutoCompleteDepartmentProps = {
value?: { value: string; label: string } | string;
value?: string;
onChange: (value: string) => void;
excludeDepartmentId?: string;
onlyMyDepartments?: boolean;
@ -48,15 +48,10 @@ const AutoCompleteDepartment = ({
const { phase: departmentsPhase, items: departmentsItems, itemCount: departmentsTotal } = useRecordList(departmentsList);
const department = useMemo(() => {
const valueFound = typeof value === 'string' ? value : value?.value || '';
return departmentsItems.find((dep) => dep.value === valueFound)?.value;
}, [departmentsItems, value]);
return (
<PaginatedSelectFiltered
withTitle
value={department}
value={value}
onChange={onChange}
filter={departmentsFilter}
setFilter={setDepartmentsFilter as (value?: string | number) => void}

View File

@ -48,6 +48,11 @@ const registerLazyComponentRoute = (
const handleExit = (context: Context): void => {
computation?.stop();
if (!context.oldRoute) {
return;
}
if (context.route.group?.name === context.oldRoute?.group?.name) {
return;
}

View File

@ -33,7 +33,7 @@ export default function NewZapier({ ...props }) {
return (
<>
<Callout bg='status-background-warning' icon={'warning'} title={t('Zapier_integration_has_been_deprecated')} mbs='x16' mbe='x4'>
<Callout type='warning' icon='warning' title={t('Zapier_integration_has_been_deprecated')} mbs='x16' mbe='x4'>
{t('Install_Zapier_from_marketplace')}
</Callout>
{!script && (

View File

@ -71,28 +71,6 @@ const getRoomType = (room: IRoom): typeof roomTypeI18nMap[keyof typeof roomTypeI
const getRoomDisplayName = (room: IRoom): string | undefined =>
room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room);
const useDisplayData = (asyncState: any, sort: [string, 'asc' | 'desc']): IRoom[] =>
useMemo(() => {
const { data = {}, isLoading } = asyncState;
if (isLoading) {
return null;
}
if (sort[0] === 'name' && data.rooms) {
return data.rooms.sort((a: IRoom, b: IRoom) => {
const aName = getRoomDisplayName(a) || '';
const bName = getRoomDisplayName(b) || '';
if (aName === bName) {
return 0;
}
const result = aName < bName ? -1 : 1;
return sort[1] === 'asc' ? result : result * -1;
});
}
return data;
}, [asyncState, sort]);
const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => {
const t = useTranslation();
const mediaQuery = useMediaQuery('(min-width: 1024px)');
@ -119,12 +97,9 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
const endpointData = useQuery(
['rooms', query, 'admin'],
async () => {
const { rooms } = await getAdminRooms({ filter: params.text, ...params });
const adminRooms = await getAdminRooms(query);
if (rooms.length === 0) {
throw new Error(t('No_results_found'));
}
return rooms;
return { ...adminRooms, rooms: adminRooms.rooms as IRoom[] };
},
{
onError: (error) => {
@ -133,7 +108,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
},
);
const { data, refetch } = endpointData;
const { data, refetch, isLoading } = endpointData;
useEffect(() => {
reload.current = refetch;
@ -163,7 +138,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
[sort, setSort],
);
const displayData = useDisplayData(endpointData, sort);
const displayData = !isLoading && data ? data.rooms : undefined;
const header = useMemo(
() =>
@ -266,7 +241,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React
header={header}
renderRow={renderRow}
results={displayData}
total={data?.length}
total={data?.total}
params={params}
setParams={setParams}
renderFilter={({ onChange, ...props }): ReactElement => <FilterByTypeAndText setFilter={onChange} {...props} />}

View File

@ -72,7 +72,7 @@ const AnalyticsPage = () => {
<DateRangePicker mi='x4' flexGrow={1} onChange={setDateRange} />
</Box>
<Box>
<Overview type={type} dateRange={dateRange} departmentId={department?.value} />
<Overview type={type} dateRange={dateRange} departmentId={department} />
</Box>
<Box display='flex' flexDirection='row'>
<Margins inline='x2'>
@ -90,12 +90,12 @@ const AnalyticsPage = () => {
w='66%'
h='100%'
chartName={chartName}
departmentId={department?.value}
departmentId={department}
dateRange={dateRange}
alignSelf='stretch'
/>
<Box display='flex' w='33%' flexDirection='row' justifyContent='stretch' p='x10' mis='x4'>
<AgentOverview type={chartName} dateRange={dateRange} departmentId={department?.value} />
<AgentOverview type={chartName} dateRange={dateRange} departmentId={department} />
</Box>
</Box>
</Margins>

View File

@ -35,7 +35,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, customFields, setCu
const [guest, setGuest] = useLocalStorage('guest', '');
const [servedBy, setServedBy] = useLocalStorage('servedBy', 'all');
const [status, setStatus] = useLocalStorage('status', 'all');
const [department, setDepartment] = useLocalStorage<{ label: string; value: string }>('department', { value: 'all', label: t('All') });
const [department, setDepartment] = useLocalStorage<string>('department', 'all');
const [from, setFrom] = useLocalStorage('from', '');
const [to, setTo] = useLocalStorage('to', '');
const [tags, setTags] = useLocalStorage<never | { label: string; value: string }[]>('tags', []);
@ -52,7 +52,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, customFields, setCu
setGuest('');
setServedBy('all');
setStatus('all');
setDepartment({ value: 'all', label: t('All') });
setDepartment('all');
setFrom('');
setTo('');
setTags([]);
@ -75,7 +75,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, customFields, setCu
guest,
servedBy,
status,
department: department?.value && department.value !== 'all' ? department.value : '',
department: department && department !== 'all' ? department : '',
from: from && moment(new Date(from)).utc().format('YYYY-MM-DDTHH:mm:ss'),
to: to && moment(new Date(to)).utc().format('YYYY-MM-DDTHH:mm:ss'),
tags: tags.map((tag) => tag.label),

View File

@ -200,8 +200,8 @@ function EditDepartment({ data, id, title, allowedToForwardData }) {
visitorInactivityTimeoutInSeconds,
abandonedRoomsCloseCustomMessage,
waitingQueueMessage,
departmentsAllowedToForward: departmentsAllowedToForward?.map((dep) => dep.value).join(),
fallbackForwardDepartment: fallbackForwardDepartment.value,
departmentsAllowedToForward: departmentsAllowedToForward?.map((dep) => dep.value),
fallbackForwardDepartment,
};
const agentListPayload = {

View File

@ -17,10 +17,7 @@ function EditDepartmentWithAllowedForwardData({ data, ...props }) {
} = useEndpointData('/v1/livechat/department.listByIds', {
params: useMemo(
() => ({
ids:
data && data.department && data.department.departmentsAllowedToForward
? data.department.departmentsAllowedToForward.split(',')
: [],
ids: data?.department?.departmentsAllowedToForward ?? [],
}),
[data],
),

View File

@ -21,7 +21,7 @@ export const QueueListFilter: QueueListFilterPropsType = ({ setFilter, ...props
const [servedBy, setServedBy] = useLocalStorage('servedBy', 'all');
const [status, setStatus] = useLocalStorage('status', 'online');
const [department, setDepartment] = useLocalStorage<{ label: string; value: string }>('department', { value: 'all', label: t('All') });
const [department, setDepartment] = useLocalStorage<string>('department', 'all');
const handleServedBy = useMutableCallback((e) => setServedBy(e));
const handleStatus = useMutableCallback((e) => setStatus(e));
@ -39,8 +39,8 @@ export const QueueListFilter: QueueListFilterPropsType = ({ setFilter, ...props
if (servedBy !== 'all') {
filters.servedBy = servedBy;
}
if (department?.value && department.value !== 'all') {
filters.departmentId = department.value;
if (department && department !== 'all') {
filters.departmentId = department;
}
setFilter(filters);

View File

@ -24,15 +24,15 @@ const RealTimeMonitoringPage = () => {
const t = useTranslation();
const [reloadFrequency, setReloadFrequency] = useState(5);
const [department, setDepartment] = useState('');
const [departmentId, setDepartment] = useState('');
const reloadRef = useRef({});
const departmentParams = useMemo(
() => ({
...(department?.value && { departmentId: department?.value }),
...(departmentId && { departmentId }),
}),
[department],
[departmentId],
);
const allParams = useMemo(
@ -75,7 +75,7 @@ const RealTimeMonitoringPage = () => {
<Box maxWidth='50%' display='flex' mi='x4' flexGrow={1} flexDirection='column'>
<Label mb='x4'>{t('Departments')}</Label>
<AutoCompleteDepartment
value={department}
value={departmentId}
onChange={setDepartment}
placeholder={t('All')}
label={t('All')}

View File

@ -1,22 +1,15 @@
import { fetchFeatures } from '../../../client/lib/fetchFeatures';
import { queryClient } from '../../../../client/lib/queryClient';
const allModules = queryClient
.fetchQuery({
queryKey: ['ee.features'],
queryFn: fetchFeatures,
})
.then((features) => new Set<string>(features))
.catch((e) => {
console.error('Error getting modules', e);
return Promise.reject(e);
});
export async function hasLicense(feature: string): Promise<boolean> {
try {
const features = await allModules;
return features.has(feature);
const features = await queryClient.fetchQuery({
queryKey: ['ee.features'],
queryFn: fetchFeatures,
});
return features.includes(feature);
} catch (e) {
console.error('Error getting modules', e);
return false;
}
}

View File

@ -240,6 +240,7 @@ export const LivechatEnterprise = {
requestTagBeforeClosingChat: Match.Optional(Boolean),
chatClosingTags: Match.Optional([String]),
fallbackForwardDepartment: Match.Optional(String),
departmentsAllowedToForward: Match.Optional([String]),
};
// The Livechat Form department support addition/custom fields, so those fields need to be added before validating

View File

@ -24,9 +24,6 @@ overwriteClassOnLicense('livechat-enterprise', LivechatDepartment, {
if (args.length > 2 && !args[1].type) {
args[1].type = 'd';
}
if (args[1] && args[1].departmentsAllowedToForward) {
args[1].departmentsAllowedToForward = args[1].departmentsAllowedToForward.split(',');
}
return originalFn.apply(this, args);
},

View File

@ -1,19 +1,15 @@
import { useState, useEffect } from 'react';
import { useMethod, useUserId } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { hasLicense } from '../../app/license/client';
import type { BundleFeature } from '../../app/license/server/bundles';
export const useHasLicenseModule = (licenseName: BundleFeature): 'loading' | boolean => {
const [license, setLicense] = useState<'loading' | boolean>('loading');
const method = useMethod('license:getModules');
const uid = useUserId();
useEffect(() => {
hasLicense(licenseName).then((enabled) => {
if (enabled) {
return setLicense(true);
}
setLicense(false);
});
}, [licenseName]);
const features = useQuery(['ee.features'], method, {
enabled: !!uid,
});
return license;
return features.data?.includes(licenseName) ?? 'loading';
};

View File

@ -19,7 +19,7 @@ const CannedResponseEdit: FC<{
departmentData?: {
department: Serialized<ILivechatDepartment>;
};
}> = ({ data, reload, totalDataReload, isNew = false, departmentData = {} }) => {
}> = ({ data, reload, totalDataReload, isNew = false }) => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const Route = useRoute('omnichannel-canned-responses');
@ -44,9 +44,7 @@ const CannedResponseEdit: FC<{
? data.cannedResponse.tags.map((tag) => ({ label: tag, value: tag }))
: [],
scope: data ? data.cannedResponse.scope : 'user',
departmentId: data?.cannedResponse?.departmentId
? { value: data.cannedResponse.departmentId, label: departmentData?.department?.name }
: '',
departmentId: data?.cannedResponse?.departmentId ? data.cannedResponse.departmentId : '',
});
const { values, handlers, hasUnsavedChanges } = form;
@ -100,7 +98,7 @@ const CannedResponseEdit: FC<{
text: string;
scope: string;
tags: any;
departmentId: { value: string; label: string };
departmentId: string;
};
const mappedTags = tags.map((tag: string | { value: string; label: string }) => (typeof tag === 'object' ? tag?.value : tag));
await saveCannedResponse({
@ -109,7 +107,7 @@ const CannedResponseEdit: FC<{
text,
scope,
...(mappedTags.length > 0 && { tags: mappedTags }),
...(departmentId && { departmentId: departmentId.value }),
...(departmentId && { departmentId }),
});
dispatchToastMessage({
type: 'success',

View File

@ -75,7 +75,7 @@ const WrapCreateCannedResponseModal: FC<{ data?: any; reloadCannedList?: any }>
text: string;
scope: string;
tags: any;
departmentId: { value: string; label: string };
departmentId: string;
};
const mappedTags = tags.map((tag: string | { value: string; label: string }) => (typeof tag === 'object' ? tag?.value : tag));
await saveCannedResponse({
@ -84,7 +84,7 @@ const WrapCreateCannedResponseModal: FC<{ data?: any; reloadCannedList?: any }>
text,
scope,
...(tags.length > 0 && { tags: mappedTags }),
...(departmentId && { departmentId: departmentId.value }),
...(departmentId && { departmentId }),
});
dispatchToastMessage({
type: 'success',

View File

@ -62,7 +62,7 @@ export class RoomsRaw extends BaseRaw {
return statistic;
}
findByNameContainingAndTypes(name, types, discussion = false, teams = false, showOnlyTeams = false, options = {}) {
findByNameOrFnameContainingAndTypes(name, types, discussion = false, teams = false, showOnlyTeams = false, options = {}) {
const nameRegex = new RegExp(escapeRegExp(name).trim(), 'i');
const onlyTeamsQuery = showOnlyTeams ? { teamMain: { $exists: true } } : {};
@ -81,16 +81,8 @@ export class RoomsRaw extends BaseRaw {
},
prid: { $exists: discussion },
$or: [
{
$and: [
{
$or: [
{ $and: [{ $or: [{ federated: { $exists: false } }, { federated: false }], name: nameRegex }] },
{ federated: true, fname: nameRegex },
],
},
],
},
{ name: nameRegex, federated: { $ne: true } },
{ fname: nameRegex },
{
t: 'd',
usernames: nameRegex,
@ -124,7 +116,7 @@ export class RoomsRaw extends BaseRaw {
return this.findPaginated(query, options);
}
findByNameContaining(name, discussion = false, teams = false, onlyTeams = false, options = {}) {
findByNameOrFnameContaining(name, discussion = false, teams = false, onlyTeams = false, options = {}) {
const nameRegex = new RegExp(escapeRegExp(name).trim(), 'i');
const teamCondition = teams
@ -140,16 +132,8 @@ export class RoomsRaw extends BaseRaw {
const query = {
prid: { $exists: discussion },
$or: [
{
$and: [
{
$or: [
{ $and: [{ $or: [{ federated: { $exists: false } }, { federated: false }], name: nameRegex }] },
{ federated: true, fname: nameRegex },
],
},
],
},
{ name: nameRegex, federated: { $ne: true } },
{ fname: nameRegex },
{
t: 'd',
usernames: nameRegex,

View File

@ -8,8 +8,6 @@ import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import type { IGetAppsFilter } from '@rocket.chat/apps-engine/server/IGetAppsFilter';
import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator';
import { AppEvents as AppLifeCycleEvents } from '../../../ee/server/apps/communication/websockets';
import notifications from '../../../app/notifications/server/lib/Notifications';
import { SystemLogger } from '../../lib/logger/system';
export class AppsEngineService extends ServiceClassInternal implements IAppsEngineService {
@ -28,83 +26,51 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi
this.onEvent('apps.added', async (appId: string): Promise<void> => {
await (Apps.getManager() as any)?.loadOne(appId);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_ADDED, appId);
});
this.onEvent('apps.removed', async (appId: string): Promise<void> => {
const app = Apps.getManager()?.getOneById(appId);
if (!app) {
return;
}
await Apps.getManager()?.removeLocal(appId);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_REMOVED, appId);
});
this.onEvent('apps.updated', async (appId: string): Promise<void> => {
const storageItem = await Apps.getStorage()?.retrieveOne(appId);
if (!storageItem) {
return;
}
const appPackage = await Apps.getAppSourceStorage()?.fetch(storageItem);
if (!appPackage) {
return;
}
await Apps.getManager()?.updateLocal(storageItem, appPackage);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_UPDATED, appId);
});
this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise<void> => {
const app = Apps.getManager()?.getOneById(appId);
if (!app || app.getStatus() === status) {
return;
}
if (AppStatusUtils.isEnabled(status)) {
await Apps.getManager()?.enable(appId).catch(SystemLogger.error);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_STATUS_CHANGE, { appId, status });
} else if (AppStatusUtils.isDisabled(status)) {
await Apps.getManager()?.disable(appId, status, true).catch(SystemLogger.error);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_STATUS_CHANGE, { appId, status });
}
});
this.onEvent('apps.settingUpdated', async (appId: string, setting: ISetting): Promise<void> => {
const appManager = Apps.getManager();
if (!appManager) {
return;
}
await appManager.getSettingsManager().updateAppSetting(appId, setting as any);
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_SETTING_UPDATED, { appId });
});
this.onEvent('command.added', (command: string) => {
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_ADDED, command);
});
this.onEvent('command.disabled', (command: string) => {
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_DISABLED, command);
});
this.onEvent('command.updated', (command: string) => {
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_UPDATED, command);
});
this.onEvent('command.removed', (command: string) => {
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_REMOVED, command);
});
this.onEvent('actions.changed', () => {
notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.ACTIONS_CHANGED);
});
}

View File

@ -1245,6 +1245,16 @@ describe('[Rooms]', function () {
});
});
describe('/rooms.adminRooms', () => {
const suffix = `test-${Date.now()}`;
const fnameRoom = `Ελληνικά-${suffix}`;
const nameRoom = `Ellinika-${suffix}`;
before((done) => {
updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
createRoom({ type: 'p', name: fnameRoom }).end(done);
});
});
it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
updatePermission('view-room-administration', []).then(() => {
request
@ -1290,6 +1300,48 @@ describe('[Rooms]', function () {
})
.end(done);
});
it('should search the list of admin rooms using non-latin characters when UI_Allow_room_names_with_special_chars setting is toggled', (done) => {
updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
request
.get(api('rooms.adminRooms'))
.set(credentials)
.query({
filter: fnameRoom,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('rooms').and.to.be.an('array');
expect(res.body.rooms).to.have.lengthOf(1);
expect(res.body.rooms[0].fname).to.be.equal(fnameRoom);
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
})
.end(done);
});
});
it('should search the list of admin rooms using latin characters only when UI_Allow_room_names_with_special_chars setting is disabled', (done) => {
updateSetting('UI_Allow_room_names_with_special_chars', false).then(() => {
request
.get(api('rooms.adminRooms'))
.set(credentials)
.query({
filter: nameRoom,
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('rooms').and.to.be.an('array');
expect(res.body.rooms).to.have.lengthOf(1);
expect(res.body.rooms[0].name).to.be.equal(nameRoom);
expect(res.body).to.have.property('offset');
expect(res.body).to.have.property('total');
expect(res.body).to.have.property('count');
})
.end(done);
});
});
});
describe('update group dms name', () => {

View File

@ -14,6 +14,7 @@ export interface ILivechatDepartment {
businessHourId?: string;
fallbackForwardDepartment?: string;
archived?: boolean;
departmentsAllowedToForward?: string[];
// extra optional fields
[k: string]: any;
}

View File

@ -15,6 +15,7 @@ export interface ILivechatDepartmentRecord extends IRocketChatRecord {
numAgents: number;
businessHourId?: string;
fallbackForwardDepartment?: string;
departmentsAllowedToForward?: string[];
// extra optional fields
[k: string]: any;
}

View File

@ -12,11 +12,18 @@ export interface IRoomsModel extends IBaseModel<IRoom> {
getMostRecentAverageChatDurationTime(numberMostRecentChats: any, department: any): Promise<any>;
findByNameContainingAndTypes(name: any, types: any, discussion?: boolean, teams?: boolean, showOnlyTeams?: boolean, options?: any): any;
findByNameOrFnameContainingAndTypes(
name: any,
types: any,
discussion?: boolean,
teams?: boolean,
showOnlyTeams?: boolean,
options?: any,
): any;
findByTypes(types: any, discussion?: boolean, teams?: boolean, onlyTeams?: boolean, options?: any): any;
findByNameContaining(name: any, discussion?: boolean, teams?: boolean, onlyTeams?: boolean, options?: any): any;
findByNameOrFnameContaining(name: any, discussion?: boolean, teams?: boolean, onlyTeams?: boolean, options?: any): any;
findByTeamId(teamId: any, options?: any): any;

View File

@ -2,7 +2,6 @@ import type {
IOmnichannelCannedResponse,
ILivechatAgent,
ILivechatDepartment,
ILivechatDepartmentRecord,
ILivechatDepartmentAgents,
ILivechatMonitor,
ILivechatTag,
@ -2938,7 +2937,7 @@ export type OmnichannelEndpoints = {
};
'/v1/livechat/department/:_id': {
GET: (params: LivechatDepartmentId) => {
department: ILivechatDepartmentRecord | null;
department: ILivechatDepartment | null;
agents?: ILivechatDepartmentAgents[];
};
PUT: (params: { department: Partial<ILivechatDepartment>[]; agents: any[] }) => {