mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
Merge remote-tracking branch 'origin/develop' into release-candidate
This commit is contained in:
commit
44a9a912ba
2
.github/workflows/ci-test-e2e.yml
vendored
2
.github/workflows/ci-test-e2e.yml
vendored
@ -50,8 +50,6 @@ on:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
TURBO_TEAM:
|
||||
required: true
|
||||
CR_USER:
|
||||
required: true
|
||||
CR_PAT:
|
||||
|
||||
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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} />}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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],
|
||||
),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
},
|
||||
|
||||
@ -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';
|
||||
};
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -14,6 +14,7 @@ export interface ILivechatDepartment {
|
||||
businessHourId?: string;
|
||||
fallbackForwardDepartment?: string;
|
||||
archived?: boolean;
|
||||
departmentsAllowedToForward?: string[];
|
||||
// extra optional fields
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ export interface ILivechatDepartmentRecord extends IRocketChatRecord {
|
||||
numAgents: number;
|
||||
businessHourId?: string;
|
||||
fallbackForwardDepartment?: string;
|
||||
departmentsAllowedToForward?: string[];
|
||||
// extra optional fields
|
||||
[k: string]: any;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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[] }) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user