From db919f9b23b81c13b653303faab5500cc8a2564e Mon Sep 17 00:00:00 2001 From: rocketchat-github-ci Date: Tue, 5 Sep 2023 20:13:30 +0000 Subject: [PATCH 01/26] Bump 6.3.4 --- .changeset/bump-patch-1693944810152.md | 5 +++++ yarn.lock | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .changeset/bump-patch-1693944810152.md diff --git a/.changeset/bump-patch-1693944810152.md b/.changeset/bump-patch-1693944810152.md new file mode 100644 index 000000000000..e1eaa7980afb --- /dev/null +++ b/.changeset/bump-patch-1693944810152.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Bump @rocket.chat/meteor version. diff --git a/yarn.lock b/yarn.lock index 251222652634..95498d62f3b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9898,9 +9898,9 @@ __metadata: "@rocket.chat/icons": "*" "@rocket.chat/prettier-config": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.2 + "@rocket.chat/ui-contexts": 1.0.3 "@rocket.chat/ui-kit": "*" - "@rocket.chat/ui-video-conf": 1.0.2 + "@rocket.chat/ui-video-conf": 1.0.3 "@tanstack/react-query": "*" react: "*" react-dom: "*" @@ -9982,14 +9982,14 @@ __metadata: ts-jest: ~29.0.5 typescript: ~5.1.3 peerDependencies: - "@rocket.chat/core-typings": 6.3.2 + "@rocket.chat/core-typings": 6.3.3 "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-tokens": "*" "@rocket.chat/message-parser": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-client": 1.0.2 - "@rocket.chat/ui-contexts": 1.0.2 + "@rocket.chat/ui-client": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.3 katex: "*" react: "*" languageName: unknown @@ -11048,7 +11048,7 @@ __metadata: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" - "@rocket.chat/ui-contexts": 1.0.2 + "@rocket.chat/ui-contexts": 1.0.3 react: ~17.0.2 languageName: unknown linkType: soft @@ -11202,7 +11202,7 @@ __metadata: "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.2 + "@rocket.chat/ui-contexts": 1.0.3 react: ^17.0.2 react-dom: ^17.0.2 languageName: unknown @@ -11268,7 +11268,7 @@ __metadata: typescript: ~5.1.3 peerDependencies: "@rocket.chat/layout": "*" - "@rocket.chat/ui-contexts": 1.0.2 + "@rocket.chat/ui-contexts": 1.0.3 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" From ebeb088441777a3c32bba0d4d50a048e3e9f5b8c Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 4 Sep 2023 13:33:57 -0600 Subject: [PATCH 02/26] fix: Prevent `RoomProvider.useEffect` from subscribing multiple times (#30273) --- .changeset/forty-hotels-pretend.md | 5 +++++ .../views/room/providers/RoomProvider.tsx | 22 ++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 .changeset/forty-hotels-pretend.md diff --git a/.changeset/forty-hotels-pretend.md b/.changeset/forty-hotels-pretend.md new file mode 100644 index 000000000000..b23825d5a02a --- /dev/null +++ b/.changeset/forty-hotels-pretend.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: Prevent `RoomProvider.useEffect` from subscribing to room-data stream multiple times diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index b2935f4702ea..20da5194895f 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -1,5 +1,4 @@ import type { IRoom } from '@rocket.chat/core-typings'; -import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { usePermission, useStream, useUserId, useRouter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import type { ReactNode, ContextType, ReactElement } from 'react'; @@ -36,17 +35,18 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { const queryClient = useQueryClient(); const userId = useUserId(); const isLivechatAdmin = usePermission('view-livechat-rooms'); + const { t: roomType } = room ?? {}; // TODO: move this to omnichannel context only useEffect(() => { - if (!room || !isOmnichannelRoom(room)) { + if (roomType !== 'l') { return; } return subscribeToRoom(rid, (room) => { queryClient.setQueryData(['rooms', rid], room); }); - }, [subscribeToRoom, rid, queryClient, room]); + }, [subscribeToRoom, rid, queryClient, roomType]); // TODO: the following effect is a workaround while we don't have a general and definitive solution for it const router = useRouter(); @@ -56,19 +56,21 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { } }, [isSuccess, room, router]); + const { _id: servedById } = room?.servedBy ?? {}; + // TODO: Review the necessity of this effect when we move away from cached collections useEffect(() => { - if (!room || !isOmnichannelRoom(room) || !room.servedBy) { + if (roomType !== 'l' || !servedById) { return; } - if (!isLivechatAdmin && room.servedBy._id !== userId) { - ChatRoom.remove(room._id); - queryClient.removeQueries(['rooms', room._id]); - queryClient.removeQueries(['rooms', { reference: room._id, type: 'l' }]); - queryClient.removeQueries(['/v1/rooms.info', room._id]); + if (!isLivechatAdmin && servedById !== userId) { + ChatRoom.remove(rid); + queryClient.removeQueries(['rooms', rid]); + queryClient.removeQueries(['rooms', { reference: rid, type: 'l' }]); + queryClient.removeQueries(['/v1/rooms.info', rid]); } - }, [isLivechatAdmin, queryClient, userId, room]); + }, [isLivechatAdmin, queryClient, userId, rid, roomType, servedById]); const subscriptionQuery = useReactiveQuery(['subscriptions', { rid }], () => ChatSubscription.findOne({ rid }) ?? null); From d418d0e752a69abecfe41ef84d1f7abf70a03d20 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Wed, 6 Sep 2023 12:45:10 -0300 Subject: [PATCH 03/26] fix: added debounce to units and custom fields search (#30291) --- .../views/omnichannel/customFields/CustomFieldsTable.tsx | 7 ++++--- apps/meteor/ee/client/omnichannel/units/UnitsTable.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx index b92fad97633a..d9ef416226cc 100644 --- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx +++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx @@ -1,5 +1,5 @@ import { Pagination } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useRoute, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { MutableRefObject } from 'react'; @@ -24,18 +24,19 @@ const CustomFieldsTable = ({ reload }: { reload: MutableRefObject<() => void> }) const t = useTranslation(); const [filter, setFilter] = useState(''); const departmentsRoute = useRoute('omnichannel-customfields'); + const debouncedFilter = useDebouncedValue(filter, 500); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); const { sortBy, sortDirection, setSort } = useSort<'_id' | 'label' | 'scope' | 'visibility'>('_id'); const query = useMemo( () => ({ - text: filter, + text: debouncedFilter, sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, ...(itemsPerPage && { count: itemsPerPage }), ...(current && { offset: current }), }), - [filter, itemsPerPage, current, sortBy, sortDirection], + [debouncedFilter, itemsPerPage, current, sortBy, sortDirection], ); const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); diff --git a/apps/meteor/ee/client/omnichannel/units/UnitsTable.tsx b/apps/meteor/ee/client/omnichannel/units/UnitsTable.tsx index a9ab094229f6..f85b73216612 100644 --- a/apps/meteor/ee/client/omnichannel/units/UnitsTable.tsx +++ b/apps/meteor/ee/client/omnichannel/units/UnitsTable.tsx @@ -1,5 +1,5 @@ import { Pagination } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useEndpoint, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { MutableRefObject } from 'react'; @@ -24,6 +24,7 @@ const UnitsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => { const t = useTranslation(); const [filter, setFilter] = useState(''); const unitsRoute = useRoute('omnichannel-units'); + const debouncedFilter = useDebouncedValue(filter, 500); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); const { sortBy, sortDirection, setSort } = useSort<'name' | 'visibility'>('name'); @@ -31,12 +32,12 @@ const UnitsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => { const query = useMemo( () => ({ fields: JSON.stringify({ name: 1 }), - text: filter, + text: debouncedFilter, sort: JSON.stringify({ [sortBy]: sortDirection === 'asc' ? 1 : -1 }), ...(itemsPerPage && { count: itemsPerPage }), ...(current && { offset: current }), }), - [filter, itemsPerPage, current, sortBy, sortDirection], + [debouncedFilter, itemsPerPage, current, sortBy, sortDirection], ); const getUnits = useEndpoint('GET', '/v1/livechat/units'); From 69bb4771d76fdeaf95c439a90597223397d9e3eb Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 6 Sep 2023 15:02:39 -0300 Subject: [PATCH 04/26] chore: prevent unneeded calls to EE settings service (#30249) --- .../modules/listeners/listeners.module.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index 5c46343f67a4..856b91efce56 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -1,10 +1,11 @@ import type { ISetting as AppsSetting } from '@rocket.chat/apps-engine/definition/settings'; -import { UserStatus, isSettingColor } from '@rocket.chat/core-typings'; +import { UserStatus, isSettingColor, isSettingEnterprise } from '@rocket.chat/core-typings'; import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; -import type { IUser, IRoom, VideoConference, ISetting, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { parse } from '@rocket.chat/message-parser'; import type { IServiceClass } from '@rocket.chat/core-services'; import { EnterpriseSettings } from '@rocket.chat/core-services'; +import type { IUser, IRoom, VideoConference, ISetting, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { Logger } from '@rocket.chat/logger'; +import { parse } from '@rocket.chat/message-parser'; import type { NotificationsModule } from '../notifications/notifications.module'; import { settings } from '../../../app/settings/server/cached'; @@ -26,6 +27,8 @@ const minimongoChangeMap: Record = { export class ListenersModule { constructor(service: IServiceClass, notifications: NotificationsModule) { + const logger = new Logger('ListenersModule'); + service.onEvent('emoji.deleteCustom', (emoji) => { notifications.notifyLoggedInThisInstance('deleteEmojiCustom', { emojiData: emoji, @@ -247,11 +250,16 @@ export class ListenersModule { }); service.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { - if (clientAction !== 'removed') { - // TODO check if setting is EE before calling this - const result = await EnterpriseSettings.changeSettingValue(setting); - if (result !== undefined && !(result instanceof Error)) { - setting.value = result; + // if a EE setting changed make sure we broadcast the correct value according to license + if (clientAction !== 'removed' && isSettingEnterprise(setting)) { + try { + const result = await EnterpriseSettings.changeSettingValue(setting); + if (result !== undefined && !(result instanceof Error)) { + setting.value = result; + } + } catch (err: unknown) { + logger.error({ msg: 'Error getting proper enterprise setting value. Returning `invalidValue` instead.', err }); + setting.value = setting.invalidValue; } } From 8ca4ac78cf5584bc02b6faa92781d1171ebcd01f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 6 Sep 2023 16:56:47 -0300 Subject: [PATCH 05/26] chore: fix logger import --- apps/meteor/server/modules/listeners/listeners.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index 856b91efce56..06e494fdb556 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -4,11 +4,11 @@ import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IServiceClass } from '@rocket.chat/core-services'; import { EnterpriseSettings } from '@rocket.chat/core-services'; import type { IUser, IRoom, VideoConference, ISetting, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { Logger } from '@rocket.chat/logger'; import { parse } from '@rocket.chat/message-parser'; import type { NotificationsModule } from '../notifications/notifications.module'; import { settings } from '../../../app/settings/server/cached'; +import { Logger } from '../../lib/logger/Logger'; const isMessageParserDisabled = process.env.DISABLE_MESSAGE_PARSER === 'true'; From 8a7d5d3898c40c221aad88c7df28753fe595dab1 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 7 Sep 2023 22:41:37 +0400 Subject: [PATCH 06/26] fix: agent role being removed upon user deactivation (#30284) --- .changeset/seven-jobs-tickle.md | 6 + .../business-hour/AbstractBusinessHour.ts | 28 +---- .../livechat/server/business-hour/Single.ts | 34 +++++- .../livechat/server/hooks/afterUserActions.ts | 10 +- .../server/business-hour/Multiple.ts | 111 +++++++++++++++++- apps/meteor/server/models/raw/Users.js | 25 +++- .../model-typings/src/models/IUsersModel.ts | 1 + 7 files changed, 180 insertions(+), 35 deletions(-) create mode 100644 .changeset/seven-jobs-tickle.md diff --git a/.changeset/seven-jobs-tickle.md b/.changeset/seven-jobs-tickle.md new file mode 100644 index 000000000000..870bafbb7d9d --- /dev/null +++ b/.changeset/seven-jobs-tickle.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +fix: agent role being removed upon user deactivation diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index aeaf6bc1f287..7f9e8eaa404b 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -1,13 +1,10 @@ import moment from 'moment-timezone'; -import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; -import type { ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings'; +import type { ILivechatAgentStatus, ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings'; import type { ILivechatBusinessHoursModel, IUsersModel } from '@rocket.chat/model-typings'; import { LivechatBusinessHours, Users } from '@rocket.chat/models'; import type { UpdateFilter } from 'mongodb'; import type { IWorkHoursCronJobsWrapper } from '../../../../server/models/raw/LivechatBusinessHours'; -import { businessHourLogger } from '../lib/logger'; -import { filterBusinessHoursThatMustBeOpened } from './Helper'; export interface IBusinessHourBehavior { findHoursToCreateJobs(): Promise; @@ -61,29 +58,6 @@ export abstract class AbstractBusinessHourBehavior { { livechatStatusSystemModified: true }, ); } - - async onNewAgentCreated(agentId: string): Promise { - businessHourLogger.debug(`Executing onNewAgentCreated for agentId: ${agentId}`); - - const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour(); - if (!defaultBusinessHour) { - businessHourLogger.debug(`No default business hour found for agentId: ${agentId}`); - return; - } - - const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]); - if (!businessHourToOpen.length) { - businessHourLogger.debug( - `No business hour to open found for agentId: ${agentId}. Default business hour is closed. Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.NOT_AVAILABLE}`, - ); - await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE); - return; - } - - await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id); - - businessHourLogger.debug(`Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.AVAILABLE}`); - } } export abstract class AbstractBusinessHourType { diff --git a/apps/meteor/app/livechat/server/business-hour/Single.ts b/apps/meteor/app/livechat/server/business-hour/Single.ts index df9b3bce2a2e..cdb02d59d2b9 100644 --- a/apps/meteor/app/livechat/server/business-hour/Single.ts +++ b/apps/meteor/app/livechat/server/business-hour/Single.ts @@ -1,8 +1,9 @@ -import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; +import { ILivechatAgentStatus, LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; +import { LivechatBusinessHours, Users } from '@rocket.chat/models'; import type { IBusinessHourBehavior } from './AbstractBusinessHour'; import { AbstractBusinessHourBehavior } from './AbstractBusinessHour'; -import { openBusinessHourDefault } from './Helper'; +import { filterBusinessHoursThatMustBeOpened, openBusinessHourDefault } from './Helper'; import { businessHourLogger } from '../lib/logger'; export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { @@ -26,6 +27,35 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp return openBusinessHourDefault(); } + async onNewAgentCreated(agentId: string): Promise { + const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour(); + if (!defaultBusinessHour) { + businessHourLogger.debug('No default business hour found for agentId', { + agentId, + }); + return; + } + + const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]); + if (!businessHourToOpen.length) { + businessHourLogger.debug({ + msg: 'No business hours found. Moving agent to NOT_AVAILABLE status', + agentId, + newStatus: ILivechatAgentStatus.NOT_AVAILABLE, + }); + await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE); + return; + } + + await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id); + + businessHourLogger.debug({ + msg: 'Business hours found. Moving agent to AVAILABLE status', + agentId, + newStatus: ILivechatAgentStatus.AVAILABLE, + }); + } + afterSaveBusinessHours(): Promise { return openBusinessHourDefault(); } diff --git a/apps/meteor/app/livechat/server/hooks/afterUserActions.ts b/apps/meteor/app/livechat/server/hooks/afterUserActions.ts index 50fe5846637b..30900481c4e2 100644 --- a/apps/meteor/app/livechat/server/hooks/afterUserActions.ts +++ b/apps/meteor/app/livechat/server/hooks/afterUserActions.ts @@ -1,4 +1,5 @@ -import type { IUser } from '@rocket.chat/core-typings'; +import { type IUser } from '@rocket.chat/core-typings'; +import { Users } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { Livechat } from '../lib/Livechat'; @@ -33,8 +34,11 @@ const handleAgentCreated = async (user: IUser) => { const handleDeactivateUser = async (user: IUser) => { if (wasAgent(user)) { - callbackLogger.debug('Removing agent', user._id); - await Livechat.removeAgent(user.username); + callbackLogger.debug({ + msg: 'Removing agent extension & making agent unavailable', + userId: user._id, + }); + await Users.makeAgentUnavailableAndUnsetExtension(user._id); } }; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index 0fd1d3ee8284..a6bc2a456ad7 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -1,13 +1,18 @@ import moment from 'moment'; +import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import type { ILivechatDepartment, ILivechatBusinessHour } from '@rocket.chat/core-typings'; import { LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models'; import type { IBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { AbstractBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; -import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper'; +import { + filterBusinessHoursThatMustBeOpened, + filterBusinessHoursThatMustBeOpenedByDay, +} from '../../../../../app/livechat/server/business-hour/Helper'; +import { settings } from '../../../../../app/settings/server'; +import { isTruthy } from '../../../../../lib/isTruthy'; import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper'; import { bhLogger } from '../lib/logger'; -import { settings } from '../../../../../app/settings/server'; import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { @@ -248,6 +253,108 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return this.UsersRepository.isAgentWithinBusinessHours(agentId); } + async onNewAgentCreated(agentId: string): Promise { + bhLogger.debug({ + msg: 'Executing onNewAgentCreated for agent', + agentId, + }); + + await this.applyAnyOpenBusinessHourToAgent(agentId); + + await Users.updateLivechatStatusBasedOnBusinessHours([agentId]); + } + + private async applyAnyOpenBusinessHourToAgent(agentId: string): Promise { + const currentTime = moment().utc(); + const day = currentTime.format('dddd'); + const allActiveBusinessHoursForEntireWeek = await this.BusinessHourRepository.findActiveBusinessHours({ + projection: { + workHours: 1, + timezone: 1, + type: 1, + active: 1, + }, + }); + const openedBusinessHours = await filterBusinessHoursThatMustBeOpenedByDay(allActiveBusinessHoursForEntireWeek, day); + if (!openedBusinessHours.length) { + bhLogger.debug({ + msg: 'Business hour status check failed for agent. No opened business hour found for the current day', + agentId, + }); + return; + } + + const agentDepartments = await LivechatDepartmentAgents.find( + { departmentEnabled: true, agentId }, + { projection: { agentId: 1, departmentId: 1 } }, + ).toArray(); + + if (!agentDepartments.length) { + // check if default businessHour is active + const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT); + if (isDefaultBHActive?._id) { + await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId); + + bhLogger.debug({ + msg: 'Business hour status check passed for agent. Found default business hour to be active', + agentId, + }); + return; + } + + bhLogger.debug({ + msg: 'Business hour status check failed for agent. Found default business hour to be inactive', + agentId, + }); + return; + } + + // check if any one these departments have a opened business hour linked to it + const departments = (await LivechatDepartment.findInIds( + agentDepartments.map(({ departmentId }) => departmentId), + { projection: { _id: 1, businessHourId: 1 } }, + ).toArray()) as Pick[]; + + const departmentsWithActiveBH = departments.filter( + ({ businessHourId }) => businessHourId && openedBusinessHours.findIndex(({ _id }) => _id === businessHourId) !== -1, + ); + + if (!departmentsWithActiveBH.length) { + // No opened business hour found for any of the departments connected to the agent + // check if this agent has any departments that is connected to any non-default business hour + // if no such departments found then check default BH and if it is active, then allow the agent to change service status + const hasAtLeastOneDepartmentWithNonDefaultBH = departments.some(({ businessHourId }) => { + // check if business hour is active + return businessHourId && allActiveBusinessHoursForEntireWeek.findIndex(({ _id }) => _id === businessHourId) !== -1; + }); + if (!hasAtLeastOneDepartmentWithNonDefaultBH) { + const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT); + if (isDefaultBHActive?._id) { + await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId); + + bhLogger.debug({ + msg: 'Business hour status check passed for agentId. Found default business hour to be active and agent has no departments with non-default business hours', + agentId, + }); + return; + } + } + bhLogger.debug({ + msg: 'Business hour status check failed for agent. No opened business hour found for any of the departments connected to the agent', + agentId, + }); + return; + } + + const activeBusinessHoursForAgent = departmentsWithActiveBH.map(({ businessHourId }) => businessHourId).filter(isTruthy); + await Users.openAgentBusinessHoursByBusinessHourIdsAndAgentId(activeBusinessHoursForAgent, agentId); + + bhLogger.debug({ + msg: `Business hour status check passed for agent. Found opened business hour for departments connected to the agent`, + activeBusinessHoursForAgent, + }); + } + private async handleRemoveAgentsFromDepartments(department: Record, agentsIds: string[], options: any): Promise { const agentIdsWithoutDepartment: string[] = []; const agentIdsToRemoveCurrentBusinessHour: string[] = []; diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 2719a5f32b9c..8c9bb726ef07 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -1,3 +1,4 @@ +import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Subscriptions } from '@rocket.chat/models'; @@ -6,6 +7,8 @@ import { BaseRaw } from './BaseRaw'; const queryStatusAgentOnline = (extraFilters = {}, isLivechatEnabledWhenAgentIdle) => ({ statusLivechat: 'available', roles: 'livechat-agent', + // ignore deactivated users + active: true, ...(!isLivechatEnabledWhenAgentIdle && { $or: [ { @@ -887,7 +890,7 @@ export class UsersRaw extends BaseRaw { }, }; - return this.updateMany(query, update); + return this.updateOne(query, update); } addBusinessHourByAgentIds(agentIds = [], businessHourId) { @@ -985,6 +988,8 @@ export class UsersRaw extends BaseRaw { const query = { $or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }], roles: 'livechat-agent', + // exclude deactivated users + active: true, // Avoid unnecessary updates statusLivechat: 'available', ...(Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } }), @@ -1641,6 +1646,24 @@ export class UsersRaw extends BaseRaw { return this.updateOne(query, update); } + makeAgentUnavailableAndUnsetExtension(userId) { + const query = { + _id: userId, + roles: 'livechat-agent', + }; + + const update = { + $set: { + statusLivechat: ILivechatAgentStatus.NOT_AVAILABLE, + }, + $unset: { + extension: 1, + }, + }; + + return this.updateOne(query, update); + } + setLivechatData(userId, data = {}) { // TODO: Create class Agent const query = { diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 9ee633a1cd80..393513dd78bc 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -258,6 +258,7 @@ export interface IUsersModel extends IBaseModel { getNextAgent(ignoreAgentId?: string, extraQuery?: Filter): Promise<{ agentId: string; username: string } | null>; getNextBotAgent(ignoreAgentId?: string): Promise<{ agentId: string; username: string } | null>; setLivechatStatus(userId: string, status: ILivechatAgentStatus): Promise; + makeAgentUnavailableAndUnsetExtension(userId: string): Promise; setLivechatData(userId: string, data?: Record): Promise; closeOffice(): Promise; openOffice(): Promise; From 16bfbb50bebafcd0f94d337000e640bbc0030d38 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Wed, 6 Sep 2023 18:37:45 -0300 Subject: [PATCH 07/26] fix: Selected options not being displayed for monitors and departments (#30292) Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com> --- .../views/hooks/useDepartmentsByUnitsList.ts | 1 - .../client/views/hooks/useMonitorsList.ts | 12 ++++----- .../server/lib/Department.ts | 2 +- .../ee/client/omnichannel/units/UnitEdit.js | 25 +++++++++---------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts b/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts index ceb7dc933df5..6c402a199ded 100644 --- a/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts +++ b/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts @@ -45,7 +45,6 @@ export const useDepartmentsByUnitsList = ( name: department.archived ? `${name} [${t('Archived')}]` : name, label: name, value: _id, - ...(_updatedAt && { _updatedAt: new Date(_updatedAt) }), }; }), itemCount: total, diff --git a/apps/meteor/client/views/hooks/useMonitorsList.ts b/apps/meteor/client/views/hooks/useMonitorsList.ts index 4d9ccba5c883..512b49d49d8e 100644 --- a/apps/meteor/client/views/hooks/useMonitorsList.ts +++ b/apps/meteor/client/views/hooks/useMonitorsList.ts @@ -33,15 +33,15 @@ export const useMonitorsList = ( text: options.filter, offset: start, count: end + start, + sort: JSON.stringify({ username: 1 }), }); return { - items: monitors.map((members: any) => { - members._updatedAt = new Date(members._updatedAt); - members.label = members.username; - members.value = members._id; - return members; - }), + items: monitors.map((members: any) => ({ + ...members, + label: members.username, + value: members._id, + })), itemCount: total, }; }, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/Department.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/Department.ts index a47515502840..01ce3b9470a8 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/Department.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/Department.ts @@ -24,7 +24,7 @@ export const findAllDepartmentsAvailable = async ( query = await callbacks.run('livechat.applyDepartmentRestrictions', query, { userId: uid }); } - const { cursor, totalCount } = LivechatDepartment.findPaginated(query, { limit: count, offset }); + const { cursor, totalCount } = LivechatDepartment.findPaginated(query, { limit: count, offset, sort: { name: 1 } }); const [departments, total] = await Promise.all([cursor.toArray(), totalCount]); diff --git a/apps/meteor/ee/client/omnichannel/units/UnitEdit.js b/apps/meteor/ee/client/omnichannel/units/UnitEdit.js index bf7b7db50e80..ef33d8da93e7 100644 --- a/apps/meteor/ee/client/omnichannel/units/UnitEdit.js +++ b/apps/meteor/ee/client/omnichannel/units/UnitEdit.js @@ -33,17 +33,6 @@ function UnitEdit({ title, data, unitId, isNew, unitMonitors, unitDepartments, r const { phase: departmentsPhase, items: departmentsItems, itemCount: departmentsTotal } = useRecordList(departmentsList); - const departmentsSortedByName = departmentsItems.sort((a, b) => { - if (a.name > b.name) { - return 1; - } - if (a.name < b.name) { - return -1; - } - - return 0; - }); - const unit = data || {}; const currUnitMonitors = useMemo( @@ -82,6 +71,16 @@ function UnitEdit({ title, data, unitId, isNew, unitMonitors, unitDepartments, r const { handleName, handleVisibility, handleDepartments, handleMonitors } = handlers; const { name, visibility, departments, monitors } = values; + const departmentsOptions = useMemo(() => { + const pending = departments.filter(({ value }) => !departmentsItems.find((dep) => dep.value === value)); + return [...departmentsItems, ...pending]; + }, [departments, departmentsItems]); + + const monitorsOptions = useMemo(() => { + const pending = monitors.filter(({ value }) => !monitorsItems.find((mon) => mon.value === value)); + return [...monitorsItems, ...pending]; + }, [monitors, monitorsItems]); + const nameError = useMemo(() => (!name || name.length === 0 ? t('The_field_is_required', t('name')) : undefined), [name, t]); const visibilityError = useMemo( () => (!visibility || visibility.length === 0 ? t('The_field_is_required', t('description')) : undefined), @@ -172,7 +171,7 @@ function UnitEdit({ title, data, unitId, isNew, unitMonitors, unitDepartments, r withTitle filter={departmentsFilter} setFilter={setDepartmentsFilter} - options={departmentsSortedByName} + options={departmentsOptions} value={departments} error={hasUnsavedChanges && departmentError} maxWidth='100%' @@ -194,7 +193,7 @@ function UnitEdit({ title, data, unitId, isNew, unitMonitors, unitDepartments, r withTitle filter={monitorsFilter} setFilter={setMonitorsFilter} - options={monitorsItems} + options={monitorsOptions} value={monitors} error={hasUnsavedChanges && unitMonitorsError} maxWidth='100%' From 759fe2472aadc4c06277cde9014870bf1347bdd4 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 6 Sep 2023 09:59:09 -0600 Subject: [PATCH 08/26] chore: Increase cache time to 10s on `getUnits` function (#30285) --- .changeset/three-birds-tickle.md | 5 +++++ apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts | 2 +- .../server/methods/getUnitsFromUserRoles.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/three-birds-tickle.md diff --git a/.changeset/three-birds-tickle.md b/.changeset/three-birds-tickle.md new file mode 100644 index 000000000000..0ce911d9f6fa --- /dev/null +++ b/.changeset/three-birds-tickle.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +chore: Increase cache time from 5s to 10s on `getUnits` helpers. This should reduce the number of DB calls made by this method to fetch the unit limitations for a user. diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts index 4b370b6b7d8a..f22099a618db 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/units.ts @@ -8,7 +8,7 @@ async function hasUnits(): Promise { } // Units should't change really often, so we can cache the result -const memoizedHasUnits = mem(hasUnits, { maxAge: 5000 }); +const memoizedHasUnits = mem(hasUnits, { maxAge: 10000 }); export async function getUnitsFromUser(): Promise<{ [k: string]: any }[] | undefined> { if (!(await memoizedHasUnits())) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts index a9d3c3556b97..0c2fdb7b806b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/getUnitsFromUserRoles.ts @@ -17,7 +17,7 @@ async function getUnitsFromUserRoles(user: string | null): Promise Date: Fri, 8 Sep 2023 23:25:20 +0530 Subject: [PATCH 09/26] fix: update type of message bridge's create method (#30321) --- .changeset/loud-bees-smoke.md | 5 +++++ apps/meteor/app/apps/server/bridges/messages.ts | 4 ++-- apps/meteor/package.json | 4 ++-- yarn.lock | 14 +++++++------- 4 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/loud-bees-smoke.md diff --git a/.changeset/loud-bees-smoke.md b/.changeset/loud-bees-smoke.md new file mode 100644 index 000000000000..7b34a0d58af4 --- /dev/null +++ b/.changeset/loud-bees-smoke.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +New helper for Apps to notify users via a Direct Message diff --git a/apps/meteor/app/apps/server/bridges/messages.ts b/apps/meteor/app/apps/server/bridges/messages.ts index d75a0c244674..47648e445939 100644 --- a/apps/meteor/app/apps/server/bridges/messages.ts +++ b/apps/meteor/app/apps/server/bridges/messages.ts @@ -1,4 +1,4 @@ -import type { IMessage } from '@rocket.chat/apps-engine/definition/messages'; +import type { IMessage, IDirectMessage } from '@rocket.chat/apps-engine/definition/messages'; import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import type { IUser } from '@rocket.chat/apps-engine/definition/users'; import type { ITypingDescriptor } from '@rocket.chat/apps-engine/server/bridges/MessageBridge'; @@ -17,7 +17,7 @@ export class AppMessageBridge extends MessageBridge { super(); } - protected async create(message: IMessage, appId: string): Promise { + protected async create(message: IMessage | IDirectMessage, appId: string): Promise { this.orch.debugLog(`The App ${appId} is creating a new message.`); const convertedMessage = await this.orch.getConverters()?.get('messages').convertAppMessage(message); diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 4b4d1275656e..562908ff67a9 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -225,7 +225,7 @@ "@rocket.chat/account-utils": "workspace:^", "@rocket.chat/agenda": "workspace:^", "@rocket.chat/api-client": "workspace:^", - "@rocket.chat/apps-engine": "1.41.0-alpha.312", + "@rocket.chat/apps-engine": "1.41.0-alpha.325", "@rocket.chat/base64": "workspace:^", "@rocket.chat/cas-validate": "workspace:^", "@rocket.chat/core-services": "workspace:^", @@ -469,4 +469,4 @@ "installConfig": { "hoistingLimits": "workspaces" } -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 36f1aba8d8ae..249995955936 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7774,9 +7774,9 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/apps-engine@npm:1.41.0-alpha.312": - version: 1.41.0-alpha.312 - resolution: "@rocket.chat/apps-engine@npm:1.41.0-alpha.312" +"@rocket.chat/apps-engine@npm:1.41.0-alpha.325": + version: 1.41.0-alpha.325 + resolution: "@rocket.chat/apps-engine@npm:1.41.0-alpha.325" dependencies: adm-zip: ^0.5.9 cryptiles: ^4.1.3 @@ -7784,11 +7784,11 @@ __metadata: lodash.clonedeep: ^4.5.0 semver: ^5.7.1 stack-trace: 0.0.10 - uuid: ^3.4.0 + uuid: ~8.3.2 vm2: ^3.9.19 peerDependencies: "@rocket.chat/ui-kit": "*" - checksum: 003853d3c4d4374ab984474026e4ae657daf4591fe4c375b914aa57c27f576af0fcba66e70c539e056b5d80a1ef655775f6f3a07bf81a36ab6fd438ce464e70f + checksum: 3159b69d1174166bfe1fea13ac51e81bc39ddcabad3a8dcd20e4614d33592b7f93ae45625578d7545c149f52f90e9c30dee92a1bbd3f5830f7bcdc13d19fcef4 languageName: node linkType: hard @@ -8520,7 +8520,7 @@ __metadata: "@rocket.chat/account-utils": "workspace:^" "@rocket.chat/agenda": "workspace:^" "@rocket.chat/api-client": "workspace:^" - "@rocket.chat/apps-engine": 1.41.0-alpha.312 + "@rocket.chat/apps-engine": 1.41.0-alpha.325 "@rocket.chat/base64": "workspace:^" "@rocket.chat/cas-validate": "workspace:^" "@rocket.chat/core-services": "workspace:^" @@ -38612,7 +38612,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.0.0, uuid@npm:^8.3.1, uuid@npm:^8.3.2": +"uuid@npm:^8.0.0, uuid@npm:^8.3.1, uuid@npm:^8.3.2, uuid@npm:~8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" bin: From 392bed8eefeafa194acb061dd5e7a5482c37a321 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Fri, 8 Sep 2023 18:15:00 -0300 Subject: [PATCH 10/26] regression: `AccountProfileForm` not reseting (#30332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Júlia Jaeger Foresti <60678893+juliajforesti@users.noreply.github.com> --- .../account/profile/AccountProfileForm.tsx | 168 +++++++++++------- 1 file changed, 100 insertions(+), 68 deletions(-) diff --git a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx index cb65ce80317e..1a326db4544d 100644 --- a/apps/meteor/client/views/account/profile/AccountProfileForm.tsx +++ b/apps/meteor/client/views/account/profile/AccountProfileForm.tsx @@ -22,7 +22,6 @@ import UserAvatarEditor from '../../../components/avatar/UserAvatarEditor'; import { useUpdateAvatar } from '../../../hooks/useUpdateAvatar'; import { USER_STATUS_TEXT_MAX_LENGTH, BIO_TEXT_MAX_LENGTH } from '../../../lib/constants'; import type { AccountProfileFormValues } from './getProfileInitialValues'; -import { getProfileInitialValues } from './getProfileInitialValues'; import { useAccountProfileSettings } from './useAccountProfileSettings'; const AccountProfileForm = (props: AllHTMLAttributes): ReactElement => { @@ -46,10 +45,8 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle } = useAccountProfileSettings(); const { - register, control, watch, - reset, handleSubmit, formState: { errors }, } = useFormContext(); @@ -97,10 +94,10 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle try { await updateOwnBasicInfo( { - ...(allowRealNameChange ? { realname: name } : {}), - ...(allowEmailChange && user ? getUserEmailAddress(user) !== email && { email } : {}), - ...(canChangeUsername ? { username } : {}), - ...(allowUserStatusMessageChange ? { statusText } : {}), + realname: name, + ...(user ? getUserEmailAddress(user) !== email && { email } : {}), + username, + statusText, statusType, nickname, bio, @@ -110,7 +107,6 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle await updateAvatar(); dispatchToastMessage({ type: 'success', message: t('Profile_saved_successfully') }); - reset(getProfileInitialValues(user)); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } @@ -147,16 +143,21 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle {t('Name')} - (requireName && name === '' ? t('error-the-field-is-required', { field: t('Name') }) : true), - })} - id={nameId} - error={errors.name?.message} - disabled={!allowRealNameChange} - aria-required='true' - aria-invalid={errors.username ? 'true' : 'false'} - aria-describedby={`${nameId}-error ${nameId}-hint`} + (requireName && name === '' ? t('error-the-field-is-required', { field: t('Name') }) : true) }} + render={({ field }) => ( + + )} /> {errors.name && ( @@ -171,17 +172,25 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle {t('Username')} - validateUsername(username), - })} - id={usernameId} - error={errors.username?.message} - addon={} - aria-required='true' - aria-invalid={errors.username ? 'true' : 'false'} - aria-describedby={`${usernameId}-error ${usernameId}-hint`} + }} + render={({ field }) => ( + } + aria-required='true' + aria-invalid={errors.username ? 'true' : 'false'} + aria-describedby={`${usernameId}-error ${usernameId}-hint`} + /> + )} /> {errors?.username && ( @@ -195,26 +204,31 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle {t('StatusMessage')} - ( - - )} + ( + ( + + )} + /> + } /> - } + )} /> {errors?.statusText && ( @@ -227,21 +241,34 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle {t('Nickname')} - } /> + ( + } /> + )} + /> {t('Bio')} - } - aria-invalid={errors.statusText ? 'true' : 'false'} - aria-describedby={`${bioId}-error`} + ( + } + aria-invalid={errors.statusText ? 'true' : 'false'} + aria-describedby={`${bioId}-error`} + /> + )} /> {errors?.bio && ( @@ -255,18 +282,23 @@ const AccountProfileForm = (props: AllHTMLAttributes): ReactEle {t('Email')} - (validateEmail(email) ? undefined : t('error-invalid-email-address')) }, - })} - flexGrow={1} - error={errors.email?.message} - addon={} - disabled={!allowEmailChange} - aria-required='true' - aria-invalid={errors.email ? 'true' : 'false'} - aria-describedby={`${emailId}-error ${emailId}-hint`} + (validateEmail(email) ? undefined : t('error-invalid-email-address')) } }} + render={({ field }) => ( + } + disabled={!allowEmailChange} + aria-required='true' + aria-invalid={errors.email ? 'true' : 'false'} + aria-describedby={`${emailId}-error ${emailId}-hint`} + /> + )} /> {!isUserVerified && ( - + )} ); diff --git a/apps/meteor/client/views/admin/info/LicenseCard.tsx b/apps/meteor/client/views/admin/info/LicenseCard.tsx index 8865ed990a4a..bccbddaa6db7 100644 --- a/apps/meteor/client/views/admin/info/LicenseCard.tsx +++ b/apps/meteor/client/views/admin/info/LicenseCard.tsx @@ -1,6 +1,6 @@ import { ButtonGroup, Button, Skeleton } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardCol, CardTitle, CardColSection, CardColTitle, CardFooter } from '@rocket.chat/ui-client'; import { useSetModal, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -43,14 +43,14 @@ const LicenseCard = (): ReactElement => { return ( - {t('License')} - - - + {t('License')} + + + - - - {t('Features')} + + + {t('Features')} {isLoading ? ( <> @@ -67,10 +67,10 @@ const LicenseCard = (): ReactElement => { )} - - - - + + + + {isAirGapped ? ( )} - + ); }; diff --git a/apps/meteor/client/views/admin/info/UsageCard.tsx b/apps/meteor/client/views/admin/info/UsageCard.tsx index 793789c30a03..7a3b2123e5f2 100644 --- a/apps/meteor/client/views/admin/info/UsageCard.tsx +++ b/apps/meteor/client/views/admin/info/UsageCard.tsx @@ -1,7 +1,17 @@ import type { IStats } from '@rocket.chat/core-typings'; import { ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { TextSeparator, Card } from '@rocket.chat/ui-client'; +import { + Card, + CardBody, + CardCol, + CardTitle, + CardColSection, + CardColTitle, + CardFooter, + TextSeparator, + CardIcon, +} from '@rocket.chat/ui-client'; import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; @@ -29,15 +39,15 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { return ( - {t('Usage')} - - - - {t('Users')} + {t('Usage')} + + + + {t('Users')} - {t('Total')} + {t('Total')} } value={statistics.totalUsers} @@ -45,9 +55,9 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - + - {' '} + {' '} {t('Online')} } @@ -56,9 +66,9 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - + - {' '} + {' '} {t('Busy')} } @@ -67,9 +77,9 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - + - {' '} + {' '} {t('Away')} } @@ -78,34 +88,34 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - + - {' '} + {' '} {t('Offline')} } value={statistics.offlineUsers} /> - - - {t('Types_and_Distribution')} + + + {t('Types_and_Distribution')} - - - {t('Uploads')} + + + {t('Uploads')} - - - {t('Total_rooms')} + + + {t('Total_rooms')} - {t('Stats_Total_Rooms')} + {t('Stats_Total_Rooms')} } value={statistics.totalRooms} @@ -113,7 +123,7 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - {t('Stats_Total_Channels')} + {t('Stats_Total_Channels')} } value={statistics.totalChannels} @@ -121,7 +131,7 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - {t('Stats_Total_Private_Groups')} + {t('Stats_Total_Private_Groups')} } value={statistics.totalPrivateGroups} @@ -129,7 +139,7 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - {t('Stats_Total_Direct_Messages')} + {t('Stats_Total_Direct_Messages')} } value={statistics.totalDirect} @@ -137,7 +147,7 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - {t('Total_Discussions')} + {t('Total_Discussions')} } value={statistics.totalDiscussions} @@ -145,30 +155,30 @@ const UsageCard = ({ statistics, vertical }: UsageCardProps): ReactElement => { - {t('Stats_Total_Livechat_Rooms')} + {t('Stats_Total_Livechat_Rooms')} } value={statistics.totalLivechat} /> - - - {t('Total_messages')} + + + {t('Total_messages')} - - - - + + + + - + ); }; diff --git a/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx b/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx index 8bf75c3753d5..26331379a9fd 100644 --- a/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx +++ b/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx @@ -1,7 +1,7 @@ import type { ISetting } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Button, Box } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardTitle, CardFooter } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; @@ -28,13 +28,13 @@ const SettingsGroupCard = ({ id, title, description }: SettingsGroupCardProps): return ( - {t(title)} - + {t(title)} + {description && t.has(description) && } - - + + - + ); }; diff --git a/apps/meteor/client/views/home/cards/AddUsersCard.tsx b/apps/meteor/client/views/home/cards/AddUsersCard.tsx index fe36c6e6f165..551552a182d4 100644 --- a/apps/meteor/client/views/home/cards/AddUsersCard.tsx +++ b/apps/meteor/client/views/home/cards/AddUsersCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -14,15 +14,15 @@ const AddUsersCard = (): ReactElement => { return ( - {t('Add_users')} - {t('Invite_and_add_members_to_this_workspace_to_start_communicating')} - - + {t('Add_users')} + {t('Invite_and_add_members_to_this_workspace_to_start_communicating')} + + - - + + ); }; diff --git a/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx b/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx index f5421bd821e4..9e84a8ff373d 100644 --- a/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx +++ b/apps/meteor/client/views/home/cards/CreateChannelsCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation, useSetModal } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -14,13 +14,13 @@ const CreateChannelsCard = (): ReactElement => { return ( - {t('Create_channels')} - {t('Create_a_public_channel_that_new_workspace_members_can_join')} - - + {t('Create_channels')} + {t('Create_a_public_channel_that_new_workspace_members_can_join')} + + - - + + ); }; diff --git a/apps/meteor/client/views/home/cards/CustomContentCard.tsx b/apps/meteor/client/views/home/cards/CustomContentCard.tsx index 118fa659e640..0f5c40ad87f9 100644 --- a/apps/meteor/client/views/home/cards/CustomContentCard.tsx +++ b/apps/meteor/client/views/home/cards/CustomContentCard.tsx @@ -1,5 +1,5 @@ import { Box, Button, Icon, Tag } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardFooter, CardFooterWrapper } from '@rocket.chat/ui-client'; import { useRole, useSettingSetValue, useSetting, useToastMessageDispatch, useTranslation, useRoute } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -63,8 +63,8 @@ const CustomContentCard = (): ReactElement | null => { {isCustomContentBodyEmpty ? t('Homepage_Custom_Content_Default_Message') : } - - + + @@ -86,8 +86,8 @@ const CustomContentCard = (): ReactElement | null => { > {!isCustomContentOnly ? t('Show_Only_This_Content') : t('Show_default_content')} - - + + ); } diff --git a/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx b/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx index b53d6603ccac..5a3c7834b3b4 100644 --- a/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx +++ b/apps/meteor/client/views/home/cards/DesktopAppsCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -16,15 +16,15 @@ const DesktopAppsCard = (): ReactElement => { return ( - {t('Desktop_apps')} - {t('Install_rocket_chat_on_your_preferred_desktop_platform')} - - + {t('Desktop_apps')} + {t('Install_rocket_chat_on_your_preferred_desktop_platform')} + + - - + + ); }; diff --git a/apps/meteor/client/views/home/cards/DocumentationCard.tsx b/apps/meteor/client/views/home/cards/DocumentationCard.tsx index c833f7425a7b..2ae8c0618093 100644 --- a/apps/meteor/client/views/home/cards/DocumentationCard.tsx +++ b/apps/meteor/client/views/home/cards/DocumentationCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -14,13 +14,13 @@ const DocumentationCard = (): ReactElement => { return ( - {t('Documentation')} - {t('Learn_how_to_unlock_the_myriad_possibilities_of_rocket_chat')} - - + {t('Documentation')} + {t('Learn_how_to_unlock_the_myriad_possibilities_of_rocket_chat')} + + - - + + ); }; diff --git a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx index 1a105ea8d58f..c4bfcc36d7e7 100644 --- a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx +++ b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardFooter, CardFooterWrapper, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -14,13 +14,13 @@ const JoinRoomsCard = (): ReactElement => { return ( - {t('Join_rooms')} - {t('Discover_public_channels_and_teams_in_the_workspace_directory')} - - + {t('Join_rooms')} + {t('Discover_public_channels_and_teams_in_the_workspace_directory')} + + - - + + ); }; diff --git a/apps/meteor/client/views/home/cards/MobileAppsCard.tsx b/apps/meteor/client/views/home/cards/MobileAppsCard.tsx index e04097185f42..5527e8d3e0f5 100644 --- a/apps/meteor/client/views/home/cards/MobileAppsCard.tsx +++ b/apps/meteor/client/views/home/cards/MobileAppsCard.tsx @@ -1,5 +1,5 @@ import { Button } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardTitle, CardBody, CardFooterWrapper, CardFooter } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -15,14 +15,14 @@ const MobileAppsCard = (): ReactElement => { return ( - {t('Mobile_apps')} - {t('Take_rocket_chat_with_you_with_mobile_applications')} - - + {t('Mobile_apps')} + {t('Take_rocket_chat_with_you_with_mobile_applications')} + + - - + + ); }; diff --git a/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx b/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx index 92a4d8d44199..d885242f7722 100644 --- a/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx +++ b/apps/meteor/ee/client/omnichannel/reports/components/ReportCard.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Box, Skeleton, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardCol, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactNode, ComponentProps, ReactElement } from 'react'; import React from 'react'; @@ -61,7 +61,7 @@ export const ReportCard = ({ data-qa={id} aria-busy={isLoading} > - + @@ -76,9 +76,9 @@ export const ReportCard = ({ - - - + + + {isLoading && LoadingSkeleton} @@ -94,8 +94,8 @@ export const ReportCard = ({ {!isLoading && isDataFound && children} - - + + ); }; diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx index 7c4965bce5d0..0fe32a90d825 100644 --- a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx +++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardCard.tsx @@ -1,5 +1,5 @@ import { Box } from '@rocket.chat/fuselage'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardCol, CardTitle } from '@rocket.chat/ui-client'; import type { ReactElement, ReactNode } from 'react'; import React from 'react'; @@ -13,14 +13,14 @@ type EngagementDashboardCardProps = { const EngagementDashboardCard = ({ children, title = undefined }: EngagementDashboardCardProps): ReactElement => ( - {title && {title}} - - + {title && {title}} + + {children} - - + + ); diff --git a/apps/meteor/ee/client/views/admin/info/SeatsCard.tsx b/apps/meteor/ee/client/views/admin/info/SeatsCard.tsx index ddcfe312122f..b595dd9c1fae 100644 --- a/apps/meteor/ee/client/views/admin/info/SeatsCard.tsx +++ b/apps/meteor/ee/client/views/admin/info/SeatsCard.tsx @@ -1,6 +1,6 @@ import { Box, Button, ButtonGroup, Skeleton } from '@rocket.chat/fuselage'; import colors from '@rocket.chat/fuselage-tokens/colors'; -import { Card } from '@rocket.chat/ui-client'; +import { Card, CardBody, CardCol, CardFooter, CardTitle } from '@rocket.chat/ui-client'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -27,9 +27,9 @@ const SeatsCard = ({ seatsCap }: SeatsCardProps): ReactElement => { return ( - {t('Seats_usage')} - - + {t('Seats_usage')} + + {!seatsCap ? ( @@ -43,15 +43,15 @@ const SeatsCard = ({ seatsCap }: SeatsCardProps): ReactElement => { /> )} - - - + + + - + ); }; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 562908ff67a9..2e288c8dbd30 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -469,4 +469,4 @@ "installConfig": { "hoistingLimits": "workspaces" } -} \ No newline at end of file +} diff --git a/packages/ui-client/src/components/Card/Card.stories.tsx b/packages/ui-client/src/components/Card/Card.stories.tsx index 4697443e9259..56a2220219ed 100644 --- a/packages/ui-client/src/components/Card/Card.stories.tsx +++ b/packages/ui-client/src/components/Card/Card.stories.tsx @@ -1,7 +1,7 @@ import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import Card from '.'; +import { Card, CardBody, CardCol, CardColSection, CardColTitle, CardDivider, CardFooter, CardIcon, CardTitle } from '.'; import TextSeparator from '../TextSeparator'; import { UserStatus } from '../UserStatus'; @@ -9,13 +9,13 @@ export default { title: 'Components/Card', component: Card, subcomponents: { - 'Card.Title': Card.Title, - 'Card.Body': Card.Body, - 'Card.Col': Card.Col, - 'Card.Col.Section': Card.Col.Section, - 'Card.Col.Title': Card.Col.Title, - 'Card.Footer': Card.Footer, - 'Card.Divider': Card.Divider, + CardTitle, + CardBody, + CardCol, + CardColSection, + CardColTitle, + CardFooter, + CardDivider, }, parameters: { layout: 'centered', @@ -25,14 +25,14 @@ export default { export const Example: ComponentStory = () => ( - Usage - - - Users + Usage + + + Users - Total + Total } value={123} @@ -40,9 +40,9 @@ export const Example: ComponentStory = () => ( - + - {' '} + {' '} Online } @@ -51,9 +51,9 @@ export const Example: ComponentStory = () => ( - + - {' '} + {' '} Busy } @@ -62,9 +62,9 @@ export const Example: ComponentStory = () => ( - + - {' '} + {' '} Away } @@ -73,111 +73,111 @@ export const Example: ComponentStory = () => ( - + - {' '} + {' '} Offline } value={123} /> - - - Types and Distribution + + + Types and Distribution - - - Uploads + + + Uploads - - + + ); export const Single: ComponentStory = () => ( - A card - - + A card + + - A Section + A Section
A bunch of stuff
A bunch of stuff
A bunch of stuff
A bunch of stuff
- Another Section + Another Section
A bunch of stuff
A bunch of stuff
A bunch of stuff
A bunch of stuff
-
-
- + + + - +
); export const Double: ComponentStory = () => ( - A card - - + A card + + - A Section + A Section
A bunch of stuff
A bunch of stuff
A bunch of stuff
A bunch of stuff
- Another Section + Another Section
A bunch of stuff
A bunch of stuff
A bunch of stuff
A bunch of stuff
-
- - + + + - A Section + A Section - A bunch of stuff + A bunch of stuff - A bunch of stuff + A bunch of stuff - A bunch of stuff + A bunch of stuff - A bunch of stuff + A bunch of stuff - Another Section + Another Section
A bunch of stuff
A bunch of stuff
A bunch of stuff
A bunch of stuff
-
-
- + + + - +
); diff --git a/packages/ui-client/src/components/Card/index.ts b/packages/ui-client/src/components/Card/index.ts index ade5391e61d6..76df65e5a809 100644 --- a/packages/ui-client/src/components/Card/index.ts +++ b/packages/ui-client/src/components/Card/index.ts @@ -1,30 +1,10 @@ -import Card from './Card'; -import CardBody from './CardBody'; -import CardCol from './CardCol'; -import CardColSection from './CardColSection'; -import CardColTitle from './CardColTitle'; -import CardDivider from './CardDivider'; -import CardFooter from './CardFooter'; -import CardFooterWrapper from './CardFooterWrapper'; -import CardIcon from './CardIcon'; -import CardTitle from './CardTitle'; - -export const DOUBLE_COLUMN_CARD_WIDTH = 552; - -/** - * @deprecated Avoid default usage, use named imports instead - */ -export default Object.assign(Card, { - Title: CardTitle, - Body: CardBody, - Col: Object.assign(CardCol, { - Title: CardColTitle, - Section: CardColSection, - }), - Footer: CardFooter, - FooterWrapper: CardFooterWrapper, - Divider: CardDivider, - Icon: CardIcon, -}); - -export { Card, CardBody, CardCol, CardColSection, CardColTitle, CardDivider, CardFooter, CardFooterWrapper, CardIcon, CardTitle }; +export { default as Card } from './Card'; +export { default as CardBody } from './CardBody'; +export { default as CardCol } from './CardCol'; +export { default as CardColSection } from './CardColSection'; +export { default as CardColTitle } from './CardColTitle'; +export { default as CardDivider } from './CardDivider'; +export { default as CardFooter } from './CardFooter'; +export { default as CardFooterWrapper } from './CardFooterWrapper'; +export { default as CardIcon } from './CardIcon'; +export { default as CardTitle } from './CardTitle'; diff --git a/packages/ui-client/src/components/index.ts b/packages/ui-client/src/components/index.ts index 6448f37b3e68..3de4bf411a5e 100644 --- a/packages/ui-client/src/components/index.ts +++ b/packages/ui-client/src/components/index.ts @@ -7,7 +7,7 @@ export * from '../hooks/useValidatePassword'; export { default as TextSeparator } from './TextSeparator'; export * from './TooltipComponent'; export * as UserStatus from './UserStatus'; -export { default as Card } from './Card'; +export * from './Card'; export * from './Header'; export * from './MultiSelectCustom/MultiSelectCustom'; export * from './FeaturePreview/FeaturePreview'; From ba856a2a776545e764e116ecbc6160524bbdd5a8 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Mon, 11 Sep 2023 10:57:53 -0300 Subject: [PATCH 15/26] chore: remove istanbul from prod env (#30313) --- .github/actions/build-docker/action.yml | 73 +++++++++ .github/actions/meteor-build/action.yml | 129 ++++++++++++++++ .github/workflows/ci.yml | 196 +++++++----------------- apps/meteor/.babelrc | 20 ++- 4 files changed, 268 insertions(+), 150 deletions(-) create mode 100644 .github/actions/build-docker/action.yml create mode 100644 .github/actions/meteor-build/action.yml diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml new file mode 100644 index 000000000000..808b8acdcbe3 --- /dev/null +++ b/.github/actions/build-docker/action.yml @@ -0,0 +1,73 @@ +name: 'Meteor Docker' + +inputs: + CR_USER: + required: true + CR_PAT: + required: true + node-version: + required: true + description: 'Node version' + type: string + platform: + required: false + description: 'Platform' + type: string + +runs: + using: composite + + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ inputs.CR_USER }} + password: ${{ inputs.CR_PAT }} + + - name: Restore build + uses: actions/download-artifact@v3 + with: + name: build + path: /tmp/build + + - name: Unpack build + shell: bash + run: | + cd /tmp/build + tar xzf Rocket.Chat.tar.gz + rm Rocket.Chat.tar.gz + + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: ${{ inputs.node-version }} + cache-modules: true + install: true + + - run: yarn build + shell: bash + + - name: Build Docker images + shell: bash + run: | + args=(rocketchat) + + if [[ '${{ inputs.platform }}' = 'alpine' ]]; then + args+=($SERVICES_PUBLISH) + fi; + + docker compose -f docker-compose-ci.yml build "${args[@]}" + + - name: Publish Docker images to GitHub Container Registry + shell: bash + run: | + args=(rocketchat) + + if [[ '${{ inputs.platform }}' = 'alpine' ]]; then + args+=($SERVICES_PUBLISH) + fi; + + docker compose -f docker-compose-ci.yml push "${args[@]}" diff --git a/.github/actions/meteor-build/action.yml b/.github/actions/meteor-build/action.yml new file mode 100644 index 000000000000..21fec059c8de --- /dev/null +++ b/.github/actions/meteor-build/action.yml @@ -0,0 +1,129 @@ +name: 'Meteor Build' + +inputs: + coverage: + required: false + description: 'Enable coverage' + type: boolean + reset-meteor: + required: false + description: 'Reset Meteor' + type: boolean + node-version: + required: true + description: 'Node version' + type: string + +runs: + using: composite + + steps: + - name: Set Swap Space + uses: pierotofy/set-swap-space@master + with: + swap-size-gb: 4 + + - name: Setup NodeJS + uses: ./.github/actions/setup-node + with: + node-version: ${{ inputs.node-version }} + cache-modules: true + install: true + + # - name: Free disk space + # run: | + # sudo apt clean + # docker rmi $(docker image ls -aq) + # df -h + + - name: Cache vite + uses: actions/cache@v3 + with: + path: ./node_modules/.vite + key: vite-local-cache-${{ runner.OS }}-${{ hashFiles('package.json') }} + restore-keys: | + vite-local-cache-${{ runner.os }}- + + - name: Cache meteor local + uses: actions/cache@v3 + with: + path: ./apps/meteor/.meteor/local + key: meteor-local-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/versions') }} + restore-keys: | + meteor-local-cache-${{ runner.os }}- + + - name: Cache meteor + uses: actions/cache@v3 + with: + path: ~/.meteor + key: meteor-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/release') }} + restore-keys: | + meteor-cache-${{ runner.os }}- + + - name: Install Meteor + shell: bash + run: | + # Restore bin from cache + set +e + METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) + METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") + set -e + LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor + if [ -e $LAUNCHER ] + then + echo "Cached Meteor bin found, restoring it" + sudo cp "$LAUNCHER" "/usr/local/bin/meteor" + else + echo "No cached Meteor bin found." + fi + + # only install meteor if bin isn't found + command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + + - name: Versions + shell: bash + run: | + npm --versions + yarn -v + node -v + meteor --version + meteor npm --versions + meteor node -v + git version + + - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + + - name: Translation check + shell: bash + run: yarn turbo run translation-check + + - name: Reset Meteor + shell: bash + if: ${{ inputs.reset-meteor == 'true' }} + working-directory: ./apps/meteor + run: meteor reset + + - name: Build Rocket.Chat From Pull Request + shell: bash + if: startsWith(github.ref, 'refs/pull/') == true + env: + METEOR_PROFILE: 1000 + BABEL_ENV: ${{ inputs.coverage == 'true' && 'coverage' || '' }} + run: yarn build:ci -- --directory /tmp/dist + + - name: Build Rocket.Chat + shell: bash + if: startsWith(github.ref, 'refs/pull/') != true + run: yarn build:ci -- --directory /tmp/dist + + - name: Prepare build + shell: bash + run: | + cd /tmp/dist + tar czf /tmp/Rocket.Chat.tar.gz bundle + + - name: Store build + uses: actions/upload-artifact@v3 + with: + name: build + path: /tmp/Rocket.Chat.tar.gz diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70ec4dcba6ed..22250705ea58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: run: yarn build build: - name: 📦 Meteor Build + name: 📦 Meteor Build - coverage needs: [release-versions, packages-build] runs-on: ubuntu-20.04 @@ -138,111 +138,38 @@ jobs: echo "github.event_name: ${{ github.event_name }}" cat $GITHUB_EVENT_PATH - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 4 - - uses: actions/checkout@v3 - - name: Setup NodeJS - uses: ./.github/actions/setup-node + - uses: ./.github/actions/meteor-build with: node-version: ${{ needs.release-versions.outputs.node-version }} - cache-modules: true - install: true - - # - name: Free disk space - # run: | - # sudo apt clean - # docker rmi $(docker image ls -aq) - # df -h - - - name: Cache vite - uses: actions/cache@v3 - with: - path: ./node_modules/.vite - key: vite-local-cache-${{ runner.OS }}-${{ hashFiles('package.json') }} - restore-keys: | - vite-local-cache-${{ runner.os }}- - - - name: Cache meteor local - uses: actions/cache@v3 - with: - path: ./apps/meteor/.meteor/local - key: meteor-local-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/versions') }} - restore-keys: | - meteor-local-cache-${{ runner.os }}- - - - name: Cache meteor - uses: actions/cache@v3 - with: - path: ~/.meteor - key: meteor-cache-${{ runner.OS }}-${{ hashFiles('apps/meteor/.meteor/release') }} - restore-keys: | - meteor-cache-${{ runner.os }}- + coverage: true - - name: Install Meteor - run: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + build-prod: + name: 📦 Meteor Build - official + needs: [tests-done, release-versions, packages-build] + if: (github.event_name == 'release' || github.ref == 'refs/heads/develop') + runs-on: ubuntu-20.04 - - name: Versions + steps: + - name: Github Info run: | - npm --versions - yarn -v - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - - - name: Translation check - run: yarn turbo run translation-check - - - name: Reset Meteor - if: startsWith(github.ref, 'refs/tags/') == 'true' || github.ref == 'refs/heads/develop' - working-directory: ./apps/meteor - run: meteor reset - - - name: Build Rocket.Chat From Pull Request - if: startsWith(github.ref, 'refs/pull/') == true - env: - METEOR_PROFILE: 1000 - run: yarn build:ci -- --directory /tmp/dist - - - name: Build Rocket.Chat - if: startsWith(github.ref, 'refs/pull/') != true - run: yarn build:ci -- --directory /tmp/dist + echo "GITHUB_ACTION: $GITHUB_ACTION" + echo "GITHUB_ACTOR: $GITHUB_ACTOR" + echo "GITHUB_REF: $GITHUB_REF" + echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" + echo "github.event_name: ${{ github.event_name }}" + cat $GITHUB_EVENT_PATH - - name: Prepare build - run: | - cd /tmp/dist - tar czf /tmp/Rocket.Chat.tar.gz bundle + - uses: actions/checkout@v3 - - name: Store build - uses: actions/upload-artifact@v3 + - uses: ./.github/actions/meteor-build with: - name: build - path: /tmp/Rocket.Chat.tar.gz + node-version: ${{ needs.release-versions.outputs.node-version }} + coverage: false - build-gh-docker: + build-gh-docker-coverage: name: 🚢 Build Docker Images for Testing needs: [build, release-versions] if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') @@ -262,56 +189,39 @@ jobs: steps: - uses: actions/checkout@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ secrets.CR_USER }} - password: ${{ secrets.CR_PAT }} - - - name: Restore build - uses: actions/download-artifact@v3 - with: - name: build - path: /tmp/build - - - name: Unpack build - run: | - cd /tmp/build - tar xzf Rocket.Chat.tar.gz - rm Rocket.Chat.tar.gz - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - - - name: Setup NodeJS - uses: ./.github/actions/setup-node + - uses: ./.github/actions/build-docker with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} node-version: ${{ needs.release-versions.outputs.node-version }} - cache-modules: true - install: true - - - run: yarn build + platform: ${{ matrix.platform }} - - name: Build Docker images - run: | - args=(rocketchat) - - if [[ '${{ matrix.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + build-gh-docker: + name: 🚢 Build Docker Images for Production + needs: [build-prod, release-versions] + runs-on: ubuntu-20.04 - docker compose -f docker-compose-ci.yml build "${args[@]}" + env: + RC_DOCKERFILE: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-dockerfile-alpine || needs.release-versions.outputs.rc-dockerfile }} + RC_DOCKER_TAG: ${{ matrix.platform == 'alpine' && needs.release-versions.outputs.rc-docker-tag-alpine || needs.release-versions.outputs.rc-docker-tag }} + DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} + LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} + SERVICES_PUBLISH: 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service' - - name: Publish Docker images to GitHub Container Registry - run: | - args=(rocketchat) + strategy: + fail-fast: false + matrix: + platform: ['official', 'alpine'] - if [[ '${{ matrix.platform }}' = 'alpine' ]]; then - args+=($SERVICES_PUBLISH) - fi; + steps: + - uses: actions/checkout@v3 - docker compose -f docker-compose-ci.yml push "${args[@]}" + - uses: ./.github/actions/build-docker + with: + CR_USER: ${{ secrets.CR_USER }} + CR_PAT: ${{ secrets.CR_PAT }} + node-version: ${{ needs.release-versions.outputs.node-version }} + platform: ${{ matrix.platform }} - name: Rename official Docker tag to GitHub Container Registry if: matrix.platform == 'official' @@ -342,7 +252,7 @@ jobs: test-api: name: 🔨 Test API (CE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -361,7 +271,7 @@ jobs: test-ui: name: 🔨 Test UI (CE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -387,7 +297,7 @@ jobs: test-api-ee: name: 🔨 Test API (EE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -409,7 +319,7 @@ jobs: test-ui-ee: name: 🔨 Test UI (EE) - needs: [checks, build-gh-docker, release-versions] + needs: [checks, build-gh-docker-coverage, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: @@ -449,7 +359,7 @@ jobs: name: 🚀 Publish build and update our registry runs-on: ubuntu-20.04 if: github.event_name == 'release' || github.ref == 'refs/heads/develop' - needs: [tests-done, release-versions] + needs: [build-gh-docker, release-versions] steps: - uses: actions/checkout@v3 diff --git a/apps/meteor/.babelrc b/apps/meteor/.babelrc index a8c20b400ca5..382b93318fab 100644 --- a/apps/meteor/.babelrc +++ b/apps/meteor/.babelrc @@ -1,9 +1,15 @@ { - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ], - "plugins": [ - "babel-plugin-istanbul" - ] + "presets": ["@babel/preset-env", "@babel/preset-react"], + "env": { + "coverage": { + "plugins": [ + [ + "istanbul", + { + "exclude": ["**/*.spec.js", "**/*.test.js"] + } + ] + ] + } + } } From be98f53493e753fb1c3cedefeb8589c2d744ab9e Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:58:28 +0400 Subject: [PATCH 16/26] test: More Business Hour tests surrounding user activation/deactivation (#30343) Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com> --- .../lib/server/functions/getFullUserData.ts | 1 + apps/meteor/tests/data/users.helper.js | 12 +++ apps/meteor/tests/end-to-end/api/01-users.js | 20 ++++- .../api/livechat/19-business-hours.ts | 85 +++++++++++++++++-- 4 files changed, 110 insertions(+), 8 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/getFullUserData.ts b/apps/meteor/app/lib/server/functions/getFullUserData.ts index c04148c07ba8..0703b24d9210 100644 --- a/apps/meteor/app/lib/server/functions/getFullUserData.ts +++ b/apps/meteor/app/lib/server/functions/getFullUserData.ts @@ -21,6 +21,7 @@ const defaultFields = { avatarETag: 1, extension: 1, federated: 1, + statusLivechat: 1, } as const; const fullFields = { diff --git a/apps/meteor/tests/data/users.helper.js b/apps/meteor/tests/data/users.helper.js index 9dac1772dcdc..92425902cb5b 100644 --- a/apps/meteor/tests/data/users.helper.js +++ b/apps/meteor/tests/data/users.helper.js @@ -77,3 +77,15 @@ export const getMe = (overrideCredential = credentials) => resolve(res.body); }); }); + +export const setUserActiveStatus = (userId, activeStatus = true) => + new Promise((resolve) => { + request + .post(api('users.setActiveStatus')) + .set(credentials) + .send({ + userId, + activeStatus, + }) + .end(resolve); + }); diff --git a/apps/meteor/tests/end-to-end/api/01-users.js b/apps/meteor/tests/end-to-end/api/01-users.js index 130f365c96c5..d99fa68a036f 100644 --- a/apps/meteor/tests/end-to-end/api/01-users.js +++ b/apps/meteor/tests/end-to-end/api/01-users.js @@ -23,7 +23,7 @@ import { imgURL } from '../../data/interactions.js'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom } from '../../data/rooms.helper'; import { adminEmail, preferences, password, adminUsername } from '../../data/user'; -import { createUser, login, deleteUser, getUserStatus } from '../../data/users.helper.js'; +import { createUser, login, deleteUser, getUserStatus, getUserByUsername } from '../../data/users.helper.js'; async function createChannel(userCredentials, name) { const res = await request.post(api('channels.create')).set(userCredentials).send({ @@ -3447,6 +3447,24 @@ describe('[Users]', function () { .end(done); }); }); + it('users should retain their roles when they are deactivated', async () => { + const testUser = await createUser({ roles: ['user', 'livechat-agent'] }); + + await request + .post(api('users.setActiveStatus')) + .set(credentials) + .send({ + activeStatus: false, + userId: testUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + const user = await getUserByUsername(testUser.username); + expect(user).to.have.property('roles'); + expect(user.roles).to.be.an('array').of.length(2); + expect(user.roles).to.include('user', 'livechat-agent'); + }); }); describe('[/users.deactivateIdle]', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 9085e1cd388d..0585c20bf127 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -22,12 +22,12 @@ import { getDepartmentById, deleteDepartment, } from '../../../data/livechat/department'; -import { createAgent, makeAgentAvailable } from '../../../data/livechat/rooms'; +import { createAgent, createManager, makeAgentAvailable } from '../../../data/livechat/rooms'; import { removeAgent } from '../../../data/livechat/users'; import { removePermissionFromAllRoles, restorePermissionToRoles, updateSetting, updateEESetting } from '../../../data/permissions.helper'; import type { IUserCredentialsHeader } from '../../../data/user'; import { password } from '../../../data/user'; -import { createUser, deleteUser, getMe, login } from '../../../data/users.helper'; +import { setUserActiveStatus, createUser, deleteUser, getMe, getUserByUsername, login } from '../../../data/users.helper'; import { IS_EE } from '../../../e2e/config/constants'; describe('LIVECHAT - business hours', function () { @@ -249,7 +249,7 @@ describe('LIVECHAT - business hours', function () { }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations upon creation', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Business Hour created', () => { let defaultBusinessHour: ILivechatBusinessHour; before(async () => { @@ -288,7 +288,7 @@ describe('LIVECHAT - business hours', function () { // and "dep2" and both these depts are connected to same BH, then in this case after // archiving "dep1", we'd still need to BH within this user's cache since he's part of // "dep2" which is linked to BH - (IS_EE ? describe : describe.skip)('[EE] BH operations post department archiving', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department archived', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -434,7 +434,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations post department disablement', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department disabled', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -578,7 +578,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - (IS_EE ? describe : describe.skip)('[EE] BH operations post department removal', () => { + (IS_EE ? describe : describe.skip)('[EE][BH] On Department removed', () => { let defaultBusinessHour: ILivechatBusinessHour; let customBusinessHour: ILivechatBusinessHour; let deptLinkedToCustomBH: ILivechatDepartment; @@ -702,7 +702,7 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agentLinkedToDept.user); }); }); - describe('BH behavior upon new agent creation/deletion', () => { + describe('[CE][BH] On Agent created/removed', () => { let defaultBH: ILivechatBusinessHour; let agent: ILivechatAgent; let agentCredentials: IUserCredentialsHeader; @@ -782,4 +782,75 @@ describe('LIVECHAT - business hours', function () { await deleteUser(agent._id); }); }); + + describe('[CE][BH] On Agent deactivated/activated', () => { + let defaultBH: ILivechatBusinessHour; + let agent: ILivechatAgent; + + before(async () => { + await updateSetting('Livechat_enable_business_hours', true); + await updateEESetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.SINGLE); + // wait for callbacks to run + await sleep(2000); + + defaultBH = await getDefaultBusinessHour(); + await openOrCloseBusinessHour(defaultBH, true); + + agent = await createUser(); + await createAgent(agent.username); + }); + + after(async () => { + await deleteUser(agent); + await updateSetting('Livechat_enable_business_hours', false); + }); + + it('should verify if agent becomes unavailable to take chats when user is deactivated', async () => { + await setUserActiveStatus(agent._id, false); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + }); + + it('should verify if agent becomes available to take chats when user is activated, if business hour is active', async () => { + await openOrCloseBusinessHour(defaultBH, true); + + await setUserActiveStatus(agent._id, true); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.AVAILABLE); + }); + it('should verify if agent becomes unavailable to take chats when user is activated, if business hour is inactive', async () => { + await openOrCloseBusinessHour(defaultBH, false); + + await setUserActiveStatus(agent._id, false); + await setUserActiveStatus(agent._id, true); + + const latestAgent = await getUserByUsername(agent.username); + + expect(latestAgent).to.be.an('object'); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + }); + it('should verify if managers are not able to make deactivated agents available', async () => { + await createManager(); + + await setUserActiveStatus(agent._id, false); + + const response = await request + .post(api('livechat/agent.status')) + .set(credentials) + .send({ + status: 'available', + agentId: agent._id, + }) + .expect(400); + + expect(response.body).to.have.property('success', false); + expect(response.body).to.have.property('error', 'error-user-deactivated'); + }); + }); }); From bb3a0d4222061f629c97933d2ab6135be356d04d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 11 Sep 2023 17:41:24 -0300 Subject: [PATCH 17/26] chore: add subscription metrics to ddp-streamer (#30289) --- ee/apps/ddp-streamer/src/DDPStreamer.ts | 11 +++++++++++ ee/apps/ddp-streamer/src/Server.ts | 11 +++++++++++ packages/core-services/src/types/IBroker.ts | 1 + 3 files changed, 23 insertions(+) diff --git a/ee/apps/ddp-streamer/src/DDPStreamer.ts b/ee/apps/ddp-streamer/src/DDPStreamer.ts index 868ad8fec202..bccb35d2b326 100644 --- a/ee/apps/ddp-streamer/src/DDPStreamer.ts +++ b/ee/apps/ddp-streamer/src/DDPStreamer.ts @@ -72,6 +72,15 @@ export class DDPStreamer extends ServiceClass { return; } + metrics.register({ + name: 'rocketchat_subscription', + type: 'histogram', + labelNames: ['subscription'], + description: 'Client subscriptions to Rocket.Chat', + unit: 'millisecond', + quantiles: true, + }); + metrics.register({ name: 'users_connected', type: 'gauge', @@ -86,6 +95,8 @@ export class DDPStreamer extends ServiceClass { description: 'Users logged by streamer', }); + server.setMetrics(metrics); + server.on(DDP_EVENTS.CONNECTED, () => { metrics.increment('users_connected', { nodeID }, 1); }); diff --git a/ee/apps/ddp-streamer/src/Server.ts b/ee/apps/ddp-streamer/src/Server.ts index 01c7c63511ff..af083621230d 100644 --- a/ee/apps/ddp-streamer/src/Server.ts +++ b/ee/apps/ddp-streamer/src/Server.ts @@ -1,5 +1,6 @@ import { EventEmitter } from 'events'; +import type { IServiceMetrics } from '@rocket.chat/core-services'; import { MeteorService, isMeteorError, MeteorError } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import ejson from 'ejson'; @@ -38,6 +39,8 @@ export class Server extends EventEmitter { private _methods = new Map(); + private metrics?: IServiceMetrics; + public readonly id = uuidv1(); serialize = ejson.stringify; @@ -52,6 +55,10 @@ export class Server extends EventEmitter { return ejson.parse(payload); }; + setMetrics(metrics: IServiceMetrics): void { + this.metrics = metrics; + } + async call(client: Client, packet: IPacket): Promise { // if client is not connected we don't need to do anything if (client.ws.readyState !== WebSocket.OPEN) { @@ -103,9 +110,13 @@ export class Server extends EventEmitter { throw new MeteorError(404, `Subscription '${packet.name}' not found`); } + const end = this.metrics?.timer('rocketchat_subscription', { subscription: packet.name }); + const publication = new Publication(client, packet, this); const [eventName, options] = packet.params; await fn.call(publication, eventName, options); + + end?.(); } catch (err: unknown) { return this.nosub(client, packet, handleInternalException(err, 'Subscription error')); } diff --git a/packages/core-services/src/types/IBroker.ts b/packages/core-services/src/types/IBroker.ts index 8647d04a56dc..4bd48afef0ff 100644 --- a/packages/core-services/src/types/IBroker.ts +++ b/packages/core-services/src/types/IBroker.ts @@ -27,6 +27,7 @@ export type BaseMetricOptions = { labelNames?: Array; unit?: string; aggregator?: string; + [key: string]: unknown; }; export interface IServiceMetrics { From 258e9b338172b24066c8baa825ed56085ab05194 Mon Sep 17 00:00:00 2001 From: Heitor Tanoue <68477006+heitortanoue@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:57:53 -0300 Subject: [PATCH 18/26] chore: move team mentions to ce (#30175) Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- .../server/getMentionedTeamMembers.ts | 25 ++++++++++++ apps/meteor/app/mentions/server/index.ts | 3 +- apps/meteor/app/mentions/server/server.ts | 24 +++++++++--- apps/meteor/ee/app/license/server/bundles.ts | 2 - .../teams-mention/server/EEMentionQueries.js | 19 --------- .../app/teams-mention/server/EESpotlight.js | 34 ---------------- .../ee/app/teams-mention/server/index.ts | 39 ------------------- apps/meteor/ee/server/index.ts | 1 - apps/meteor/server/lib/spotlight.js | 30 ++++++++++++-- 9 files changed, 72 insertions(+), 105 deletions(-) create mode 100644 apps/meteor/app/mentions/server/getMentionedTeamMembers.ts delete mode 100644 apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js delete mode 100644 apps/meteor/ee/app/teams-mention/server/EESpotlight.js delete mode 100644 apps/meteor/ee/app/teams-mention/server/index.ts diff --git a/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts b/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts new file mode 100644 index 000000000000..1d36400296b4 --- /dev/null +++ b/apps/meteor/app/mentions/server/getMentionedTeamMembers.ts @@ -0,0 +1,25 @@ +import { Team } from '@rocket.chat/core-services'; +import type { IMessage } from '@rocket.chat/core-typings'; + +import { callbacks } from '../../../lib/callbacks'; + +interface IExtraDataForNotification { + userMentions: any[]; + otherMentions: any[]; + message: IMessage; +} + +callbacks.add('beforeGetMentions', async (mentionIds: string[], extra?: IExtraDataForNotification) => { + const { otherMentions } = extra ?? {}; + + const teamIds = otherMentions?.filter(({ type }) => type === 'team').map(({ _id }) => _id); + + if (!teamIds?.length) { + return mentionIds; + } + + const members = await Team.getMembersByTeamIds(teamIds, { projection: { userId: 1 } }); + mentionIds.push(...new Set(members.map(({ userId }) => userId).filter((userId) => !mentionIds.includes(userId)))); + + return mentionIds; +}); diff --git a/apps/meteor/app/mentions/server/index.ts b/apps/meteor/app/mentions/server/index.ts index 474d41a439e1..a04af05b9db1 100644 --- a/apps/meteor/app/mentions/server/index.ts +++ b/apps/meteor/app/mentions/server/index.ts @@ -1,2 +1,3 @@ -import './server'; +import './getMentionedTeamMembers'; import './methods/getUserMentionsByChannel'; +import './server'; diff --git a/apps/meteor/app/mentions/server/server.ts b/apps/meteor/app/mentions/server/server.ts index 5eb70aae4656..a5b0f89526a2 100644 --- a/apps/meteor/app/mentions/server/server.ts +++ b/apps/meteor/app/mentions/server/server.ts @@ -1,5 +1,5 @@ -import { api } from '@rocket.chat/core-services'; -import type { IUser, IRoom } from '@rocket.chat/core-typings'; +import { api, Team } from '@rocket.chat/core-services'; +import type { IUser, IRoom, ITeam } from '@rocket.chat/core-typings'; import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -9,16 +9,28 @@ import { settings } from '../../settings/server'; import MentionsServer from './Mentions'; export class MentionQueries { - async getUsers(usernames: string[]): Promise<(Pick & { type: 'user' })[]> { + async getUsers( + usernames: string[], + ): Promise<((Pick & { type: 'user' }) | (Pick & { type: 'team' }))[]> { + const uniqueUsernames = [...new Set(usernames)]; + const teams = await Team.listByNames(uniqueUsernames, { projection: { name: 1 } }); + const users = await Users.find( - { username: { $in: [...new Set(usernames)] } }, + { username: { $in: uniqueUsernames } }, { projection: { _id: true, username: true, name: 1 } }, ).toArray(); - return users.map((user) => ({ + const taggedUsers = users.map((user) => ({ ...user, - type: 'user', + type: 'user' as const, + })); + + const taggedTeams = teams.map((team) => ({ + ...team, + type: 'team' as const, })); + + return [...taggedUsers, ...taggedTeams]; } async getUser(userId: string): Promise { diff --git a/apps/meteor/ee/app/license/server/bundles.ts b/apps/meteor/ee/app/license/server/bundles.ts index 507283b3e60f..70f9d7b5a653 100644 --- a/apps/meteor/ee/app/license/server/bundles.ts +++ b/apps/meteor/ee/app/license/server/bundles.ts @@ -8,7 +8,6 @@ export type BundleFeature = | 'engagement-dashboard' | 'push-privacy' | 'scalability' - | 'teams-mention' | 'saml-enterprise' | 'device-management' | 'oauth-enterprise' @@ -32,7 +31,6 @@ const bundles: IBundle = { 'engagement-dashboard', 'push-privacy', 'scalability', - 'teams-mention', 'saml-enterprise', 'oauth-enterprise', 'device-management', diff --git a/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js b/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js deleted file mode 100644 index 020a90365d26..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/EEMentionQueries.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; - -export const MentionQueriesEnterprise = { - async getUsers(sup, usernames) { - const uniqueUsernames = [...new Set(usernames)]; - const teams = await Team.listByNames(uniqueUsernames, { projection: { name: 1 } }); - - if (!teams?.length) { - return sup(usernames); - } - - return teams - .map((team) => ({ - ...team, - type: 'team', - })) - .concat(sup(usernames)); - }, -}; diff --git a/apps/meteor/ee/app/teams-mention/server/EESpotlight.js b/apps/meteor/ee/app/teams-mention/server/EESpotlight.js deleted file mode 100644 index 83829a759bba..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/EESpotlight.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; - -export const SpotlightEnterprise = { - mapTeams(_, teams) { - return teams.map((t) => { - t.isTeam = true; - t.username = t.name; - t.status = 'online'; - return t; - }); - }, - - async _searchTeams(_, userId, { text, options, users, mentions }) { - if (!mentions) { - return users; - } - - options.limit -= users.length; - - if (options.limit <= 0) { - return users; - } - - const teamOptions = { ...options, projection: { name: 1, type: 1 } }; - const teams = await Team.search(userId, text, teamOptions); - users.push(...this.mapTeams(teams)); - - return users; - }, - - async _performExtraUserSearches(_, userId, searchParams) { - return this._searchTeams(userId, searchParams); - }, -}; diff --git a/apps/meteor/ee/app/teams-mention/server/index.ts b/apps/meteor/ee/app/teams-mention/server/index.ts deleted file mode 100644 index 631610e4c6d8..000000000000 --- a/apps/meteor/ee/app/teams-mention/server/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Team } from '@rocket.chat/core-services'; -import type { ITeamMember, IMessage } from '@rocket.chat/core-typings'; - -import { MentionQueries } from '../../../../app/mentions/server/server'; -import { callbacks } from '../../../../lib/callbacks'; -import { Spotlight } from '../../../../server/lib/spotlight'; -import { onLicense } from '../../license/server'; -import { overwriteClassOnLicense } from '../../license/server/license'; -import { MentionQueriesEnterprise } from './EEMentionQueries'; -import { SpotlightEnterprise } from './EESpotlight'; - -interface IExtraDataForNotification { - userMentions: any[]; - otherMentions: any[]; - message: IMessage; -} - -await onLicense('teams-mention', async () => { - // Override spotlight with EE version - await overwriteClassOnLicense('teams-mention', Spotlight, SpotlightEnterprise); - await overwriteClassOnLicense('teams-mention', MentionQueries, MentionQueriesEnterprise); - - callbacks.add('beforeGetMentions', async (mentionIds: string[], extra?: IExtraDataForNotification) => { - const { otherMentions } = extra ?? {}; - - const teamIds = otherMentions?.filter(({ type }) => type === 'team').map(({ _id }) => _id); - - if (!teamIds?.length) { - return mentionIds; - } - - const members: ITeamMember[] = await Team.getMembersByTeamIds(teamIds, { projection: { userId: 1 } }); - mentionIds.push( - ...new Set(members.map(({ userId }: { userId: string }) => userId).filter((userId: string) => !mentionIds.includes(userId))), - ); - - return mentionIds; - }); -}); diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 2e526776c772..9b56239ad046 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -8,7 +8,6 @@ import '../app/livechat-enterprise/server/index'; import '../app/message-read-receipt/server/index'; import '../app/voip-enterprise/server/index'; import '../app/settings/server/index'; -import '../app/teams-mention/server/index'; import './api'; import './requestSeatsRoute'; import './configuration/index'; diff --git a/apps/meteor/server/lib/spotlight.js b/apps/meteor/server/lib/spotlight.js index 38dc1b873878..ae38c903cbe2 100644 --- a/apps/meteor/server/lib/spotlight.js +++ b/apps/meteor/server/lib/spotlight.js @@ -1,3 +1,4 @@ +import { Team } from '@rocket.chat/core-services'; import { Users, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -133,8 +134,31 @@ export class Spotlight { } } - async _performExtraUserSearches(/* userId, searchParams */) { - // Overwrite this method to include extra searches + mapTeams(teams) { + return teams.map((t) => { + t.isTeam = true; + t.username = t.name; + t.status = 'online'; + return t; + }); + } + + async _searchTeams(userId, { text, options, users, mentions }) { + if (!mentions) { + return users; + } + + options.limit -= users.length; + + if (options.limit <= 0) { + return users; + } + + const teamOptions = { ...options, projection: { name: 1, type: 1 } }; + const teams = await Team.search(userId, text, teamOptions); + users.push(...this.mapTeams(teams)); + + return users; } async searchUsers({ userId, rid, text, usernames, mentions }) { @@ -245,7 +269,7 @@ export class Spotlight { return users; } - if (await this._performExtraUserSearches(userId, searchParams)) { + if (await this._searchTeams(userId, searchParams)) { return users; } From f556518fa1300b91bb5b035e463d80ff8556ce55 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 11 Sep 2023 21:24:29 -0300 Subject: [PATCH 19/26] fix: Consider only sessions from last few days for SAU/MAU (#30354) --- .changeset/strong-laws-pump.md | 8 + .../app/statistics/server/lib/SAUMonitor.ts | 38 +--- apps/meteor/server/models/raw/Sessions.ts | 205 +++++++++--------- .../src/models/ISessionsModel.ts | 4 + 4 files changed, 129 insertions(+), 126 deletions(-) create mode 100644 .changeset/strong-laws-pump.md diff --git a/.changeset/strong-laws-pump.md b/.changeset/strong-laws-pump.md new file mode 100644 index 000000000000..a4afefd65316 --- /dev/null +++ b/.changeset/strong-laws-pump.md @@ -0,0 +1,8 @@ +--- +'@rocket.chat/model-typings': patch +'@rocket.chat/meteor': patch +--- + +Change SAU aggregation to consider only sessions from few days ago instead of the whole past. + +This is particularly important for large workspaces in case the cron job did not run for some time, in that case the amount of sessions would accumulate and the aggregation would take a long time to run. diff --git a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts index 79ce688cffd3..b3aa68337106 100644 --- a/apps/meteor/app/statistics/server/lib/SAUMonitor.ts +++ b/apps/meteor/app/statistics/server/lib/SAUMonitor.ts @@ -318,33 +318,19 @@ export class SAUMonitorClass { return; } - logger.info('[aggregate] - Aggregating data.'); - - const date = new Date(); - date.setDate(date.getDate() - 0); // yesterday - const yesterday = getDateObj(date); - - for await (const record of aggregates.dailySessionsOfYesterday(Sessions.col, yesterday)) { - await Sessions.updateOne( - { _id: `${record.userId}-${record.year}-${record.month}-${record.day}` }, - { $set: record }, - { upsert: true }, - ); + const today = new Date(); + + // get sessions from 3 days ago to make sure even if a few cron jobs were skipped, we still have the data + const threeDaysAgo = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 3, 0, 0, 0, 0); + + const period = { start: getDateObj(threeDaysAgo), end: getDateObj(today) }; + + logger.info({ msg: '[aggregate] - Aggregating data.', period }); + + for await (const record of aggregates.dailySessions(Sessions.col, period)) { + await Sessions.updateDailySessionById(`${record.userId}-${record.year}-${record.month}-${record.day}`, record); } - await Sessions.updateMany( - { - type: 'session', - year: { $lte: yesterday.year }, - month: { $lte: yesterday.month }, - day: { $lte: yesterday.day }, - }, - { - $set: { - type: 'computed-session', - _computedAt: new Date(), - }, - }, - ); + await Sessions.updateAllSessionsByDateToComputed(period); } } diff --git a/apps/meteor/server/models/raw/Sessions.ts b/apps/meteor/server/models/raw/Sessions.ts index 68d5149232ed..c02fc8b5de99 100644 --- a/apps/meteor/server/models/raw/Sessions.ts +++ b/apps/meteor/server/models/raw/Sessions.ts @@ -167,9 +167,9 @@ const getProjectionByFullDate = (): { day: string; month: string; year: string } }); export const aggregates = { - dailySessionsOfYesterday( + dailySessions( collection: Collection, - { year, month, day }: DestructuredDate, + { start, end }: DestructuredRange, ): AggregationCursor< Pick & { time: number; @@ -178,115 +178,101 @@ export const aggregates = { _computedAt: string; } > { - return collection.aggregate< - Pick & { - time: number; - sessions: number; - devices: ISession['device'][]; - _computedAt: string; - } - >( - [ - { - $match: { - userId: { $exists: true }, - lastActivityAt: { $exists: true }, - device: { $exists: true }, - type: 'session', - $or: [ - { - year: { $lt: year }, - }, - { - year, - month: { $lt: month }, - }, - { - year, - month, - day: { $lte: day }, - }, - ], - }, - }, - { - $project: { - userId: 1, - device: 1, - day: 1, - month: 1, - year: 1, - mostImportantRole: 1, - time: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } }, - }, - }, - { - $match: { - time: { $gt: 0 }, - }, + const pipeline = [ + { + $match: { + userId: { $exists: true }, + lastActivityAt: { $exists: true }, + device: { $exists: true }, + type: 'session', + ...matchBasedOnDate(start, end), }, - { - $group: { - _id: { - userId: '$userId', - device: '$device', - day: '$day', - month: '$month', - year: '$year', - }, - mostImportantRole: { $first: '$mostImportantRole' }, - time: { $sum: '$time' }, - sessions: { $sum: 1 }, - }, + }, + { + $project: { + userId: 1, + device: 1, + day: 1, + month: 1, + year: 1, + mostImportantRole: 1, + time: { $trunc: { $divide: [{ $subtract: ['$lastActivityAt', '$loginAt'] }, 1000] } }, }, - { - $sort: { - time: -1, - }, + }, + { + $match: { + time: { $gt: 0 }, }, - { - $group: { - _id: { - userId: '$_id.userId', - day: '$_id.day', - month: '$_id.month', - year: '$_id.year', - }, - mostImportantRole: { $first: '$mostImportantRole' }, - time: { $sum: '$time' }, - sessions: { $sum: '$sessions' }, - devices: { - $push: { - sessions: '$sessions', - time: '$time', - device: '$_id.device', - }, - }, + }, + { + $group: { + _id: { + userId: '$userId', + device: '$device', + day: '$day', + month: '$month', + year: '$year', }, + mostImportantRole: { $first: '$mostImportantRole' }, + time: { $sum: '$time' }, + sessions: { $sum: 1 }, }, - { - $sort: { - _id: 1, - }, + }, + { + $sort: { + time: -1, }, - { - $project: { - _id: 0, - type: { $literal: 'user_daily' }, - _computedAt: { $literal: new Date() }, + }, + { + $group: { + _id: { + userId: '$_id.userId', day: '$_id.day', month: '$_id.month', year: '$_id.year', - userId: '$_id.userId', - mostImportantRole: 1, - time: 1, - sessions: 1, - devices: 1, + }, + mostImportantRole: { $first: '$mostImportantRole' }, + time: { $sum: '$time' }, + sessions: { $sum: '$sessions' }, + devices: { + $push: { + sessions: '$sessions', + time: '$time', + device: '$_id.device', + }, }, }, - ], - { allowDiskUse: true }, - ); + }, + { + $sort: { + _id: 1, + }, + }, + { + $project: { + _id: 0, + type: { $literal: 'user_daily' }, + _computedAt: { $literal: new Date() }, + day: '$_id.day', + month: '$_id.month', + year: '$_id.year', + userId: '$_id.userId', + mostImportantRole: 1, + time: 1, + sessions: 1, + devices: 1, + }, + }, + ]; + + return collection.aggregate< + Pick & { + time: number; + sessions: number; + devices: ISession['device'][]; + _computedAt: string; + } + >(pipeline, { allowDiskUse: true }); }, async getUniqueUsersOfYesterday( @@ -1616,4 +1602,23 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { return this.col.bulkWrite(ops, { ordered: false }); } + + async updateDailySessionById(_id: ISession['_id'], record: Partial): Promise { + return this.updateOne({ _id }, { $set: record }, { upsert: true }); + } + + async updateAllSessionsByDateToComputed({ start, end }: DestructuredRange): Promise { + return this.updateMany( + { + type: 'session', + ...matchBasedOnDate(start, end), + }, + { + $set: { + type: 'computed-session', + _computedAt: new Date(), + }, + }, + ); + } } diff --git a/packages/model-typings/src/models/ISessionsModel.ts b/packages/model-typings/src/models/ISessionsModel.ts index cebe0c861d3f..1e6a36fd6f78 100644 --- a/packages/model-typings/src/models/ISessionsModel.ts +++ b/packages/model-typings/src/models/ISessionsModel.ts @@ -145,4 +145,8 @@ export interface ISessionsModel extends IBaseModel { }): Promise; createBatch(sessions: OptionalId[]): Promise; + + updateDailySessionById(_id: ISession['_id'], record: Partial): Promise; + + updateAllSessionsByDateToComputed({ start, end }: DestructuredRange): Promise; } From f37e404cb3226b3971e305722af1d75097790e82 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Tue, 12 Sep 2023 06:05:48 +0530 Subject: [PATCH 20/26] chore: bump fuselage (#30357) --- yarn.lock | 88 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/yarn.lock b/yarn.lock index 249995955936..100b3ba60fe7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7928,6 +7928,19 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-in-js@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.23" + dependencies: + "@emotion/hash": ^0.9.0 + "@rocket.chat/css-supports": ~0.31.26-dev.23 + "@rocket.chat/memo": ~0.31.26-dev.23 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.26-dev.23 + stylis: ~4.1.3 + checksum: 6d71bd0f232c8ea3fc2711347064ddd14925b1c2b8713f6d7649b98679455029a53ee41d08b98d010da3ea4789afa21a15901a92efef61dee7b32d6965157445 + languageName: node + linkType: hard + "@rocket.chat/css-supports@npm:~0.31.26-dev.19": version: 0.31.26-dev.19 resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.19" @@ -7937,6 +7950,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-supports@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/memo": ~0.31.26-dev.23 + checksum: a4f25562df67214b1c92c85a1cd16eb03fc2aea385f48cdde42ad0053b9e03a92ca9e3486d1387c7a31cf68f47fa888825f31acae8f4700ee2b9f03495286a12 + languageName: node + linkType: hard + "@rocket.chat/ddp-client@workspace:^, @rocket.chat/ddp-client@workspace:ee/packages/ddp-client": version: 0.0.0-use.local resolution: "@rocket.chat/ddp-client@workspace:ee/packages/ddp-client" @@ -8142,13 +8164,20 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:next, @rocket.chat/fuselage-tokens@npm:~0.32.0-dev.379": +"@rocket.chat/fuselage-tokens@npm:next": version: 0.32.0-dev.379 resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.379" checksum: c5cf40295c4ae1a5918651b9e156629d6400d5823b8cf5f81a14c66da986a9302d79392b45e991c2fc37aad9633f3d8e2f7f29c68969592340b05968265244e6 languageName: node linkType: hard +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.383": + version: 0.32.0-dev.383 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.383" + checksum: bd3504fa6a7ce4ed6fc91246c4c8a4e3e3da8bc5e2c5590e7f913bc1fd6f08896aa4a6c4b1d01dccf78267ade9ad5a831c788cb17a4eb744deefb45032a34894 + languageName: node + linkType: hard + "@rocket.chat/fuselage-ui-kit@workspace:^, @rocket.chat/fuselage-ui-kit@workspace:packages/fuselage-ui-kit, @rocket.chat/fuselage-ui-kit@workspace:~": version: 0.0.0-use.local resolution: "@rocket.chat/fuselage-ui-kit@workspace:packages/fuselage-ui-kit" @@ -8195,9 +8224,9 @@ __metadata: "@rocket.chat/icons": "*" "@rocket.chat/prettier-config": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 "@rocket.chat/ui-kit": "*" - "@rocket.chat/ui-video-conf": 1.0.3 + "@rocket.chat/ui-video-conf": 1.0.4 "@tanstack/react-query": "*" react: "*" react-dom: "*" @@ -8205,14 +8234,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.429 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.429" - dependencies: - "@rocket.chat/css-in-js": ~0.31.26-dev.19 - "@rocket.chat/css-supports": ~0.31.26-dev.19 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.379 - "@rocket.chat/memo": ~0.31.26-dev.19 - "@rocket.chat/styled": ~0.31.26-dev.19 + version: 0.32.0-dev.433 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.433" + dependencies: + "@rocket.chat/css-in-js": ~0.31.26-dev.23 + "@rocket.chat/css-supports": ~0.31.26-dev.23 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.383 + "@rocket.chat/memo": ~0.31.26-dev.23 + "@rocket.chat/styled": ~0.31.26-dev.23 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -8224,7 +8253,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 13ea95dea15c3677f82ffeb50780bc3479512cba6e226080bf464cf876794ed267db3419c45f63ddeaaff6a3401426ca4722e23e0f3586ca4f8eb2e6e25a7a70 + checksum: 2696da3e5cdf9d21c9c96ba069a3ef44b946ce832796ed10047666da780ee3ae88d679f3c3222fe6c6d88b1bdc0b7dab97c83d0acfbb67ca330a14b6e4739aa9 languageName: node linkType: hard @@ -8279,7 +8308,7 @@ __metadata: ts-jest: ~29.0.5 typescript: ~5.2.2 peerDependencies: - "@rocket.chat/core-typings": 6.3.3 + "@rocket.chat/core-typings": 6.3.4 "@rocket.chat/css-in-js": "*" "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-tokens": "*" @@ -8482,6 +8511,13 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/memo@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/memo@npm:0.31.26-dev.23" + checksum: 68301161d87ba25347f1d2ab85c139ba86c5fdd1101f41678808c19ba461772814f4bff048a30e4aefd08978fe2feb952c541bddc0beb6bc3cd190bd7852393b + languageName: node + linkType: hard + "@rocket.chat/message-parser@npm:next": version: 0.32.0-dev.377 resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.377" @@ -9312,6 +9348,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/styled@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/styled@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/css-in-js": ~0.31.26-dev.23 + checksum: 0a1ff89b068f011097671c617844856b91f2477c16ff3771fcfc0bab62a905a9b21c7b79549ff028613700a72685fd591ba9cbeda6b5d3bd8becd3af7aef0498 + languageName: node + linkType: hard + "@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.19": version: 0.31.26-dev.19 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.19" @@ -9323,6 +9368,17 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/css-supports": ~0.31.26-dev.23 + peerDependencies: + stylis: 4.0.10 + checksum: b2fbfad3b2f4dedd9023b30d4cdc51e76ae76faeeca5819cf697e896c02fd4bb2dde5bbc428b377d77f32011fd8cc82c6d98a84d66b93056ef981c13aee1dc67 + languageName: node + linkType: hard + "@rocket.chat/tools@workspace:^, @rocket.chat/tools@workspace:packages/tools": version: 0.0.0-use.local resolution: "@rocket.chat/tools@workspace:packages/tools" @@ -9382,7 +9438,7 @@ __metadata: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 react: ~17.0.2 languageName: unknown linkType: soft @@ -9534,7 +9590,7 @@ __metadata: "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/icons": "*" "@rocket.chat/styled": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 react: ^17.0.2 react-dom: ^17.0.2 languageName: unknown @@ -9618,7 +9674,7 @@ __metadata: typescript: ~5.2.2 peerDependencies: "@rocket.chat/layout": "*" - "@rocket.chat/ui-contexts": 1.0.3 + "@rocket.chat/ui-contexts": 1.0.4 "@tanstack/react-query": "*" react: "*" react-hook-form: "*" From afd2b0d974595198c63e28b8aacc8cb774c4e936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Tue, 12 Sep 2023 00:18:12 -0300 Subject: [PATCH 21/26] regression: Rooms page filters faulty behavior (#30315) --- .../client/views/admin/rooms/RoomRow.tsx | 90 ++++++++ .../client/views/admin/rooms/RoomsTable.tsx | 200 ++++-------------- .../views/admin/rooms/RoomsTableFilters.tsx | 46 +--- .../admin/rooms/useFilteredTypeRooms.tsx | 31 --- .../rooms/useFilteredVisibilityRooms.tsx | 24 --- .../rocketchat-i18n/i18n/en.i18n.json | 7 +- 6 files changed, 144 insertions(+), 254 deletions(-) create mode 100644 apps/meteor/client/views/admin/rooms/RoomRow.tsx delete mode 100644 apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx delete mode 100644 apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx diff --git a/apps/meteor/client/views/admin/rooms/RoomRow.tsx b/apps/meteor/client/views/admin/rooms/RoomRow.tsx new file mode 100644 index 000000000000..2c0dbb8c31a6 --- /dev/null +++ b/apps/meteor/client/views/admin/rooms/RoomRow.tsx @@ -0,0 +1,90 @@ +import { isDiscussion } from '@rocket.chat/core-typings'; +import type { IRoom, RoomAdminFieldsType } from '@rocket.chat/core-typings'; +import { Box, Icon } from '@rocket.chat/fuselage'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback } from 'react'; + +import { GenericTableCell, GenericTableRow } from '../../../components/GenericTable'; +import RoomAvatar from '../../../components/avatar/RoomAvatar'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; + +const roomTypeI18nMap = { + l: 'Omnichannel', + c: 'Channel', + d: 'Direct_Message', + p: 'Private_Channel', +} as const; + +const getRoomDisplayName = (room: Pick): string | undefined => + room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room); + +const RoomRow = ({ room }: { room: Pick }) => { + const t = useTranslation(); + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + const router = useRouter(); + + const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room; + const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room); + const roomName = getRoomDisplayName(room); + + const getRoomType = ( + room: Pick, + ): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => { + if (room.teamMain) { + return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team'; + } + if (isDiscussion(room)) { + return 'Discussion'; + } + return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap]; + }; + + const onClick = useCallback( + (rid) => (): void => + router.navigate({ + name: 'admin-rooms', + params: { + context: 'edit', + id: rid, + }, + }), + [router], + ); + + return ( + + + + + + {icon && } + + {roomName} + + + + + + + {t(getRoomType(room))} + + + {usersCount} + {mediaQuery && {msgs}} + {mediaQuery && {isDefault ? t('True') : t('False')}} + {mediaQuery && {featured ? t('True') : t('False')}} + + ); +}; + +export default RoomRow; diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index 6b2c4435c8fa..c480fab9f657 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -1,31 +1,23 @@ -import { type IRoom, isDiscussion, isPublicRoom } from '@rocket.chat/core-typings'; -import { Box, Icon, Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; +import { Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import type { OptionProp } from '@rocket.chat/ui-client'; -import { useEndpoint, useRouter, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import type { CSSProperties, ReactElement, MutableRefObject } from 'react'; -import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; +import type { ReactElement, MutableRefObject } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import GenericNoResults from '../../../components/GenericNoResults'; import { GenericTable, GenericTableBody, - GenericTableCell, GenericTableHeader, GenericTableHeaderCell, GenericTableLoadingTable, - GenericTableRow, } from '../../../components/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; -import RoomAvatar from '../../../components/avatar/RoomAvatar'; -import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import RoomRow from './RoomRow'; import RoomsTableFilters from './RoomsTableFilters'; -import { useFilteredTypeRooms } from './useFilteredTypeRooms'; -import { useFilteredVisibilityRooms } from './useFilteredVisibilityRooms'; - -const style: CSSProperties = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }; type RoomFilters = { searchText: string; @@ -33,34 +25,9 @@ type RoomFilters = { visibility: OptionProp[]; }; -const DEFAULT_TYPES = ['d', 'p', 'c', 'l', 'discussions', 'teams']; - -const roomTypeI18nMap = { - l: 'Omnichannel', - c: 'Channel', - d: 'Direct_Message', - p: 'Private_Channel', -} as const; - -const getRoomType = ( - room: IRoom, -): (typeof roomTypeI18nMap)[keyof typeof roomTypeI18nMap] | 'Teams_Public_Team' | 'Teams_Private_Team' | 'Discussion' => { - if (room.teamMain) { - return room.t === 'c' ? 'Teams_Public_Team' : 'Teams_Private_Team'; - } - if (isDiscussion(room)) { - return 'Discussion'; - } - return roomTypeI18nMap[(room as IRoom).t as keyof typeof roomTypeI18nMap]; -}; - -const getRoomDisplayName = (room: IRoom): string | undefined => - room.t === 'd' ? room.usernames?.join(' x ') : roomCoordinator.getRoomName(room.t, room); - const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): ReactElement => { - const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const t = useTranslation(); + const mediaQuery = useMediaQuery('(min-width: 1024px)'); const [roomFilters, setRoomFilters] = useState({ searchText: '', types: [], visibility: [] }); @@ -80,29 +47,15 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, count: itemsPerPage, offset: searchText === prevRoomFilterText.current ? current : 0, - types: DEFAULT_TYPES, + types: [...roomFilters.types.map((roomType) => roomType.id)], }; - }, [searchText, sortBy, sortDirection, itemsPerPage, prevRoomFilterText, current, setCurrent]), + }, [searchText, sortBy, sortDirection, itemsPerPage, current, roomFilters.types, setCurrent]), 500, ); const getAdminRooms = useEndpoint('GET', '/v1/rooms.adminRooms'); - const dispatchToastMessage = useToastMessageDispatch(); - - const { data, refetch, isSuccess, isLoading, isError } = useQuery( - ['rooms', query, 'admin'], - async () => { - const adminRooms = await getAdminRooms(query); - - return { ...adminRooms, rooms: adminRooms.rooms as IRoom[] }; - }, - { - onError: (error) => { - dispatchToastMessage({ type: 'error', message: error }); - }, - }, - ); + const { data, refetch, isSuccess, isLoading, isError } = useQuery(['rooms', query, 'admin'], async () => getAdminRooms(query)); useEffect(() => { reload.current = refetch; @@ -112,48 +65,29 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React prevRoomFilterText.current = searchText; }, [searchText]); - const router = useRouter(); - - const onClick = useCallback( - (rid) => (): void => - router.navigate({ - name: 'admin-rooms', - params: { - context: 'edit', - id: rid, - }, - }), - [router], - ); - - const headers = useMemo( - () => - [ - - {t('Name')} - , - - {t('Type')} - , - - {t('Visibility')} - , - - {t('Users')} - , - mediaQuery && ( + const headers = ( + <> + + {t('Name')} + + + {t('Type')} + + + {t('Users')} + + {mediaQuery && ( + <> {t('Msgs')} - ), - mediaQuery && ( void> }): React > {t('Default')} - ), - mediaQuery && ( void> }): React > {t('Featured')} - ), - ].filter(Boolean), - [sortDirection, sortBy, setSort, t, mediaQuery], - ); - - const renderRow = useCallback( - (room: IRoom) => { - const { _id, t: type, usersCount, msgs, default: isDefault, featured, ...args } = room; - const visibility = isPublicRoom(room) ? 'Public' : 'Private'; - const icon = roomCoordinator.getRoomDirectives(room.t).getIcon?.(room); - const roomName = getRoomDisplayName(room); - - return ( - - - - - - - {icon && } - - {roomName} - - - - - - - - {t(getRoomType(room))} - - - - - - {t(visibility)} - - - - {usersCount} - {mediaQuery && {msgs}} - {mediaQuery && {isDefault ? t('True') : t('False')}} - {mediaQuery && {featured ? t('True') : t('False')}} - - ); - }, - [mediaQuery, onClick, t], + + )} + ); - function intersectArraysWithoutDuplicates(array1: IRoom[], array2: IRoom[]) { - const set2 = new Set(array2); - - return [...new Set(array1)].filter((item) => set2.has(item)); - } - - const roomsTypeList = useFilteredTypeRooms(roomFilters.types, isLoading, data?.rooms); - const roomsVisibilityList = useFilteredVisibilityRooms(roomFilters.visibility, isLoading, data?.rooms); - - const roomsList = intersectArraysWithoutDuplicates(roomsTypeList, roomsVisibilityList); - return ( <> - {isLoading && ( {headers} - + )} - {isSuccess && data && data?.rooms.length > 0 && ( + {isSuccess && data.rooms.length === 0 && } + {isSuccess && data.rooms.length > 0 && ( <> {headers} - {isSuccess && roomsList?.map((room) => renderRow(room))} + + {data.rooms?.map((room) => ( + + ))} + void> }): React /> )} - {isSuccess && data && data.rooms.length === 0 && } {isError && ( diff --git a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx index 0d8e5bd0c97e..dede0b34b918 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTableFilters.tsx @@ -12,12 +12,7 @@ const roomTypeFilterStructure = [ isGroupTitle: true, }, { - id: 'channels', - text: 'Channels', - checked: false, - }, - { - id: 'directMessages', + id: 'd', text: 'Direct_Message', checked: false, }, @@ -27,31 +22,23 @@ const roomTypeFilterStructure = [ checked: false, }, { - id: 'omnichannel', + id: 'l', text: 'Omnichannel', checked: false, }, { - id: 'teams', - text: 'Teams', + id: 'p', + text: 'Private_Channels', checked: false, }, -] as OptionProp[]; - -const roomVisibilityFilterStructure = [ { - id: 'filter_by_visibility', - text: 'Filter_by_visibility', - isGroupTitle: true, - }, - { - id: 'private', - text: 'Private', + id: 'c', + text: 'Public_Channels', checked: false, }, { - id: 'public', - text: 'Public', + id: 'teams', + text: 'Teams', checked: false, }, ] as OptionProp[]; @@ -60,13 +47,11 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch(roomTypeFilterStructure); - const [roomVisibilityOptions, setRoomVisibilityOptions] = useState(roomVisibilityFilterStructure); const [roomTypeSelectedOptions, setRoomTypeSelectedOptions] = useState([]); - const [roomVisibilitySelectedOptions, setRoomVisibilitySelectedOptions] = useState([]); useEffect(() => { - return setFilters({ searchText: text, types: roomTypeSelectedOptions, visibility: roomVisibilitySelectedOptions }); - }, [setFilters, roomTypeSelectedOptions, roomVisibilitySelectedOptions, text]); + return setFilters({ searchText: text, types: roomTypeSelectedOptions }); + }, [setFilters, roomTypeSelectedOptions, text]); const handleSearchTextChange = useCallback((event) => setText(event.currentTarget.value), []); @@ -100,17 +85,6 @@ const RoomsTableFilters = ({ setFilters }: { setFilters: Dispatch - - - - ); }; diff --git a/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx b/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx deleted file mode 100644 index 7114aa4f35c9..000000000000 --- a/apps/meteor/client/views/admin/rooms/useFilteredTypeRooms.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import { isDiscussion, isTeamRoom, isDirectMessageRoom } from '@rocket.chat/core-typings'; -import type { OptionProp } from '@rocket.chat/ui-client'; - -const filterRoomsByChannels = (room: Partial): boolean => - (room.t === 'c' || room.t === 'p') && !isDiscussion(room) && !isTeamRoom(room); // can be a public channel or a private channel (group) -const filterRoomsByDirectMessages = (room: Partial): boolean => isDirectMessageRoom(room); -const filterRoomsByDiscussions = (room: Partial): boolean => isDiscussion(room); -const filterRoomsByOmnichannel = ({ t }: Partial): boolean => t === 'l'; // LiveChat -const filterRoomsByTeams = (room: Partial): boolean => isTeamRoom(room); - -const filters: Record) => boolean> = { - channels: filterRoomsByChannels, - directMessages: filterRoomsByDirectMessages, - discussions: filterRoomsByDiscussions, - omnichannel: filterRoomsByOmnichannel, - teams: filterRoomsByTeams, -}; - -export const useFilteredTypeRooms = (selectedOptions: OptionProp[], isLoading: boolean, rooms?: IRoom[]) => { - if (isLoading || !rooms) return []; - if (selectedOptions.length === 0) return rooms; - - let filtered: IRoom[] = []; - - selectedOptions.forEach((option) => { - filtered = [...new Set([...filtered, ...rooms.filter(filters[option.id])])]; - }); - - return filtered; -}; diff --git a/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx b/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx deleted file mode 100644 index be4064817001..000000000000 --- a/apps/meteor/client/views/admin/rooms/useFilteredVisibilityRooms.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; -import { isPublicRoom } from '@rocket.chat/core-typings'; -import type { OptionProp } from '@rocket.chat/ui-client'; - -const filterRoomsByPrivate = (room: Partial): boolean => !isPublicRoom(room); -const filterRoomsByPublic = (room: Partial): boolean => isPublicRoom(room); - -const filters: Record) => boolean> = { - private: filterRoomsByPrivate, - public: filterRoomsByPublic, -}; - -export const useFilteredVisibilityRooms = (selectedOptions: OptionProp[], isLoading: boolean, rooms?: IRoom[]) => { - if (isLoading || !rooms) return []; - if (selectedOptions.length === 0) return rooms; - - let filtered: IRoom[] = []; - - selectedOptions.forEach((option) => { - filtered = [...new Set([...filtered, ...rooms.filter(filters[option.id])])]; - }); - - return filtered; -}; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 34d6a6c4e8ba..f3e5e89c80d6 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1622,7 +1622,7 @@ "Direct": "Direct", "Direction": "Direction", "Livechat_Facebook_API_Secret": "OmniChannel API Secret", - "Direct_Message": "Direct Message", + "Direct_Message": "Direct message", "Livechat_Facebook_Enabled": "Facebook integration enabled", "Direct_message_creation_description": "You are about to create a chat with multiple users. Add the ones you would like to talk, everyone in the same place, using direct messages.", "Direct_message_someone": "Direct message someone", @@ -4053,9 +4053,10 @@ "Privacy_summary": "Privacy summary", "Private": "Private", "private": "private", + "Private_channels": "Private channels", "Private_Apps": "Private Apps", "Private_Channel": "Private Channel", - "Private_Channels": "Private Channels", + "Private_Channels": "Private channels", "Private_Chats": "Private Chats", "Private_Group": "Private Group", "Private_Groups": "Private Groups", @@ -4085,7 +4086,7 @@ "Public": "Public", "public": "public", "Public_Channel": "Public Channel", - "Public_Channels": "Public Channels", + "Public_Channels": "Public channels", "Public_Community": "Public Community", "Public_URL": "Public URL", "Purchase_for_free": "Purchase for FREE", From 93d4912e174be7e34f3287e8cb1f176b3514d2bc Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 12 Sep 2023 01:52:15 -0300 Subject: [PATCH 22/26] fix: missing params on `updateOwnBasicInfo` endpoint (#30156) --- .changeset/wise-walls-tan.md | 6 ++++++ apps/meteor/app/api/server/v1/users.ts | 2 ++ packages/rest-typings/src/v1/users.ts | 16 +++------------- .../users/UsersUpdateOwnBasicInfoParamsPOST.ts | 10 ++++++++++ 4 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 .changeset/wise-walls-tan.md diff --git a/.changeset/wise-walls-tan.md b/.changeset/wise-walls-tan.md new file mode 100644 index 000000000000..f558de82ec4c --- /dev/null +++ b/.changeset/wise-walls-tan.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +fix: missing params on updateOwnBasicInfo endpoint diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index a4e3f974ac65..b23d41255c3b 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -126,7 +126,9 @@ API.v1.addRoute( realname: this.bodyParams.data.name, username: this.bodyParams.data.username, nickname: this.bodyParams.data.nickname, + bio: this.bodyParams.data.bio, statusText: this.bodyParams.data.statusText, + statusType: this.bodyParams.data.statusType, newPassword: this.bodyParams.data.newPassword, typedPassword: this.bodyParams.data.currentPassword, }; diff --git a/packages/rest-typings/src/v1/users.ts b/packages/rest-typings/src/v1/users.ts index 947228476bdd..c47f4be6404d 100644 --- a/packages/rest-typings/src/v1/users.ts +++ b/packages/rest-typings/src/v1/users.ts @@ -9,7 +9,6 @@ import type { } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; -import type { UsersSendConfirmationEmailParamsPOST } from '..'; import type { PaginatedRequest } from '../helpers/PaginatedRequest'; import type { PaginatedResult } from '../helpers/PaginatedResult'; import type { UserCreateParamsPOST } from './users/UserCreateParamsPOST'; @@ -20,7 +19,9 @@ import type { UserSetActiveStatusParamsPOST } from './users/UserSetActiveStatusP import type { UsersAutocompleteParamsGET } from './users/UsersAutocompleteParamsGET'; import type { UsersInfoParamsGet } from './users/UsersInfoParamsGet'; import type { UsersListTeamsParamsGET } from './users/UsersListTeamsParamsGET'; +import type { UsersSendConfirmationEmailParamsPOST } from './users/UsersSendConfirmationEmailParamsPOST'; import type { UsersSetPreferencesParamsPOST } from './users/UsersSetPreferenceParamsPOST'; +import type { UsersUpdateOwnBasicInfoParamsPOST } from './users/UsersUpdateOwnBasicInfoParamsPOST'; import type { UsersUpdateParamsPOST } from './users/UsersUpdateParamsPOST'; const ajv = new Ajv({ @@ -358,18 +359,7 @@ export type UsersEndpoints = { }; '/v1/users.updateOwnBasicInfo': { - POST: (params: { - data: { - email?: string; - name?: string; - username?: string; - nickname?: string; - statusText?: string; - newPassword?: string; - currentPassword?: string; - }; - customFields?: Record; - }) => { + POST: (params: UsersUpdateOwnBasicInfoParamsPOST) => { user: IUser; }; }; diff --git a/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts index cff6fee56bfa..13c3066e4767 100644 --- a/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersUpdateOwnBasicInfoParamsPOST.ts @@ -10,7 +10,9 @@ export type UsersUpdateOwnBasicInfoParamsPOST = { name?: string; username?: string; nickname?: string; + bio?: string; statusText?: string; + statusType?: string; currentPassword?: string; newPassword?: string; }; @@ -39,6 +41,14 @@ const UsersUpdateOwnBasicInfoParamsPostSchema = { type: 'string', nullable: true, }, + bio: { + type: 'string', + nullable: true, + }, + statusType: { + type: 'string', + nullable: true, + }, statusText: { type: 'string', nullable: true, From b58aee71fcebb41c23a15e1e15594214b35867c3 Mon Sep 17 00:00:00 2001 From: janainaCoelhoRocketchat <105796517+janainaCoelhoRocketchat@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:18:33 -0300 Subject: [PATCH 23/26] test: Changed Home Page spec titles (#30349) --- apps/meteor/tests/e2e/homepage.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/meteor/tests/e2e/homepage.spec.ts b/apps/meteor/tests/e2e/homepage.spec.ts index 380fa54d2af3..4f8f9d09a2f3 100644 --- a/apps/meteor/tests/e2e/homepage.spec.ts +++ b/apps/meteor/tests/e2e/homepage.spec.ts @@ -32,7 +32,7 @@ test.describe.serial('homepage', () => { await adminPage.close(); }); - test('layout', async () => { + test('expect customize button and all cards to be visible', async () => { await test.step('expect show customize button', async () => { await expect(adminPage.locator('role=button[name="Customize"]')).toBeVisible(); }); @@ -47,7 +47,7 @@ test.describe.serial('homepage', () => { await expect((await api.post('/settings/Layout_Home_Body', { value: '' })).status()).toBe(200); }); - test('layout', async () => { + test('visibility and button functionality in custom body with empty custom content', async () => { await test.step('expect default value in custom body', async () => { await expect( adminPage.locator('role=status[name="Admins may insert content html to be rendered in this white space."]'), @@ -70,7 +70,7 @@ test.describe.serial('homepage', () => { await expect((await api.post('/settings/Layout_Home_Body', { value: 'Hello admin' })).status()).toBe(200); }); - test('layout', async () => { + test('visibility and button functionality in custom body with custom content', async () => { await test.step('expect custom body to be visible', async () => { await expect(adminPage.locator('role=status[name="Hello admin"]')).toBeVisible(); }); @@ -122,7 +122,7 @@ test.describe.serial('homepage', () => { await regularUserPage.close(); }); - test('layout', async () => { + test('the option customize is not be active', async () => { await test.step('expect to not show customize button', async () => { await expect(regularUserPage.locator('role=button[name="Customize"]')).not.toBeVisible(); }); @@ -162,7 +162,7 @@ test.describe.serial('homepage', () => { expect((await api.post('/settings/Layout_Home_Title', { value: 'Home' })).status()).toBe(200); }); - test('layout', async () => { + test('expect welcome text and header text to be correct', async () => { await test.step('expect welcome text to be NewSiteName', async () => { await expect(regularUserPage.locator('role=heading[name="Welcome to NewSiteName"]')).toBeVisible(); }); @@ -202,7 +202,7 @@ test.describe.serial('homepage', () => { expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); }); - test('layout', async () => { + test('expect default layout not be visible and custom body visible', async () => { await test.step('expect default layout to not be visible', async () => { await expect(regularUserPage.locator('[data-qa-id="homepage-welcome-text"]')).not.toBeVisible(); }); From 1000b9b317f35b0bff4c1981e7a7c52f9e53fe6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=87=E3=83=AF=E3=83=B3=E3=82=B7=E3=83=A5?= <61188295+Dnouv@users.noreply.github.com> Date: Tue, 12 Sep 2023 20:27:07 +0530 Subject: [PATCH 24/26] fix: apps name misalignment in case of no icons (#30356) --- .changeset/young-trains-glow.md | 5 +++++ .../client/components/message/toolbox/MessageActionMenu.tsx | 1 + .../MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx | 1 + 3 files changed, 7 insertions(+) create mode 100644 .changeset/young-trains-glow.md diff --git a/.changeset/young-trains-glow.md b/.changeset/young-trains-glow.md new file mode 100644 index 000000000000..77f50812143f --- /dev/null +++ b/.changeset/young-trains-glow.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Fixed the issue of apps icon uneven alignment in case of missing icons inside message composer toolbar & message toolbar menu. diff --git a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx index 4c5d442652f2..54a320ebf3d7 100644 --- a/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx +++ b/apps/meteor/client/components/message/toolbox/MessageActionMenu.tsx @@ -101,6 +101,7 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility, ...props }: Messag data-qa-type='message-action' data-qa-id={option.id} role={option.role ? option.role : 'button'} + gap={!option.icon && option.type === 'apps'} /> ))} {index !== arr.length - 1 && } diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx index 021f6518021a..5066ecb192e1 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/ActionsToolbarDropdown.tsx @@ -93,6 +93,7 @@ const ActionsToolbarDropdown = ({ isRecording, rid, tmid, actions, ...props }: A chat: chatContext, }) } + gap={!item.icon} > {item.icon && ['name']} />} {item.name} From 091f248a55ac5ca9cea249dbf4196240b6de4993 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 12 Sep 2023 12:19:24 -0300 Subject: [PATCH 25/26] refactor(client): async loading (#30314) Co-authored-by: Guilherme Gazzo --- .../meteor/client/lib/2fa/process2faReturn.ts | 4 +++- apps/meteor/client/main.ts | 24 ++++++++++--------- apps/meteor/client/polyfills/index.ts | 1 - .../client/polyfills/objectFromEntries.ts | 5 ---- .../client/providers/RouterProvider.tsx | 6 ----- 5 files changed, 16 insertions(+), 24 deletions(-) delete mode 100644 apps/meteor/client/polyfills/objectFromEntries.ts diff --git a/apps/meteor/client/lib/2fa/process2faReturn.ts b/apps/meteor/client/lib/2fa/process2faReturn.ts index e5ff73be77b1..95f7f1dcb361 100644 --- a/apps/meteor/client/lib/2fa/process2faReturn.ts +++ b/apps/meteor/client/lib/2fa/process2faReturn.ts @@ -1,10 +1,12 @@ import { SHA256 } from '@rocket.chat/sha256'; import { Meteor } from 'meteor/meteor'; +import { lazy } from 'react'; -import TwoFactorModal from '../../components/TwoFactorModal'; import { imperativeModal } from '../imperativeModal'; import { isTotpInvalidError, isTotpRequiredError } from './utils'; +const TwoFactorModal = lazy(() => import('../../components/TwoFactorModal')); + const twoFactorMethods = ['totp', 'email', 'password'] as const; type TwoFactorMethod = (typeof twoFactorMethods)[number]; diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts index bb08f0242d4f..334515980787 100644 --- a/apps/meteor/client/main.ts +++ b/apps/meteor/client/main.ts @@ -1,13 +1,15 @@ -import '../ee/client/ecdh'; -import './polyfills'; +import { FlowRouter } from 'meteor/kadira:flow-router'; -import '../lib/oauthRedirectUriClient'; -import './lib/meteorCallWrapper'; -import './importPackages'; +FlowRouter.wait(); -import '../ee/client'; -import './methods'; -import './startup'; -import './views/admin'; -import './views/marketplace'; -import './views/account'; +FlowRouter.notFound = { + action: () => undefined, +}; + +import('./polyfills') + .then(() => Promise.all([import('./lib/meteorCallWrapper'), import('../lib/oauthRedirectUriClient')])) + .then(() => import('../ee/client/ecdh')) + .then(() => import('./importPackages')) + .then(() => Promise.all([import('./methods'), import('./startup')])) + .then(() => import('../ee/client')) + .then(() => Promise.all([import('./views/admin'), import('./views/marketplace'), import('./views/account')])); diff --git a/apps/meteor/client/polyfills/index.ts b/apps/meteor/client/polyfills/index.ts index 46f5bcb8d68d..f07d828a4602 100644 --- a/apps/meteor/client/polyfills/index.ts +++ b/apps/meteor/client/polyfills/index.ts @@ -4,4 +4,3 @@ import './childNodeRemove'; import './cssVars'; import './customEventPolyfill'; import './hoverTouchClick'; -import './objectFromEntries'; diff --git a/apps/meteor/client/polyfills/objectFromEntries.ts b/apps/meteor/client/polyfills/objectFromEntries.ts deleted file mode 100644 index d59198ebd1d3..000000000000 --- a/apps/meteor/client/polyfills/objectFromEntries.ts +++ /dev/null @@ -1,5 +0,0 @@ -Object.fromEntries = - Object.fromEntries || - function fromEntries(entries: Iterable): { [k: string]: T } { - return [...entries].reduce((obj, { 0: key, 1: val }) => Object.assign(obj, { [key]: val }), {}); - }; diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index 0dd7ee31deed..0f146ec83128 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -17,12 +17,6 @@ import React from 'react'; import { appLayout } from '../lib/appLayout'; import { queueMicrotask } from '../lib/utils/queueMicrotask'; -FlowRouter.wait(); - -FlowRouter.notFound = { - action: () => undefined, -}; - const subscribers = new Set<() => void>(); const listenToRouteChange = () => { From 8202f2782ce987ed36159bfc1640944c1b7fa41c Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 12 Sep 2023 10:10:21 -0600 Subject: [PATCH 26/26] regression: Reports by-department showing null when department no longer exists (#30350) --- apps/meteor/ee/server/models/raw/LivechatRooms.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/meteor/ee/server/models/raw/LivechatRooms.ts b/apps/meteor/ee/server/models/raw/LivechatRooms.ts index 227da1c98e8c..70824c7d7130 100644 --- a/apps/meteor/ee/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/ee/server/models/raw/LivechatRooms.ts @@ -540,6 +540,13 @@ export class LivechatRoomsRawEE extends LivechatRoomsRaw implements ILivechatRoo }, }, }, + { + $match: { + _id: { + $ne: null, + }, + }, + }, { $sort: sort || { total: 1 }, },