{value?.url ? (
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/BooleanSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/BooleanSettingInput.tsx
index acac16b5b7e8..308e781e8e46 100644
--- a/apps/meteor/client/views/admin/settings/inputs/BooleanSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/BooleanSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Field, ToggleSwitch } from '@rocket.chat/fuselage';
+import { FieldLabel, FieldRow, ToggleSwitch } from '@rocket.chat/fuselage';
import type { ReactElement, SyntheticEvent } from 'react';
import React from 'react';
@@ -30,7 +30,7 @@ function BooleanSettingInput({
};
return (
-
+
-
+
{label}
-
+
{hasResetButton && }
-
+
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/CodeSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/CodeSettingInput.tsx
index 2b615a63b8d5..85698b66e2b7 100644
--- a/apps/meteor/client/views/admin/settings/inputs/CodeSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/CodeSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldHint, Flex } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React from 'react';
@@ -43,12 +43,12 @@ function CodeSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
- {hint && {hint}}
+ {hint && {hint}}
-
+
{label}
-
+
{hasResetButton && }
-
+
{editor === 'color' && (
@@ -104,9 +104,9 @@ function ColorSettingInput({
options={allowedTypes.map((type) => [type, t(type)])}
/>
-
+
- Variable name: {_id.replace(/theme-color-/, '@')}
+ Variable name: {_id.replace(/theme-color-/, '@')}
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/FontSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/FontSettingInput.tsx
index 2d7a93b9c96f..35b255b4e756 100644
--- a/apps/meteor/client/views/admin/settings/inputs/FontSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/FontSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, TextInput } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, TextInput } from '@rocket.chat/fuselage';
import type { FormEventHandler, ReactElement } from 'react';
import React from 'react';
@@ -36,13 +36,13 @@ function FontSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/GenericSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/GenericSettingInput.tsx
index 46ffc61a8f3b..32425a57c698 100644
--- a/apps/meteor/client/views/admin/settings/inputs/GenericSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/GenericSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, TextInput } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, TextInput } from '@rocket.chat/fuselage';
import type { FormEventHandler, ReactElement } from 'react';
import React from 'react';
@@ -36,13 +36,13 @@ function GenericSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/IntSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/IntSettingInput.tsx
index 03f837117223..cd5abd54f481 100644
--- a/apps/meteor/client/views/admin/settings/inputs/IntSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/IntSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, InputBox } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, InputBox } from '@rocket.chat/fuselage';
import type { FormEventHandler, ReactElement } from 'react';
import React from 'react';
@@ -37,13 +37,13 @@ function IntSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/LanguageSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/LanguageSettingInput.tsx
index 770edd5c12a6..8bfe977aaf39 100644
--- a/apps/meteor/client/views/admin/settings/inputs/LanguageSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/LanguageSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, Select } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, Select } from '@rocket.chat/fuselage';
import { useLanguages } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
@@ -40,13 +40,13 @@ function LanguageSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/LookupSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/LookupSettingInput.tsx
index 8b7175320d98..15a1aab443d7 100644
--- a/apps/meteor/client/views/admin/settings/inputs/LookupSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/LookupSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, Select } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, Select } from '@rocket.chat/fuselage';
import type { PathPattern } from '@rocket.chat/rest-typings';
import type { ReactElement } from 'react';
import React from 'react';
@@ -45,13 +45,13 @@ function LookupSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/MultiSelectSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/MultiSelectSettingInput.tsx
index 114503c959ed..bc8d8062d8a2 100644
--- a/apps/meteor/client/views/admin/settings/inputs/MultiSelectSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/MultiSelectSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Field, Flex, Box, MultiSelectFiltered, MultiSelect } from '@rocket.chat/fuselage';
+import { FieldLabel, Flex, Box, MultiSelectFiltered, MultiSelect } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -45,9 +45,9 @@ function MultiSelectSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
diff --git a/apps/meteor/client/views/admin/settings/inputs/PasswordSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/PasswordSettingInput.tsx
index 087d83b98a0d..b7d2c1d48d47 100644
--- a/apps/meteor/client/views/admin/settings/inputs/PasswordSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/PasswordSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, PasswordInput } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, PasswordInput } from '@rocket.chat/fuselage';
import type { EventHandler, ReactElement, SyntheticEvent } from 'react';
import React from 'react';
@@ -37,13 +37,13 @@ function PasswordSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/RelativeUrlSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/RelativeUrlSettingInput.tsx
index 0541ea81eb36..b94581706757 100644
--- a/apps/meteor/client/views/admin/settings/inputs/RelativeUrlSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/RelativeUrlSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, UrlInput } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, Flex, UrlInput } from '@rocket.chat/fuselage';
import { useAbsoluteUrl } from '@rocket.chat/ui-contexts';
import type { EventHandler, ReactElement, SyntheticEvent } from 'react';
import React from 'react';
@@ -40,9 +40,9 @@ function RelativeUrlSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
diff --git a/apps/meteor/client/views/admin/settings/inputs/RoomPickSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/RoomPickSettingInput.tsx
index d44235afbde9..15423742ff91 100644
--- a/apps/meteor/client/views/admin/settings/inputs/RoomPickSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/RoomPickSettingInput.tsx
@@ -1,5 +1,5 @@
import type { SettingValueRoomPick } from '@rocket.chat/core-typings';
-import { Box, Field, Flex } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React from 'react';
@@ -42,13 +42,13 @@ function RoomPickSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/SelectSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/SelectSettingInput.tsx
index 28014d65d375..a6fa88f7ffe7 100644
--- a/apps/meteor/client/views/admin/settings/inputs/SelectSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/SelectSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, Select } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, Select } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -43,13 +43,13 @@ function SelectSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/SelectTimezoneSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/SelectTimezoneSettingInput.tsx
index 1478b8746583..12c5da28365c 100644
--- a/apps/meteor/client/views/admin/settings/inputs/SelectTimezoneSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/SelectTimezoneSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, Select } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, Select } from '@rocket.chat/fuselage';
import moment from 'moment-timezone';
import type { ReactElement } from 'react';
import React from 'react';
@@ -38,13 +38,13 @@ function SelectTimezoneSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
+
>
);
}
diff --git a/apps/meteor/client/views/admin/settings/inputs/StringSettingInput.tsx b/apps/meteor/client/views/admin/settings/inputs/StringSettingInput.tsx
index 30b79e01683f..3d0ba78a127a 100644
--- a/apps/meteor/client/views/admin/settings/inputs/StringSettingInput.tsx
+++ b/apps/meteor/client/views/admin/settings/inputs/StringSettingInput.tsx
@@ -1,4 +1,4 @@
-import { Box, Field, Flex, TextAreaInput, TextInput } from '@rocket.chat/fuselage';
+import { Box, FieldLabel, FieldRow, Flex, TextAreaInput, TextInput } from '@rocket.chat/fuselage';
import type { EventHandler, ReactElement, SyntheticEvent } from 'react';
import React from 'react';
@@ -43,13 +43,13 @@ function StringSettingInput({
<>
-
+
{label}
-
+
{hasResetButton && }
-
+
{multiline ? (
)}
-
+
>
);
}
diff --git a/apps/meteor/client/views/admin/users/AdminUserForm.tsx b/apps/meteor/client/views/admin/users/AdminUserForm.tsx
index 334aca68b8f8..1912150b4a48 100644
--- a/apps/meteor/client/views/admin/users/AdminUserForm.tsx
+++ b/apps/meteor/client/views/admin/users/AdminUserForm.tsx
@@ -1,6 +1,10 @@
import type { AvatarObject, IUser, Serialized } from '@rocket.chat/core-typings';
import {
Field,
+ FieldLabel,
+ FieldRow,
+ FieldError,
+ FieldHint,
TextInput,
TextAreaInput,
PasswordInput,
@@ -179,8 +183,8 @@ const UserForm = ({ userData, onReload, ...props }: AdminUserFormProps) => {
)}
- {t('Name')}
-
+ {t('Name')}
+
{
/>
)}
/>
-
+
{errors?.name && (
-
+
{errors.name.message}
-
+
)}
- {t('Username')}
-
+ {t('Username')}
+
{
/>
)}
/>
-
+
{errors?.username && (
-
+
{errors.username.message}
-
+
)}
- {t('Email')}
-
+ {t('Email')}
+
{
/>
)}
/>
-
+
{errors?.email && (
-
+
{errors.email.message}
-
+
)}
- {t('Verified')}
-
+ {t('Verified')}
+
}
/>
-
+
- {t('StatusMessage')}
-
+ {t('StatusMessage')}
+
{
/>
)}
/>
-
+
{errors?.statusText && (
-
+
{errors.statusText.message}
-
+
)}
- {t('Bio')}
-
+ {t('Bio')}
+
{
/>
)}
/>
-
+
{errors?.bio && (
-
+
{errors.bio.message}
-
+
)}
- {t('Nickname')}
-
+ {t('Nickname')}
+
{
} />
)}
/>
-
+
{!setRandomPassword && (
- {t('Password')}
-
+ {t('Password')}
+
{
/>
)}
/>
-
+
{errors?.password && (
-
+
{errors.password.message}
-
+
)}
)}
- {t('Require_password_change')}
-
+ {t('Require_password_change')}
+
{
/>
)}
/>
-
+
- {t('Set_random_password_and_send_by_email')}
-
+ {t('Set_random_password_and_send_by_email')}
+
{
/>
)}
/>
-
+
{!isSmtpEnabled && (
-
)}
- {t('Roles')}
-
+ {t('Roles')}
+
{roleError && {roleError}}
{!roleError && (
{
)}
/>
)}
-
- {errors?.roles && {errors.roles.message}}
+
+ {errors?.roles && {errors.roles.message}}
- {t('Join_default_channels')}
-
+ {t('Join_default_channels')}
+
{
)}
/>
-
+
- {t('Send_welcome_email')}
-
+ {t('Send_welcome_email')}
+
{
/>
)}
/>
-
+
{!isSmtpEnabled && (
-
diff --git a/apps/meteor/client/views/e2e/EnterE2EPasswordModal.tsx b/apps/meteor/client/views/e2e/EnterE2EPasswordModal.tsx
index 76d5f3b61e28..249ae6df5efa 100644
--- a/apps/meteor/client/views/e2e/EnterE2EPasswordModal.tsx
+++ b/apps/meteor/client/views/e2e/EnterE2EPasswordModal.tsx
@@ -1,4 +1,4 @@
-import { Box, PasswordInput, Field, FieldGroup } from '@rocket.chat/fuselage';
+import { Box, PasswordInput, Field, FieldGroup, FieldRow, FieldError } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -50,7 +50,7 @@ const EnterE2EPasswordModal = ({
-
+
-
- {passwordError}
+
+ {passwordError}
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
index b6b536925ba1..aa5a917405b4 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx
@@ -1,5 +1,17 @@
import type { ILivechatAgent, ILivechatDepartment, ILivechatDepartmentAgents } from '@rocket.chat/core-typings';
-import { Field, TextInput, Button, Box, MultiSelect, Icon, Select, ContextualbarFooter, ButtonGroup } from '@rocket.chat/fuselage';
+import {
+ Field,
+ FieldLabel,
+ FieldRow,
+ TextInput,
+ Button,
+ Box,
+ MultiSelect,
+ Icon,
+ Select,
+ ContextualbarFooter,
+ ButtonGroup,
+} from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRoute, useSetting, useMethod, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { FC, ReactElement } from 'react';
@@ -121,26 +133,26 @@ const AgentEdit: FC = ({ data, userDepartments, availableDepartm
)}
- {t('Name')}
-
+ {t('Name')}
+
-
+
- {t('Username')}
-
+ {t('Username')}
+
} />
-
+
- {t('Email')}
-
+ {t('Email')}
+
} />
-
+
- {t('Departments')}
-
+ {t('Departments')}
+
= ({ data, userDepartments, availableDepartm
placeholder={t('Select_an_option')}
onChange={handleDepartments}
/>
-
+
- {t('Status')}
-
+ {t('Status')}
+
+
{MaxChats && }
{voipEnabled && (
- {t('VoIP_Extension')}
-
+ {t('VoIP_Extension')}
+
-
+
)}
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
index e030fe3d6305..47d60b3d9958 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx
@@ -1,4 +1,4 @@
-import { Button, Box, Field } from '@rocket.chat/fuselage';
+import { Button, Box, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -38,13 +38,13 @@ const AddAgent = ({ reload }: AddAgentProps): ReactElement => {
return (
- {t('Username')}
-
+ {t('Username')}
+
-
+
);
diff --git a/apps/meteor/client/views/omnichannel/analytics/AnalyticsPage.tsx b/apps/meteor/client/views/omnichannel/analytics/AnalyticsPage.tsx
index 333d2e772c4c..4a7405b7b8cc 100644
--- a/apps/meteor/client/views/omnichannel/analytics/AnalyticsPage.tsx
+++ b/apps/meteor/client/views/omnichannel/analytics/AnalyticsPage.tsx
@@ -1,5 +1,5 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Box, Select, Margins, Field, Label } from '@rocket.chat/fuselage';
+import { Box, Select, Margins, Field, FieldLabel, FieldRow, Label } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo, useState, useEffect } from 'react';
@@ -74,10 +74,10 @@ const AnalyticsPage = () => {
- {t('Chart')}
-
+ {t('Chart')}
+
+
diff --git a/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx b/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
index b84996f168d4..40644b1f1b04 100644
--- a/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
+++ b/apps/meteor/client/views/omnichannel/analytics/DateRangePicker.tsx
@@ -1,4 +1,4 @@
-import { Box, InputBox, Menu, Field } from '@rocket.chat/fuselage';
+import { Box, InputBox, Menu, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { Moment } from 'moment';
@@ -112,21 +112,21 @@ const DateRangePicker = ({ onChange = () => undefined, ...props }: DateRangePick
- {t('Start')}
-
+ {t('Start')}
+
-
+
- {t('End')}
-
+ {t('End')}
+
-
+
diff --git a/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx b/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx
index 9d5e176301a8..4983c5ca8837 100644
--- a/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx
+++ b/apps/meteor/client/views/omnichannel/appearance/AppearanceForm.tsx
@@ -1,4 +1,16 @@
-import { Box, Field, TextInput, ToggleSwitch, Accordion, FieldGroup, InputBox, TextAreaInput, NumberInput } from '@rocket.chat/fuselage';
+import {
+ Box,
+ Field,
+ FieldLabel,
+ FieldRow,
+ TextInput,
+ ToggleSwitch,
+ Accordion,
+ FieldGroup,
+ InputBox,
+ TextAreaInput,
+ NumberInput,
+} from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, FormEvent } from 'react';
@@ -105,46 +117,46 @@ const AppearanceForm: FC = ({ values = {}, handlers = {} })
- {t('Title')}
-
+ {t('Title')}
+
-
+
- {t('Title_bar_color')}
-
+ {t('Title_bar_color')}
+
-
+
- {t('Message_Characther_Limit')}
-
+ {t('Message_Characther_Limit')}
+
-
+
-
+
-
+
- {t('Show_agent_info')}
-
+ {t('Show_agent_info')}
+
-
+
- {t('Show_agent_email')}
-
+ {t('Show_agent_email')}
+
-
+
@@ -154,66 +166,66 @@ const AppearanceForm: FC = ({ values = {}, handlers = {} })
- {t('Display_offline_form')}
-
+ {t('Display_offline_form')}
+
-
+
- {t('Offline_form_unavailable_message')}
-
+ {t('Offline_form_unavailable_message')}
+
-
+
- {t('Offline_message')}
-
+ {t('Offline_message')}
+
-
+
- {t('Title_offline')}
-
+ {t('Title_offline')}
+
-
+
- {t('Title_bar_color_offline')}
-
+ {t('Title_bar_color_offline')}
+
-
+
- {t('Email_address_to_send_offline_messages')}
-
+ {t('Email_address_to_send_offline_messages')}
+
-
+
- {t('Offline_success_message')}
-
+ {t('Offline_success_message')}
+
-
+
@@ -222,64 +234,64 @@ const AppearanceForm: FC = ({ values = {}, handlers = {} })
- {t('Enabled')}
-
+ {t('Enabled')}
+
-
+
- {t('Show_name_field')}
-
+ {t('Show_name_field')}
+
-
+
- {t('Show_email_field')}
-
+ {t('Show_email_field')}
+
-
+
- {t('Livechat_registration_form_message')}
-
+ {t('Livechat_registration_form_message')}
+
-
+
- {t('Conversation_finished_message')}
-
+ {t('Conversation_finished_message')}
+
-
+
- {t('Conversation_finished_text')}
-
+ {t('Conversation_finished_text')}
+
-
+
diff --git a/apps/meteor/client/views/omnichannel/currentChats/CustomFieldsList.tsx b/apps/meteor/client/views/omnichannel/currentChats/CustomFieldsList.tsx
index e0b8a77381b4..803b74b9522d 100644
--- a/apps/meteor/client/views/omnichannel/currentChats/CustomFieldsList.tsx
+++ b/apps/meteor/client/views/omnichannel/currentChats/CustomFieldsList.tsx
@@ -1,5 +1,5 @@
import type { ILivechatCustomField } from '@rocket.chat/core-typings';
-import { Field, TextInput, Select } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, TextInput, Select } from '@rocket.chat/fuselage';
import { useTranslation, useRoute } from '@rocket.chat/ui-contexts';
import type { ReactElement, Dispatch, SetStateAction } from 'react';
import React, { useEffect } from 'react';
@@ -39,8 +39,8 @@ const CustomFieldsList = ({ setCustomFields, allCustomFields }: CustomFieldsList
if (customField.type === 'select') {
return (
- {customField.label}
-
+ {customField.label}
+
[item, item])} />
)}
/>
-
+
);
}
return (
- {customField.label}
-
+ {customField.label}
+
-
+
);
})}
diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentTags/index.tsx b/apps/meteor/client/views/omnichannel/departments/DepartmentTags/index.tsx
index b1542f857316..cead6a0fb15f 100644
--- a/apps/meteor/client/views/omnichannel/departments/DepartmentTags/index.tsx
+++ b/apps/meteor/client/views/omnichannel/departments/DepartmentTags/index.tsx
@@ -1,4 +1,4 @@
-import { Button, Chip, Field, TextInput } from '@rocket.chat/fuselage';
+import { Button, Chip, FieldRow, FieldHint, TextInput } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FormEvent } from 'react';
import React, { useCallback, useState } from 'react';
@@ -28,7 +28,7 @@ export const DepartmentTags = ({ error, value: tags, onChange }: DepartmentTagsP
return (
<>
-
+
{t('Add')}
-
+
- {t('Conversation_closing_tags_description')}
+ {t('Conversation_closing_tags_description')}
{tags?.length > 0 && (
-
+
{tags.map((tag, i) => (
{tag}
))}
-
+
)}
>
);
diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx b/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx
index 3a408bfa7507..46919307b052 100644
--- a/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx
+++ b/apps/meteor/client/views/omnichannel/departments/EditDepartment.tsx
@@ -2,6 +2,9 @@ import type { ILivechatDepartment, ILivechatDepartmentAgents, Serialized } from
import {
FieldGroup,
Field,
+ FieldLabel,
+ FieldRow,
+ FieldError,
TextInput,
Box,
Icon,
@@ -240,16 +243,16 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen
>
- {t('Enabled')}
-
+ {t('Enabled')}
+
-
+
- {t('Name')}*
-
+ {t('Name')}*
+
-
- {errors.name && {errors.name?.message}}
+
+ {errors.name && {errors.name?.message}}
- {t('Description')}
-
+ {t('Description')}
+
-
+
- {t('Show_on_registration_page')}
-
+ {t('Show_on_registration_page')}
+
-
+
- {t('Email')}*
-
+ {t('Email')}*
+
validateEmail(email) || t('error-invalid-email-address'),
})}
/>
-
- {errors.email && {errors.email?.message}}
+
+ {errors.email && {errors.email?.message}}
- {t('Show_on_offline_page')}
-
+ {t('Show_on_offline_page')}
+
-
+
- {t('Livechat_DepartmentOfflineMessageToChannel')}
-
+ {t('Livechat_DepartmentOfflineMessageToChannel')}
+
)}
/>
-
+
{MaxChats && (
@@ -421,7 +424,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen
{AutoCompleteDepartment && (
- {t('Fallback_forward_department')}
+ {t('Fallback_forward_department')}
- {t('Request_tag_before_closing_chat')}
-
+ {t('Request_tag_before_closing_chat')}
+
-
+
{requestTagBeforeClosingChat && (
- {t('Conversation_closing_tags')}*
+ {t('Conversation_closing_tags')}*
)}
/>
- {errors.chatClosingTags && {errors.chatClosingTags?.message}}
+ {errors.chatClosingTags && {errors.chatClosingTags?.message}}
)}
@@ -475,7 +478,7 @@ function EditDepartment({ data, id, title, allowedToForwardData }: EditDepartmen
- {t('Agents')}:
+ {t('Agents')}:
diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx
index 433af2135068..89b7b4582fa5 100644
--- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx
+++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx
@@ -1,5 +1,5 @@
import type { ILivechatVisitor, IOmnichannelRoom, Serialized } from '@rocket.chat/core-typings';
-import { Field, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage';
import { CustomFieldsForm } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -127,10 +127,10 @@ function RoomEdit({ room, visitor, reload, reloadInfo, onClose }: RoomEditProps)
)}
- {t('Topic')}
-
+ {t('Topic')}
+
-
+
diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.tsx b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.tsx
index afb609e5f801..a41c155c4f9d 100644
--- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.tsx
+++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.tsx
@@ -1,5 +1,5 @@
import type { ILivechatVisitor, Serialized } from '@rocket.chat/core-typings';
-import { Field, TextInput, ButtonGroup, Button, ContextualbarContent } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, FieldError, TextInput, ButtonGroup, Button, ContextualbarContent } from '@rocket.chat/fuselage';
import { CustomFieldsForm } from '@rocket.chat/ui-client';
import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
@@ -189,25 +189,25 @@ const ContactNewEdit = ({ id, data, close }: ContactNewEditProps): ReactElement
<>
- {t('Name')}*
-
+ {t('Name')}*
+
-
- {errors.name?.message}
+
+ {errors.name?.message}
- {t('Email')}
-
+ {t('Email')}
+
-
- {errors.email?.message}
+
+ {errors.email?.message}
- {t('Phone')}
-
+ {t('Phone')}
+
-
- {errors.phone?.message}
+
+ {errors.phone?.message}
{canViewCustomFields() && }
{ContactManager && }
diff --git a/apps/meteor/client/views/omnichannel/managers/AddManager.tsx b/apps/meteor/client/views/omnichannel/managers/AddManager.tsx
index d6668d7f35d7..b4f56f78b62b 100644
--- a/apps/meteor/client/views/omnichannel/managers/AddManager.tsx
+++ b/apps/meteor/client/views/omnichannel/managers/AddManager.tsx
@@ -1,4 +1,4 @@
-import { Button, Box, Field } from '@rocket.chat/fuselage';
+import { Button, Box, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -34,13 +34,13 @@ const AddManager = ({ reload }: { reload: () => void }): ReactElement => {
return (
- {t('Username')}
-
+ {t('Username')}
+
-
+
);
diff --git a/apps/meteor/client/views/omnichannel/triggers/TriggersForm.tsx b/apps/meteor/client/views/omnichannel/triggers/TriggersForm.tsx
index 72c8d30d973e..7c9476059411 100644
--- a/apps/meteor/client/views/omnichannel/triggers/TriggersForm.tsx
+++ b/apps/meteor/client/views/omnichannel/triggers/TriggersForm.tsx
@@ -1,5 +1,5 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Box, Field, TextInput, ToggleSwitch, Select, TextAreaInput } from '@rocket.chat/fuselage';
+import { Box, Field, FieldLabel, FieldRow, FieldError, TextInput, ToggleSwitch, Select, TextAreaInput } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps, FC, FormEvent } from 'react';
@@ -138,55 +138,55 @@ const TriggersForm: FC = ({ values, handlers, className }) =>
<>
- {t('Enabled')}
-
+ {t('Enabled')}
+
-
+
- {t('Run_only_once_for_each_visitor')}
-
+ {t('Run_only_once_for_each_visitor')}
+
-
+
- {t('Name')}*
-
+ {t('Name')}*
+
-
- {nameError}
+
+ {nameError}
- {t('Description')}
-
+ {t('Description')}
+
-
+
- {t('Condition')}
-
+ {t('Condition')}
+
-
+
{conditionValuePlaceholder && (
-
+
-
+
)}
- {t('Action')}
-
+ {t('Action')}
+
-
-
+
+
+
{actionSender === 'custom' && (
-
+
-
+
)}
-
+
-
- {msgError}
+
+ {msgError}
>
);
diff --git a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx
index a15edebf8c16..6952b5b1dafe 100644
--- a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx
+++ b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslate.tsx
@@ -1,4 +1,4 @@
-import { FieldGroup, Field, ToggleSwitch, Select } from '@rocket.chat/fuselage';
+import { FieldGroup, Field, FieldLabel, FieldRow, ToggleSwitch, Select } from '@rocket.chat/fuselage';
import type { SelectOption } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, ChangeEvent } from 'react';
@@ -41,14 +41,14 @@ const AutoTranslate = ({
-
+
- {t('Automatic_Translation')}
-
+ {t('Automatic_Translation')}
+
- {t('Language')}
-
+ {t('Language')}
+
+
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx
index 1bf59d4e8de7..28b8b56a1df2 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/ExportMessages.tsx
@@ -1,5 +1,5 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Field, Select, FieldGroup } from '@rocket.chat/fuselage';
+import { Field, Select, FieldGroup, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useState, useMemo } from 'react';
@@ -40,10 +40,10 @@ const ExportMessages = () => {
- {t('Method')}
-
+ {t('Method')}
+
+
{type && type === 'file' && }
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx
index efbf60b69606..2085e11ea11d 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Field, Select, ButtonGroup, Button, FieldGroup, InputBox } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, Select, ButtonGroup, Button, FieldGroup, InputBox } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, MouseEventHandler } from 'react';
import React, { useMemo } from 'react';
@@ -65,22 +65,22 @@ const FileExport: FC = ({ onCancel, rid }) => {
return (
- {t('Date_From')}
-
+ {t('Date_From')}
+
-
+
- {t('Date_to')}
-
+ {t('Date_to')}
+
-
+
- {t('Output_format')}
-
+ {t('Output_format')}
+
-
+
diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx
index f44e94ed69cc..c8ae5a96300d 100644
--- a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx
+++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
-import { Field, TextInput, ButtonGroup, Button, Box, Icon, Callout, FieldGroup } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, TextInput, ButtonGroup, Button, Box, Icon, Callout, FieldGroup } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useUserRoom, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, MouseEventHandler } from 'react';
@@ -116,27 +116,27 @@ const MailExportForm: FC = ({ onCancel, rid }) => {
- {t('To_users')}
-
+ {t('To_users')}
+
-
+
- {t('To_additional_emails')}
-
+ {t('To_additional_emails')}
+
}
/>
-
+
- {t('Subject')}
-
+ {t('Subject')}
+
} />
-
+
{errorMessage && {errorMessage}}
diff --git a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
index bc25f538643d..f1f69f9299f3 100644
--- a/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
+++ b/apps/meteor/client/views/room/contextualBar/MessageSearchTab/components/MessageSearchForm.tsx
@@ -1,5 +1,5 @@
import type { IMessageSearchProvider } from '@rocket.chat/core-typings';
-import { Box, Field, Icon, TextInput, ToggleSwitch } from '@rocket.chat/fuselage';
+import { Box, Field, FieldLabel, FieldRow, FieldHint, Icon, TextInput, ToggleSwitch } from '@rocket.chat/fuselage';
import { useDebouncedCallback, useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
@@ -55,7 +55,7 @@ const MessageSearchForm = ({ provider, onSearch }: MessageSearchFormProps) => {
>
-
+
}
placeholder={t('Search_Messages')}
@@ -63,15 +63,15 @@ const MessageSearchForm = ({ provider, onSearch }: MessageSearchFormProps) => {
autoComplete='off'
{...register('searchText')}
/>
-
- {provider.description && }
+
+ {provider.description && }
{globalSearchEnabled && (
-
+
- {t('Global_Search')}
-
+ {t('Global_Search')}
+
)}
diff --git a/apps/meteor/client/views/room/contextualBar/NotificationPreferences/components/NotificationPreference.tsx b/apps/meteor/client/views/room/contextualBar/NotificationPreferences/components/NotificationPreference.tsx
index f187312e12ea..c89584bd1eef 100644
--- a/apps/meteor/client/views/room/contextualBar/NotificationPreferences/components/NotificationPreference.tsx
+++ b/apps/meteor/client/views/room/contextualBar/NotificationPreferences/components/NotificationPreference.tsx
@@ -1,5 +1,5 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Field, Select } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, Select } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React from 'react';
@@ -21,11 +21,11 @@ const NotificationPreference = ({
...props
}: NotificationPreferenceProps): ReactElement => (
- {name}
-
+ {name}
+
{children}
-
+
);
diff --git a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessages.tsx b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessages.tsx
index 9f64bbc6504b..709699ebd697 100644
--- a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessages.tsx
+++ b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessages.tsx
@@ -1,4 +1,4 @@
-import { Field, ButtonGroup, Button, CheckBox, Callout } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, ButtonGroup, Button, CheckBox, Callout } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -45,7 +45,7 @@ const PruneMessages = ({ callOutText, validateText, onClickClose, onClickPrune }
- {t('Only_from_users')}
+ {t('Only_from_users')}
-
+
- {t('Inclusive')}
-
+ {t('Inclusive')}
+
-
+
- {t('RetentionPolicy_DoNotPrunePinned')}
-
+ {t('RetentionPolicy_DoNotPrunePinned')}
+
-
+
- {t('RetentionPolicy_DoNotPruneDiscussion')}
-
+ {t('RetentionPolicy_DoNotPruneDiscussion')}
+
-
+
- {t('RetentionPolicy_DoNotPruneThreads')}
-
+ {t('RetentionPolicy_DoNotPruneThreads')}
+
-
+
- {t('Files_only')}
-
+ {t('Files_only')}
+
{callOutText && !validateText && {callOutText}}
{validateText && {validateText}}
diff --git a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesDateTimeRow.tsx b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesDateTimeRow.tsx
index e774311e5564..38bf1c233f5a 100644
--- a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesDateTimeRow.tsx
+++ b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesDateTimeRow.tsx
@@ -1,4 +1,4 @@
-import { Field, InputBox, Box, Margins } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, InputBox, Box, Margins } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React from 'react';
import { useFormContext } from 'react-hook-form';
@@ -13,7 +13,7 @@ const PruneMessagesDateTimeRow = ({ label, field }: PruneMessagesDateTimeRowProp
return (
- {label}
+ {label}
diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
index 7a8f0d1e699a..09eaca08cbc9 100644
--- a/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
+++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/AddUsers/AddUsers.tsx
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
-import { Field, Button, ButtonGroup, FieldGroup } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, Button, ButtonGroup, FieldGroup } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -65,7 +65,7 @@ const AddUsers = ({ rid, onClickBack, reload }: AddUsersProps): ReactElement =>
- {t('Choose_users')}
+ {t('Choose_users')}
{isRoomFederated(room) ? (
- {t('Expiration_(Days)')}
-
+ {t('Expiration_(Days)')}
+
)}
/>
-
+
- {t('Max_number_of_uses')}
-
+ {t('Max_number_of_uses')}
+
)}
/>
-
+
-
+
-
- {errors.description && {t('You_need_to_write_something')}}
+
+ {errors.description && {t('You_need_to_write_something')}}
diff --git a/apps/meteor/client/views/room/webdav/AddWebdavAccountModal.tsx b/apps/meteor/client/views/room/webdav/AddWebdavAccountModal.tsx
index cff38579dfdb..4709e5296997 100644
--- a/apps/meteor/client/views/room/webdav/AddWebdavAccountModal.tsx
+++ b/apps/meteor/client/views/room/webdav/AddWebdavAccountModal.tsx
@@ -1,5 +1,5 @@
import type { IWebdavAccountPayload } from '@rocket.chat/core-typings';
-import { Modal, Field, FieldGroup, TextInput, PasswordInput, Button, Box } from '@rocket.chat/fuselage';
+import { Modal, Field, FieldGroup, FieldLabel, FieldRow, FieldError, TextInput, PasswordInput, Button, Box } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { useState } from 'react';
@@ -47,31 +47,31 @@ const AddWebdavAccountModal = ({ onClose, onConfirm }: AddWebdavAccountModalProp
- {t('Name_optional')}
-
+ {t('Name_optional')}
+
-
+
- {t('Webdav_Server_URL')}
-
+ {t('Webdav_Server_URL')}
+
-
- {errors.serverURL && {t('error-the-field-is-required', { field: t('Webdav_Server_URL') })}}
+
+ {errors.serverURL && {t('error-the-field-is-required', { field: t('Webdav_Server_URL') })}}
- {t('Username')}
-
+ {t('Username')}
+
-
- {errors.username && {t('error-the-field-is-required', { field: t('Username') })}}
+
+ {errors.username && {t('error-the-field-is-required', { field: t('Username') })}}
- {t('Password')}
-
+ {t('Password')}
+
-
- {errors.password && {t('error-the-field-is-required', { field: t('Password') })}}
+
+ {errors.password && {t('error-the-field-is-required', { field: t('Password') })}}
diff --git a/apps/meteor/client/views/room/webdav/SaveToWebdavModal.tsx b/apps/meteor/client/views/room/webdav/SaveToWebdavModal.tsx
index d0de433be954..5cc2ccdd48b4 100644
--- a/apps/meteor/client/views/room/webdav/SaveToWebdavModal.tsx
+++ b/apps/meteor/client/views/room/webdav/SaveToWebdavModal.tsx
@@ -1,6 +1,6 @@
import type { MessageAttachment, IWebdavAccount } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Modal, Box, Button, FieldGroup, Field, Select, Throbber } from '@rocket.chat/fuselage';
+import { Modal, Box, Button, FieldGroup, Field, FieldLabel, FieldRow, FieldError, Select, Throbber } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useMethod, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -95,8 +95,8 @@ const SaveToWebdavModal = ({ onClose, data }: SaveToWebdavModalProps): ReactElem
{!isLoading && (
- {t('Select_a_webdav_server')}
-
+ {t('Select_a_webdav_server')}
+
)}
/>
-
- {errors.accountId && {t('Field_required')}}
+
+ {errors.accountId && {t('Field_required')}}
)}
diff --git a/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx b/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx
index e7dae770c444..fec006cec966 100644
--- a/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx
+++ b/apps/meteor/client/views/root/MainLayout/RegisterUsername.tsx
@@ -1,5 +1,5 @@
import type { IUser } from '@rocket.chat/core-typings';
-import { TextInput, ButtonGroup, Button, FieldGroup, Field, Box } from '@rocket.chat/fuselage';
+import { TextInput, ButtonGroup, Button, FieldGroup, Field, FieldLabel, FieldRow, FieldError, Box } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { VerticalWizardLayout, Form } from '@rocket.chat/layout';
import { CustomFieldsForm } from '@rocket.chat/ui-client';
@@ -102,11 +102,11 @@ const RegisterUsername = () => {
{!isLoading && (
- {t('Username')}
-
+ {t('Username')}
+
-
- {errors.username && {errors.username.message}}
+
+ {errors.username && {errors.username.message}}
)}
diff --git a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx
index 3bc913338094..4fa5828d462f 100644
--- a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx
+++ b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx
@@ -1,4 +1,4 @@
-import { Box, Button, Field, Modal } from '@rocket.chat/fuselage';
+import { Box, Button, Field, FieldLabel, Modal } from '@rocket.chat/fuselage';
import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo, useCallback } from 'react';
import { useForm, Controller } from 'react-hook-form';
@@ -48,7 +48,7 @@ const AddExistingModal = ({ onClose, teamId }: AddExistingModalProps) => {
- {t('Channels')}
+ {t('Channels')}
- {t('Business_Hour')}
-
+ {t('Business_Hour')}
+
-
+
);
};
diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx
index ed9a40e4bd0e..a6760e66797b 100644
--- a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx
+++ b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentForwarding.tsx
@@ -1,4 +1,4 @@
-import { Field, Box, PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, FieldHint, Box, PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage';
import type { PaginatedMultiSelectOption } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
@@ -35,8 +35,8 @@ export const DepartmentForwarding = ({ departmentId, value = [], handler, label
return (
- {t(label)}
-
+ {t(label)}
+
-
- {t('List_of_departments_for_forward_description')}
+
+ {t('List_of_departments_for_forward_description')}
);
};
diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/MaxChatsPerAgent.tsx b/apps/meteor/ee/client/omnichannel/additionalForms/MaxChatsPerAgent.tsx
index 924305e947fd..1894b1367c15 100644
--- a/apps/meteor/ee/client/omnichannel/additionalForms/MaxChatsPerAgent.tsx
+++ b/apps/meteor/ee/client/omnichannel/additionalForms/MaxChatsPerAgent.tsx
@@ -1,4 +1,4 @@
-import { NumberInput, Field } from '@rocket.chat/fuselage';
+import { NumberInput, Field, FieldLabel, FieldRow } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React from 'react';
@@ -13,10 +13,10 @@ const MaxChatsPerAgent: FC<{
return (
- {t('Max_number_of_chats_per_agent')}
-
+ {t('Max_number_of_chats_per_agent')}
+
-
+
);
};
diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/PrioritiesSelect.tsx b/apps/meteor/ee/client/omnichannel/additionalForms/PrioritiesSelect.tsx
index 072123de21e0..d4cd920cfa9a 100644
--- a/apps/meteor/ee/client/omnichannel/additionalForms/PrioritiesSelect.tsx
+++ b/apps/meteor/ee/client/omnichannel/additionalForms/PrioritiesSelect.tsx
@@ -1,7 +1,7 @@
import type { ILivechatPriority, Serialized } from '@rocket.chat/core-typings';
import { LivechatPriorityWeight } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Options, Box, Option, Field, SelectLegacy } from '@rocket.chat/fuselage';
+import { Options, Box, Option, Field, FieldLabel, FieldRow, SelectLegacy } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
@@ -47,8 +47,8 @@ export const PrioritiesSelect = ({ value = '', label, options, onChange }: Prior
return (
- {label}
-
+ {label}
+
{renderOption(label, value)}}
renderItem={({ label, value, ...props }) => }
/>
-
+
);
};
diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/SlaPoliciesSelect.tsx b/apps/meteor/ee/client/omnichannel/additionalForms/SlaPoliciesSelect.tsx
index 61e2f9505268..5d90d0eda169 100644
--- a/apps/meteor/ee/client/omnichannel/additionalForms/SlaPoliciesSelect.tsx
+++ b/apps/meteor/ee/client/omnichannel/additionalForms/SlaPoliciesSelect.tsx
@@ -1,6 +1,6 @@
import type { IOmnichannelServiceLevelAgreements, Serialized } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Field, Select } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, Select } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
type SlaPoliciesSelectProps = {
@@ -15,10 +15,10 @@ export const SlaPoliciesSelect = ({ value, label, options, onChange }: SlaPolici
return (
- {label}
-
+ {label}
+
+
);
};
diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/components/cannedResponseForm.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/components/cannedResponseForm.tsx
index 9560d61c28ae..5b9fb7433f7f 100644
--- a/apps/meteor/ee/client/omnichannel/cannedResponses/components/cannedResponseForm.tsx
+++ b/apps/meteor/ee/client/omnichannel/cannedResponses/components/cannedResponseForm.tsx
@@ -1,5 +1,5 @@
import { css } from '@rocket.chat/css-in-js';
-import { Box, Field, TextInput } from '@rocket.chat/fuselage';
+import { Box, Field, FieldLabel, FieldRow, FieldError, FieldDescription, TextInput } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React from 'react';
@@ -36,7 +36,7 @@ const CannedResponseForm: FC<{
return (
<>
- {t('Shortcut')}
+ {t('Shortcut')}
- {errors.shortcut}
+ {errors.shortcut}
-
+
{t('Message')}
{previewState ? t('Editor') : t('Preview')}
-
+
{previewState ? : }
@@ -63,21 +63,21 @@ const CannedResponseForm: FC<{
{(isManager || isMonitor) && (
<>
- {t('Sharing')}
- {radioDescription}
-
+ {t('Sharing')}
+ {radioDescription}
+
-
+
{scope === 'department' && (
- {t('Department')}
+ {t('Department')}
- {errors.departmentId}
+ {errors.departmentId}
)}
>
diff --git a/apps/meteor/ee/client/omnichannel/monitors/MonitorsTable.tsx b/apps/meteor/ee/client/omnichannel/monitors/MonitorsTable.tsx
index 3976e81e671c..b14e293b5d79 100644
--- a/apps/meteor/ee/client/omnichannel/monitors/MonitorsTable.tsx
+++ b/apps/meteor/ee/client/omnichannel/monitors/MonitorsTable.tsx
@@ -3,6 +3,8 @@ import {
Pagination,
Button,
Field,
+ FieldLabel,
+ FieldRow,
Box,
States,
StatesIcon,
@@ -125,13 +127,13 @@ const MonitorsTable = () => {
<>
- {t('Username')}
-
+ {t('Username')}
+
void} />
-
+
{((isSuccess && data?.monitors.length > 0) || queryHasChanged) && setText(text)} />}
diff --git a/apps/meteor/ee/client/omnichannel/priorities/PriorityEditForm.tsx b/apps/meteor/ee/client/omnichannel/priorities/PriorityEditForm.tsx
index 81d0fd72a2e6..042a6a7d2498 100644
--- a/apps/meteor/ee/client/omnichannel/priorities/PriorityEditForm.tsx
+++ b/apps/meteor/ee/client/omnichannel/priorities/PriorityEditForm.tsx
@@ -1,5 +1,5 @@
import type { ILivechatPriority, Serialized } from '@rocket.chat/core-typings';
-import { Field, Button, Box, ButtonGroup, Throbber } from '@rocket.chat/fuselage';
+import { Field, FieldError, Button, Box, ButtonGroup, Throbber } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
@@ -93,7 +93,7 @@ const PriorityEditForm = ({ data, onSave, onCancel }: PriorityEditFormProps): Re
/>
)}
/>
- {errors.name?.message}
+ {errors.name?.message}
diff --git a/apps/meteor/ee/client/omnichannel/slaPolicies/SlaEdit.tsx b/apps/meteor/ee/client/omnichannel/slaPolicies/SlaEdit.tsx
index 30bcd110a56e..83f2c4a36e72 100644
--- a/apps/meteor/ee/client/omnichannel/slaPolicies/SlaEdit.tsx
+++ b/apps/meteor/ee/client/omnichannel/slaPolicies/SlaEdit.tsx
@@ -1,5 +1,5 @@
import type { IOmnichannelServiceLevelAgreements, Serialized } from '@rocket.chat/core-typings';
-import { Field, TextInput, Button, Margins, Box, NumberInput } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, FieldError, TextInput, Button, Margins, Box, NumberInput } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRoute, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
@@ -78,32 +78,32 @@ function SlaEdit({ data, isNew, slaId, reload, ...props }: SlaEditProps): ReactE
return (
- {t('Name')}*
-
+ {t('Name')}*
+
-
- {errors.name?.message}
+
+ {errors.name?.message}
- {t('Description')}
-
+ {t('Description')}
+
-
+
- {t('Estimated_wait_time_in_minutes')}*
-
+ {t('Estimated_wait_time_in_minutes')}*
+
-
- {errors.dueTimeInMinutes?.message}
+
+ {errors.dueTimeInMinutes?.message}
-
+
{!isNew && (
@@ -116,7 +116,7 @@ function SlaEdit({ data, isNew, slaId, reload, ...props }: SlaEditProps): ReactE
-
+
);
diff --git a/apps/meteor/ee/client/views/audit/components/AuditForm.tsx b/apps/meteor/ee/client/views/audit/components/AuditForm.tsx
index 3888013d8732..f8fbb63e3903 100644
--- a/apps/meteor/ee/client/views/audit/components/AuditForm.tsx
+++ b/apps/meteor/ee/client/views/audit/components/AuditForm.tsx
@@ -1,5 +1,5 @@
import type { IAuditLog } from '@rocket.chat/core-typings';
-import { Box, Field, TextInput, Button, ButtonGroup } from '@rocket.chat/fuselage';
+import { Box, Field, FieldLabel, FieldRow, FieldError, TextInput, Button, ButtonGroup } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import { useController } from 'react-hook-form';
@@ -40,18 +40,18 @@ const AuditForm = ({ type, onSubmit }: AuditFormProps) => {
diff --git a/apps/meteor/ee/client/voip/modals/DeviceSettingsModal.tsx b/apps/meteor/ee/client/voip/modals/DeviceSettingsModal.tsx
index 4bda542fdb63..70202a42348b 100644
--- a/apps/meteor/ee/client/voip/modals/DeviceSettingsModal.tsx
+++ b/apps/meteor/ee/client/voip/modals/DeviceSettingsModal.tsx
@@ -1,5 +1,5 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Modal, Field, Select, Button, Box } from '@rocket.chat/fuselage';
+import { Modal, Field, FieldLabel, FieldRow, Select, Button, Box } from '@rocket.chat/fuselage';
import {
useTranslation,
useAvailableDevices,
@@ -77,8 +77,8 @@ const DeviceSettingsModal = (): ReactElement => {
)}
- {t('Microphone')}
-
+ {t('Microphone')}
+
{
)}
/>
-
+
- {t('Speakers')}
-
+ {t('Speakers')}
+
{
)}
/>
-
+
diff --git a/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx b/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx
index bbeaf3c338c6..979e04e808c7 100644
--- a/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx
+++ b/packages/fuselage-ui-kit/src/blocks/InputBlock.tsx
@@ -1,4 +1,10 @@
-import { Field } from '@rocket.chat/fuselage';
+import {
+ Field,
+ FieldLabel,
+ FieldRow,
+ FieldError,
+ FieldHint,
+} from '@rocket.chat/fuselage';
import * as UiKit from '@rocket.chat/ui-kit';
import type { ReactElement } from 'react';
import { memo, useMemo } from 'react';
@@ -28,19 +34,19 @@ const InputBlock = ({
return (
{block.label && (
-
+
{surfaceRenderer.renderTextObject(
block.label,
0,
UiKit.BlockContext.NONE
)}
-
+
)}
-
+
{surfaceRenderer.renderInputBlockElement(inputElement, 0)}
-
- {error && {error}}
- {block.hint && {block.hint}}
+
+ {error && {error}}
+ {block.hint && {block.hint}}
);
};
diff --git a/packages/ui-client/src/components/CustomFieldsForm.tsx b/packages/ui-client/src/components/CustomFieldsForm.tsx
index 9423456ebe8e..0d1ba2ca5b1b 100644
--- a/packages/ui-client/src/components/CustomFieldsForm.tsx
+++ b/packages/ui-client/src/components/CustomFieldsForm.tsx
@@ -1,6 +1,6 @@
import type { CustomFieldMetadata } from '@rocket.chat/core-typings';
import type { SelectOption } from '@rocket.chat/fuselage';
-import { Field, Select, TextInput } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, FieldError, Select, TextInput } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
@@ -73,10 +73,10 @@ const CustomField = ({
rules={{ minLength: props.minLength, maxLength: props.maxLength, validate: { required: validateRequired } }}
render={({ field }) => (
-
+
{label || t(name as TranslationKey)}
-
-
+
+
({
options={selectOptions as SelectOption[]}
flexGrow={1}
/>
-
-
+
+
{errorMessage}
-
+
)}
/>
diff --git a/packages/web-ui-registration/src/EmailConfirmationForm.tsx b/packages/web-ui-registration/src/EmailConfirmationForm.tsx
index 6cd1331ef396..285181b6aa35 100644
--- a/packages/web-ui-registration/src/EmailConfirmationForm.tsx
+++ b/packages/web-ui-registration/src/EmailConfirmationForm.tsx
@@ -1,4 +1,4 @@
-import { FieldGroup, TextInput, Field, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage';
+import { FieldGroup, TextInput, Field, FieldLabel, FieldRow, FieldError, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage';
import { Form, ActionLink } from '@rocket.chat/layout';
import type { ReactElement } from 'react';
import { useForm } from 'react-hook-form';
@@ -39,8 +39,8 @@ export const EmailConfirmationForm = ({ email, onBackToLogin }: { email?: string
- {t('registration.component.form.email')}*
-
+ {t('registration.component.form.email')}*
+
-
- {errors.email && {t('registration.component.form.requiredField')}}
+
+ {errors.email && {t('registration.component.form.requiredField')}}
{sendEmail.isSuccess && (
diff --git a/packages/web-ui-registration/src/LoginForm.tsx b/packages/web-ui-registration/src/LoginForm.tsx
index efc7e6268feb..74f4bbec2185 100644
--- a/packages/web-ui-registration/src/LoginForm.tsx
+++ b/packages/web-ui-registration/src/LoginForm.tsx
@@ -1,4 +1,16 @@
-import { FieldGroup, TextInput, Field, PasswordInput, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage';
+import {
+ FieldGroup,
+ TextInput,
+ Field,
+ FieldLabel,
+ FieldRow,
+ FieldError,
+ FieldLink,
+ PasswordInput,
+ ButtonGroup,
+ Button,
+ Callout,
+} from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { Form, ActionLink } from '@rocket.chat/layout';
import { useLoginWithPassword, useSetting } from '@rocket.chat/ui-contexts';
@@ -122,10 +134,10 @@ export const LoginForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRoute
-
+
{t('registration.component.form.emailOrUsername')}
-
-
+
+
-
+
{errors.username && (
-
+
{errors.username.message}
-
+
)}
-
+
{t('registration.component.form.password')}
-
-
+
+
-
+
{errors.password && (
-
+
{errors.password.message}
-
+
)}
{isResetPasswordAllowed && (
-
-
+ {
e.preventDefault();
@@ -174,8 +186,8 @@ export const LoginForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRoute
}}
>
Forgot your password?
-
-
+
+
)}
diff --git a/packages/web-ui-registration/src/RegisterForm.tsx b/packages/web-ui-registration/src/RegisterForm.tsx
index 6202916c87f2..df327f05ed61 100644
--- a/packages/web-ui-registration/src/RegisterForm.tsx
+++ b/packages/web-ui-registration/src/RegisterForm.tsx
@@ -1,5 +1,17 @@
/* eslint-disable complexity */
-import { FieldGroup, TextInput, Field, PasswordInput, ButtonGroup, Button, TextAreaInput, Callout } from '@rocket.chat/fuselage';
+import {
+ FieldGroup,
+ TextInput,
+ Field,
+ FieldLabel,
+ FieldRow,
+ FieldError,
+ PasswordInput,
+ ButtonGroup,
+ Button,
+ TextAreaInput,
+ Callout,
+} from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { Form, ActionLink } from '@rocket.chat/layout';
import { CustomFieldsForm, PasswordVerifier, useValidatePassword } from '@rocket.chat/ui-client';
@@ -123,10 +135,10 @@ export const RegisterForm = ({ setLoginRoute }: { setLoginRoute: DispatchLoginRo
-
+
{t('registration.component.form.name')}
-
-
+
+
-
+
{errors.name && (
-
+
{errors.name.message}
-
+
)}
-
+
{t('registration.component.form.email')}
-
-
+
+
-
+
{errors.email && (
-
+
{errors.email.message}
-
+
)}
-
+
{t('registration.component.form.username')}
-
-
+
+
-
+
{errors.username && (
-
+
{errors.username.message}
-
+
)}
-
+
{t('registration.component.form.password')}
-
-
+
+
-
+
{errors?.password && (
-
+
{errors.password.message}
-
+
)}
{requiresPasswordConfirmation && (
-
+
{t('registration.component.form.confirmPassword')}
-
-
+
+
-
+
{errors.passwordConfirmation && (
-
+
{errors.passwordConfirmation.message}
-
+
)}
)}
{manuallyApproveNewUsersRequired && (
-
+
{t('registration.component.form.reasonToJoin')}
-
-
+
+
-
+
{errors.reason && (
-
+
{errors.reason.message}
-
+
)}
)}
diff --git a/packages/web-ui-registration/src/template/FormSkeleton.tsx b/packages/web-ui-registration/src/template/FormSkeleton.tsx
index 44f073258fe6..31425a0847f2 100644
--- a/packages/web-ui-registration/src/template/FormSkeleton.tsx
+++ b/packages/web-ui-registration/src/template/FormSkeleton.tsx
@@ -1,4 +1,4 @@
-import { Field, InputBox, Skeleton } from '@rocket.chat/fuselage';
+import { Field, FieldLabel, FieldRow, FieldHint, FieldDescription, InputBox, Skeleton } from '@rocket.chat/fuselage';
import { Form } from '@rocket.chat/layout';
import type { ReactElement } from 'react';
@@ -12,18 +12,18 @@ const FormSkeleton = (): ReactElement => {
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
From abfb6b38119bea0d1b5df065bad5f0e0c70d0d8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?=
<60678893+juliajforesti@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:31:31 -0300
Subject: [PATCH 21/61] chore: `AccessibilityPage` hide Enterprise tag when is
EE (#30486)
Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com>
---
.../accessibility/AccessibilityPage.tsx | 25 +++++++++++--------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/apps/meteor/client/views/account/accessibility/AccessibilityPage.tsx b/apps/meteor/client/views/account/accessibility/AccessibilityPage.tsx
index ceb904613533..657548d5a1b9 100644
--- a/apps/meteor/client/views/account/accessibility/AccessibilityPage.tsx
+++ b/apps/meteor/client/views/account/accessibility/AccessibilityPage.tsx
@@ -38,8 +38,9 @@ const AccessibilityPage = () => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
- const { data: license } = useIsEnterprise();
const preferencesValues = useAccessiblityPreferencesValues();
+ const { data: license } = useIsEnterprise();
+ const isEnterprise = license?.isEnterprise;
const { themeAppearence } = preferencesValues;
const [, setPrevTheme] = useLocalStorage('prevTheme', themeAppearence);
@@ -102,14 +103,14 @@ const AccessibilityPage = () => {
{themes.map(({ id, title, description, ...item }, index) => {
- const communityDisabled = 'isEEOnly' in item && item.isEEOnly && !license?.isEnterprise;
+ const showCommunityUpsellTriggers = 'isEEOnly' in item && item.isEEOnly && !isEnterprise;
return (
{t.has(title) ? t(title) : title}
- {communityDisabled && (
+ {showCommunityUpsellTriggers && (
@@ -123,7 +124,7 @@ const AccessibilityPage = () => {
control={control}
name='themeAppearence'
render={({ field: { onChange, value, ref } }) => {
- if (communityDisabled) {
+ if (showCommunityUpsellTriggers) {
return (
{
{t('Mentions_with_@_symbol')}
-
-
-
- {t('Enterprise')}
-
-
+ {!isEnterprise && (
+
+
+
+ {t('Enterprise')}
+
+
+ )}
- {license?.isEnterprise ? (
+ {isEnterprise ? (
Date: Wed, 4 Oct 2023 17:29:43 -0300
Subject: [PATCH 22/61] chore: anchor text-decoration rules (#30485)
Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com>
---
.../app/theme/client/imports/general/base.css | 6 ----
apps/meteor/client/sidebar/Sidebar.tsx | 3 ++
.../PreferencesMessagesSection.tsx | 30 ++++---------------
apps/meteor/package.json | 2 +-
ee/packages/ui-theming/package.json | 2 +-
packages/fuselage-ui-kit/package.json | 2 +-
packages/gazzodown/package.json | 2 +-
packages/ui-client/package.json | 2 +-
packages/ui-composer/package.json | 2 +-
packages/ui-video-conf/package.json | 2 +-
packages/uikit-playground/package.json | 2 +-
yarn.lock | 24 +++++++--------
12 files changed, 28 insertions(+), 51 deletions(-)
diff --git a/apps/meteor/app/theme/client/imports/general/base.css b/apps/meteor/app/theme/client/imports/general/base.css
index 311327fb6f73..d1f4b8d11fb6 100644
--- a/apps/meteor/app/theme/client/imports/general/base.css
+++ b/apps/meteor/app/theme/client/imports/general/base.css
@@ -52,12 +52,6 @@ body {
a {
cursor: pointer;
- text-decoration: none;
-
- &:hover,
- &:active {
- text-decoration: none;
- }
}
button {
diff --git a/apps/meteor/client/sidebar/Sidebar.tsx b/apps/meteor/client/sidebar/Sidebar.tsx
index 9c7634872ed4..84c63eac01be 100644
--- a/apps/meteor/client/sidebar/Sidebar.tsx
+++ b/apps/meteor/client/sidebar/Sidebar.tsx
@@ -22,6 +22,9 @@ const Sidebar = () => {
const sideBarBackground = css`
background-color: ${Palette.surface['surface-tint']};
+ a {
+ text-decoration: none;
+ }
`;
return (
diff --git a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx
index 1c93126b0345..3edfabf23ac5 100644
--- a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx
+++ b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx
@@ -1,27 +1,13 @@
import type { SelectOption } from '@rocket.chat/fuselage';
-import {
- Field,
- FieldRow,
- FieldDescription,
- FieldLabel,
- FieldGroup,
- FieldHint,
- Accordion,
- Select,
- ToggleSwitch,
- Box,
-} from '@rocket.chat/fuselage';
+import { FieldRow, FieldLink, FieldHint, FieldLabel, Accordion, Field, Select, FieldGroup, ToggleSwitch, Box } from '@rocket.chat/fuselage';
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
-import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
+import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
const PreferencesMessagesSection = () => {
const t = useTranslation();
const { control } = useFormContext();
- const router = useRouter();
-
- const handleGoToAccessibilityPage = () => router.navigate('/account/accessibility-and-appearance');
const alsoSendThreadMessageToChannelOptions = useMemo(
(): SelectOption[] => [
@@ -116,9 +102,7 @@ const PreferencesMessagesSection = () => {
{t('Message_TimeFormat')}
-
- {t('Go_to_accessibility_and_appearance')}
-
+ {t('Go_to_accessibility_and_appearance')}
@@ -192,15 +176,11 @@ const PreferencesMessagesSection = () => {
{t('Hide_usernames')}
-
- {t('Go_to_accessibility_and_appearance')}
-
+ {t('Go_to_accessibility_and_appearance')}
{t('Hide_roles')}
-
- {t('Go_to_accessibility_and_appearance')}
-
+ {t('Go_to_accessibility_and_appearance')}
diff --git a/apps/meteor/package.json b/apps/meteor/package.json
index 8bf3e3fe886a..c1014a749158 100644
--- a/apps/meteor/package.json
+++ b/apps/meteor/package.json
@@ -236,7 +236,7 @@
"@rocket.chat/favicon": "workspace:^",
"@rocket.chat/forked-matrix-appservice-bridge": "^4.0.1",
"@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.2",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/fuselage-polyfills": "next",
"@rocket.chat/fuselage-toastbar": "next",
diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json
index a378fee8f896..f15c94365c06 100644
--- a/ee/packages/ui-theming/package.json
+++ b/ee/packages/ui-theming/package.json
@@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/css-in-js": "next",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/icons": "^0.32.0",
"@rocket.chat/ui-contexts": "workspace:~",
diff --git a/packages/fuselage-ui-kit/package.json b/packages/fuselage-ui-kit/package.json
index a376275d3b90..af89df400371 100644
--- a/packages/fuselage-ui-kit/package.json
+++ b/packages/fuselage-ui-kit/package.json
@@ -56,7 +56,7 @@
"devDependencies": {
"@rocket.chat/apps-engine": "1.41.0-alpha.290",
"@rocket.chat/eslint-config": "workspace:^",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/fuselage-polyfills": "next",
"@rocket.chat/icons": "^0.32.0",
diff --git a/packages/gazzodown/package.json b/packages/gazzodown/package.json
index 1b57545091f0..bf46cd4592ce 100644
--- a/packages/gazzodown/package.json
+++ b/packages/gazzodown/package.json
@@ -6,7 +6,7 @@
"@babel/core": "~7.22.9",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "next",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-tokens": "next",
"@rocket.chat/message-parser": "next",
"@rocket.chat/styled": "next",
diff --git a/packages/ui-client/package.json b/packages/ui-client/package.json
index ce183b859e06..7df166d9e283 100644
--- a/packages/ui-client/package.json
+++ b/packages/ui-client/package.json
@@ -5,7 +5,7 @@
"devDependencies": {
"@babel/core": "~7.22.9",
"@rocket.chat/css-in-js": "next",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/icons": "^0.32.0",
"@rocket.chat/mock-providers": "workspace:^",
diff --git a/packages/ui-composer/package.json b/packages/ui-composer/package.json
index abf79394f171..1d5d478e17cd 100644
--- a/packages/ui-composer/package.json
+++ b/packages/ui-composer/package.json
@@ -5,7 +5,7 @@
"devDependencies": {
"@babel/core": "~7.22.9",
"@rocket.chat/eslint-config": "workspace:^",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/icons": "^0.32.0",
"@storybook/addon-actions": "~6.5.16",
"@storybook/addon-docs": "~6.5.16",
diff --git a/packages/ui-video-conf/package.json b/packages/ui-video-conf/package.json
index ded2331aa09e..122a6a367903 100644
--- a/packages/ui-video-conf/package.json
+++ b/packages/ui-video-conf/package.json
@@ -6,7 +6,7 @@
"@babel/core": "~7.22.9",
"@rocket.chat/css-in-js": "next",
"@rocket.chat/eslint-config": "workspace:^",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/icons": "^0.32.0",
"@rocket.chat/styled": "next",
diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json
index eb69dc5e017c..a0e43c570f31 100644
--- a/packages/uikit-playground/package.json
+++ b/packages/uikit-playground/package.json
@@ -15,7 +15,7 @@
"@codemirror/tooltip": "^0.19.16",
"@lezer/highlight": "^1.1.6",
"@rocket.chat/css-in-js": "next",
- "@rocket.chat/fuselage": "^0.32.1",
+ "@rocket.chat/fuselage": "^0.32.2",
"@rocket.chat/fuselage-hooks": "^0.32.1",
"@rocket.chat/fuselage-polyfills": "next",
"@rocket.chat/fuselage-tokens": "next",
diff --git a/yarn.lock b/yarn.lock
index b915bb0f2e2a..a25d91c4dd3c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8273,7 +8273,7 @@ __metadata:
dependencies:
"@rocket.chat/apps-engine": 1.41.0-alpha.290
"@rocket.chat/eslint-config": "workspace:^"
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/fuselage-polyfills": next
"@rocket.chat/gazzodown": "workspace:^"
@@ -8322,9 +8322,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@rocket.chat/fuselage@npm:^0.32.1":
- version: 0.32.1
- resolution: "@rocket.chat/fuselage@npm:0.32.1"
+"@rocket.chat/fuselage@npm:^0.32.2":
+ version: 0.32.2
+ resolution: "@rocket.chat/fuselage@npm:0.32.2"
dependencies:
"@rocket.chat/css-in-js": ^0.31.25
"@rocket.chat/css-supports": ^0.31.25
@@ -8342,7 +8342,7 @@ __metadata:
react: ^17.0.2
react-dom: ^17.0.2
react-virtuoso: 1.2.4
- checksum: d3937be369a4b8e0d9849f5131a0143defcc313a38c2f4055a4d9bf2be6234c09244e71fde6c0adc90fbdfac8a0d572122e5fbbdac5c83656ac27063042ec94c
+ checksum: 28e80385961b090c71d0897c22c3c799ca05d30285456d96d3ca5ff2a1a4ba02362644084e611bd3f2a376acdf4c2e75180b8aee196a63969a7d6559abd73d79
languageName: node
linkType: hard
@@ -8353,7 +8353,7 @@ __metadata:
"@babel/core": ~7.22.9
"@rocket.chat/core-typings": "workspace:^"
"@rocket.chat/css-in-js": next
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-tokens": next
"@rocket.chat/message-parser": next
"@rocket.chat/styled": next
@@ -8705,7 +8705,7 @@ __metadata:
"@rocket.chat/favicon": "workspace:^"
"@rocket.chat/forked-matrix-appservice-bridge": ^4.0.1
"@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.2
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/fuselage-polyfills": next
"@rocket.chat/fuselage-toastbar": next
@@ -9568,7 +9568,7 @@ __metadata:
dependencies:
"@babel/core": ~7.22.9
"@rocket.chat/css-in-js": next
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/icons": ^0.32.0
"@rocket.chat/mock-providers": "workspace:^"
@@ -9619,7 +9619,7 @@ __metadata:
dependencies:
"@babel/core": ~7.22.9
"@rocket.chat/eslint-config": "workspace:^"
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/icons": ^0.32.0
"@storybook/addon-actions": ~6.5.16
"@storybook/addon-docs": ~6.5.16
@@ -9690,7 +9690,7 @@ __metadata:
resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming"
dependencies:
"@rocket.chat/css-in-js": next
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/icons": ^0.32.0
"@rocket.chat/ui-contexts": "workspace:~"
@@ -9733,7 +9733,7 @@ __metadata:
"@rocket.chat/css-in-js": next
"@rocket.chat/emitter": next
"@rocket.chat/eslint-config": "workspace:^"
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/icons": ^0.32.0
"@rocket.chat/styled": next
@@ -9776,7 +9776,7 @@ __metadata:
"@codemirror/tooltip": ^0.19.16
"@lezer/highlight": ^1.1.6
"@rocket.chat/css-in-js": next
- "@rocket.chat/fuselage": ^0.32.1
+ "@rocket.chat/fuselage": ^0.32.2
"@rocket.chat/fuselage-hooks": ^0.32.1
"@rocket.chat/fuselage-polyfills": next
"@rocket.chat/fuselage-tokens": next
From 8e89b5a3b0cc49f04134814ae0d3466719b31b2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?=
<60678893+juliajforesti@users.noreply.github.com>
Date: Wed, 4 Oct 2023 18:58:27 -0300
Subject: [PATCH 23/61] fix: font-disabled color (#30569)
---
.changeset/tiny-wolves-deliver.md | 5 +++++
ee/packages/ui-theming/src/palette.ts | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 .changeset/tiny-wolves-deliver.md
diff --git a/.changeset/tiny-wolves-deliver.md b/.changeset/tiny-wolves-deliver.md
new file mode 100644
index 000000000000..f89564a9b53c
--- /dev/null
+++ b/.changeset/tiny-wolves-deliver.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/ui-theming': patch
+---
+
+fix: light-theme font-disabled color
diff --git a/ee/packages/ui-theming/src/palette.ts b/ee/packages/ui-theming/src/palette.ts
index 4825beec0cff..39f8d3f2bfed 100644
--- a/ee/packages/ui-theming/src/palette.ts
+++ b/ee/packages/ui-theming/src/palette.ts
@@ -44,7 +44,7 @@ export const palette = [
description: 'These should be applied according to surfaces',
list: [
{ name: 'font-white', token: 'white', color: '#FFFFFF' },
- { name: 'font-disabled', token: 'N100', color: '#F7F8FA' },
+ { name: 'font-disabled', token: 'N500', color: '#CBCED1' },
{ name: 'font-annotation', token: 'N600', color: '#9EA2A8' },
{ name: 'font-hint', token: 'N700', color: '#6C727A' },
{ name: 'font-secondary-info', token: 'N700', color: '#6C727A' },
From 9aee190be8dc3935359b43ac62b6a8347ed642b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?=
<60678893+juliajforesti@users.noreply.github.com>
Date: Wed, 4 Oct 2023 19:51:21 -0300
Subject: [PATCH 24/61] chore: update mentions upsell modal content (#30503)
---
.../MentionsWithSymbolUpsellModal.tsx | 13 ++++++-------
.../packages/rocketchat-i18n/i18n/en.i18n.json | 7 +++----
.../public/images/mentions-upsell-modal.png | Bin 0 -> 9723 bytes
3 files changed, 9 insertions(+), 11 deletions(-)
create mode 100644 apps/meteor/public/images/mentions-upsell-modal.png
diff --git a/apps/meteor/client/views/account/accessibility/MentionsWithSymbolUpsellModal.tsx b/apps/meteor/client/views/account/accessibility/MentionsWithSymbolUpsellModal.tsx
index 8a998af348c0..b92ca74d0f6e 100644
--- a/apps/meteor/client/views/account/accessibility/MentionsWithSymbolUpsellModal.tsx
+++ b/apps/meteor/client/views/account/accessibility/MentionsWithSymbolUpsellModal.tsx
@@ -13,22 +13,21 @@ const MentionsWithSymbolUpsellModal = ({ onClose }: { onClose: () => void }) =>
if (!isAdmin) {
return (
);
}
return (
mZSyQpA^bk*|Me*(Gaq)W#6(6S!R$2l|3>s_O-0p34=tq$MgP<-@oti{xQcK_cix*-`90spYuFF=jWVf_Y8G8+4$K20N}i%
zcgq9-jyM7U1Hj5mA2}V;&rAP0=BH;B2mtJ-4nGV)b`BqXkRi}S_XbciEVx9!U~hXO4jsqYOB=VW0t-1b`VwIP&9Wkm
z%S^AGP3b+eEqM~V{c#WCuPxYjvUE>@Kk4Hc6z1-yZ5Q+FMxKBzz>{F35YtNd-$D;hi1&pQ|AHTI+9tL&7=})cecN(UUGV&us(F*(O7r@4tjs?8^!(X
z80jx0Cog@Cp94SOZ&Y!dPCkg7x-m(kV&e~7zt>R9-ukadCi(PZ4UP7(Z8&dt4I`Z^XK=COacXFGS}
z?8DQ)xA5OH<#}NZe$|1O43aqnLLcq_xi>*bVE@N^FNIcM<$Ilur=3lr&3bRskHH!l
z*|o{{pJfdpbYm&;Zh!ySb9B~6_aCe9MztwzeJoAWK8s#^Mmno$U;Kc+rF;V~X(?WN
zk=xow0DzDvt_J{7dE8M9^b2o4hHuVSxM8hIY$own+LfMo{hD1AMQ`N<7K?rfjG-?&
zaJ6APR$aLj7_83H|DI!X+ZO&o@B8}>s}H()0-E>*hWVYdg4v?2m&Yg#$BWE7JT%dGe-V$Ypv=81CCi@hOim=_B?>jZ
zZsxJXLJAF&^DpF31bI#T04stFQrZJ>{hl7L3T-V=jfMd(&=-%8WhJlgO3C=n6DaP>
zU?G+2h_k;)@6M%9L}X?9&U7_2il-;cZV(9PVG}`TP9^pgn^DIa3707j*p_V%P&u!|
zBSS-3d)?{0(ozx$(Q;SH9+KyEP(1qZ?URLkas-5kWavyuXna3T>YIDz{?eR}d0_R6
zj`dOvW?QAzaNpW*D+?Q(9j*8_&;?>JCD{$09wYpu2ZLDWhy9Rd+e_Q(n
zo_VNO9Y5(-$sYR1kKvWg)t9Z;@)KK?;?r7VLmT2!)RiNKhZsLCUsX}~?HS&F#iC8W
z&@T%IFC~oGS-qb44hl1pmFa;0p8)u(od2U2mjJ*qLB-Hnb*kFR9xe(#T{0x5cIY^7
z^-eH7RQuyRw+};Y)!h3n3noVZ=)QE7r7YmD-{v{XKu<+)pR+GpUtyyMKTDU>lOl+F
zjE9l=>Jj<>4g-~ol6qx@bgM3=u~$l-{2dt*0S@|v3miw5%Zo%0A7;J@A${Qe_pDpS
zA0-cCiKdRfbn~C{`13;j+3OM!JoEI|m>+v`-CLKFzKZL}*9?q;A;H1oB9Z&2wj!zP
zrb|d(roN?Nwe?i4>BxNY&y+3;V9G^I)F&@-+`!iI-m&X%-0~@LWCF{YGL7-qgpGq#
z7jf~!LZl<>oJ!h~Hv)B;o}O+$&;ytqU#(HoLbxM-oa97k?&WKWp=<0;&5k510>xZ>
zv#*V4yYoyAlBL8O@KOdF4B~i6(cy3k?Lh3v{ZY;6sOrYufnYick`I&4|-caVAM|*9K-GfTOWT&)y+>|#K^JTdSZh;-{Z4>V*TfB
z4=F?J-$kFPu^tQsvbzZR%6&;uyU?1I(1B6IQb`F{1kSb1KpN*Iq
z;(a~pE{zRY_WDU6JZECiIOSa;C9J+jb|_;Y-UraJ+K!a_r#nzZO+HPp8x(+>ZHr4<
zsbvcLt3~|wF||{^B!;#haRRIe>9X{S8R!&NzE)yZ7287Vd>&%M@my&jR6_Y!^Ku2t
zN3TajFDCnB)-h{_>Mu;*MGoij+ik=z2}SNr8Z?)y$@AmE(-OX#Ddt+s9=(;TFqE}7
z(n4I?I;3daC{H-u$H8cMj&B-&PUa#gL0nHTgW0xNxy$JN)QqhCLAZEUM~0FO^vl;1
z#xczf(z^4RD3mFKcah997e(vIkCqdIucv}GjIFMg96w&Es%vJAd9$5jksD5Q*Rft;
z3+TclR}JbK6d3r9yONvp(>84)U3WqPc011F<+8983z{Q;M&}1I6LwELi%gZP6=u8#
z^6CNFAD1#%nq76`A!i)DW7g-bUwLVy!f;?JHS?=x9KgYn#)WWoAZIKk)->6N7K^;=
zTj4FEg`*MZ#1iSPE<8)GTIXoc7P@S}`&MDFpK^+v*Z7Ms1LBZrhpyO<*aK98LL+8-
zp%>cQbHv5N+(%9gW^Y5TuC;ro40QX80XNsl>ur!T5g_OuY)t%<#9)WzkJWYgDFv11
zNQ4Kpi@MJkfa~PSwwCAoT)&`qHTDAu>N4(~_Xw`Bpw1h;5|wTj)|fG3ROJ)|WqVwA9V5^A
z9s>$GyQ2;FnH(2$Cp<6a=G};?eQ4^O$E<<(NG;R8_lmuHs|UvHJ+lP5G9#ZG!)$es
zUi++nOrA272fYFzCoTALRw=k=D+?B?ghBWp=Sq9x2szfKWu~BySSmltAGpq9>+UH*
z-)y4;EV~Sp!e8LrW1(b|IVVfiC+X=}Pe9GeG`FP42>q`hi7Hj_M}c0Y|T5c4(K4j?G^FZ)xG
z@7NIc-*OyDP_%2%IzUz~69S8`gcKGlH$Wd8bmdLuY?H8(K-BKLY?jSb*6tCh)r?1X
z>qnDY;IstAo0|zp10mut;R|u#CFzWajSS6nMG&j)Mx1NvlY^Q9z;xD`1)82EH&e
zj=x_>t6-FfzqU4=59;4E_5V~GVW~xcJRNjnuw>IpajjLx%%kS~1ZN>k(?uivkpx(u&(uJA*
zevZMqk=k}GcF(}IyZc0Lem)#5`h(dnL%?rZvQaCdkeQ=%Ky?7#Eyek`hBkqO8}9R$
zNN0*36y-$jM(Y$bd;daZZEWYRNYLhCDAA>2R|n3rpz}W!rp-%0Nv?5}FF5Lhu;ZLR
z>$ewEvc#l2^T;KYMB^9Ysrty$o`O4jD*+Boi&=76kb3dHdWHC(7`HBMnO9;#GVS>M
za^zo*jOz!)uWJvOR8*?jG|yT@3Vbng^xQS_YOa~t6G*t$I#zHCPn-mYG5g4VMVOB^
zI9SUVmo(jXNiW;Q#WgY;t$WmnU84=?dI`<*dQyYAd!G%ujtd#D?-zE-fRl8^+HjdR
zKPMG=ZnUH^+vD+Qki16jkf+EV-Y8Nh>LyC3s{ATRZG8Q?xE{gKtGMyDB%IYOMx68G
zIlFrN3S9g;sx)4qd(66ej&Sl|{|x8s7_A{|89ayDgkxLuA~D`i4+u9&%Sd%Gz>!ht
zs8usx3y8Jb@5H3wUcVUaqcmp*=(+VumrM4B{%rwIpnNE0l0ggJ2HmmDu90RE5}@D|
zzFkZ&hpeZXFHy>N&ZbP45kEF>LgXlgtc)s}vK@PyYe}C7Q+<*g=UJ89#DsDmALOwJ
z)K)K^%iWs$sS7Ox`>C=~`jv}XzJpc;aRxt?ACGTIoezeMlP9W=OkjTO#E(9E
zc<^pX(rCSelEHOAPSK4%|4gn|^NlZL*={SgJnv+|VEkH_5n!|QJ@x>HT5#2q^M4@8
z5M?ktZ2uyhI+_A+D~#%}@r6`JQ1@t+z|9e7VfkEDUAGS{*8qB1>6c+xaR5)z^w#u_
zPF!Fa;suT;@XOD#MjW(=zz#{q)=MpF#jFZtx@$X=fUjv>+Kln=gMy+8-Ax#8(`z@5
zRHb$*o#tS_k+a8{jBm0EPSnZj+@k-2_hzqQMF4{QbS5WZcGOCha-@gXQQ4=Xp_3s6
z;D~Cd7hRcN4=X3ykhHlY7>5&Y=pwg6%qYzp%Rn
zQ0t676D34=Z9i67Q)<4dpVmoCGRf|0^k&usAGItm7Uf;n=0W})
zNr}+EqwADS>??jd&Qb@R6}+_HzX&Hwa+F-;L1TS%zz$2KSy8Q)
z1dwcWTBnR>kd7@ng^uCwSW^r@InlJAG%gqnqK%rBZ_=qbU!wH>^DZi9DA7&JL{;CUJiPTs56fNA;MHgGxY(|}
zbdRWD@p2Jr!M;aQ7PA*xB+R*~i`lcd9s3Z9CVmQPEW+eHLiJ@R`3$4Vh9cz4m%O&!
zE_be|t3GwyQlr@c1;-8-SkPo`wXrkTNxT1|;cDAff|f-2dSq>=EeYC-RG0WM9r8tr
zOZ^qIw(^}<%=awDq8499k}6M@L}xMU^2>V^lxkchOS&(2c)N5x(b4
zJhG$cWIY8cG{enxC&~%(Y1Q$Rt0dtEzfxrXe{qBnpii$kM*PDmb{7BP)hsV)%$sC
z?|7HFf>3D&jJ-=0%x79ck$=LDh8
zo3xc*HD;9)GD?$TXJX)h84h}pes&y!sLw%(@%F&+?W~`%FePD_;!S4X)(>BwyU?>;
z3S@T-VdZ0G%rnTqCu^5Db%?s-ve&}>SJ&OG1kUhy7B|XS-e$b8%2At(Z%9j;U)u9X&QXTfZbyPqjr!
zP~je$UyYN>c%-X|Y-5vE(gpmT6qyBG5W5(6lP@fI0+}tstBpB#9pwn(Wk74XW;Zi?
zZ8wH)DWKPv!zRDJsdrKsY2S3}a=!f_udHwMj>w{YbD1CXQEkN4;^0=K>h!YGw3hww
z(aiP4dvv-E
z6L)cA3p$){2mR%8N=cfHnG?h-z`&8xFHR}78Z|=eO)0Nv@6i3@EVS`SCp2o?H5z89
z;lD-QdqoK{l&6epGho0+7UP%|R2w?`Ssb@*uR1}$7Bkzca_aCjE;-Q|w2t)y+3MG+
znbIizfS&x8%n5an6npJ^d1(z@>bn=`?Mrc?(1TYNVRtim=s?gO8GWkk31l)GO~Rz$
zPZgK%)pmLLzzok620}f`!>X&Fvr|42e0^>Yrc?}~9M@ypBhPWZo@vW=y6c5em>n=8
zSCEz#^*C9Z7Gw^#M9T5uU%yt#Oq4|E(}Bt`=&tatvC|tqRv;lFSuJvEtx9uh^~Lh+(PNJH5O8LTLq`IwX6zEj5Vjca
z{Y`ZK)!fB$Bj}GYfAJ0L-N13EwOl97Av^m@YtPy>Nr*-n?OhA<+Hl06n6|m0-<@Ef=%hWIRhUohMei@A&dD
zk7Y2+QUM;bangp(tryYjR$a1~nn5oSg)TQCGd@I5t`#1f{I+snGz%o%e?bjf^d@@Q+8ZvjK=i`V}ZX|`8wm8m)!YpvWk;N#=Roxae9WZJvO|pgi@5K
z)!5vyrZtFvDjxaS=fu8HDSf-Bbe)Sq#LN=^KD-*%I+99n7F3l4`h(nd`;b>o8c^2Z6VbDPRh1H*S;(rdb&3f_bkCjNLFL=$Fc
z70tOT7vhZKikBzY7zLGyuo(;9IgQb>W*ArKO4NO@tZX%OZnDmV*A*<}!qIo0HR4!I
z)OC)}7a@CSaf#MGClt&(ys3+bfECsjSoa7*JL3~AFb;~$Wt{V`&uBhlln{@ECr#)!TMK;;l
zV}arSt41w~YX6p;7L89&X&uQql&}8qobF4}jJX?Di2{du7VD8ZAw{~e{}B+)N4ZlI
ze@pmjvY&j^{;h6(qpL)}rTVx1{}d(vc_5%MMPIbX!=^4UPrK)KcKb*k7qA$g{3Ixh
zpRKF+J`J(HSs3PQjf05|Nm(0avqkjtuFR#ojdD-L$e9tJz3DOKYxFAQIAX1m61xCX
zs%=4B7O{aY6(zTd0WgEDZg
z$HrG`61c>7#6y%vXU&bLpD#If!3e8{khA<&E6-Zx>#}{4_tz#JDK81^KZ9IpgS+i>
zQgmJXKlmwH|Aqy?H^GG@uwyHvO2;NQCbVMnrpss&fNZ$V~yH+wGC_mUizpOk@AtJw*_^Twht3N@bA8EDQk>CwQROZ3w
z+P2Z{OFJ*06P!O_U3mH&pfYqV4|&-o<;=PRTV(hJ{0m!q$@cQO-HLEbW33*z%g;zj
zOKZyB>EfN*72<3C5Y$eUubgH*RJcEqh`^kcq-D_Npt!xzYj*vSp_9D2YqHPJnX4#LG@6
zR7=u6+#>3}WJX#Ys`$#h^TH1qzTp)yCbQ{6EmKdGu^;HhJ%tkyVmp-f|1=(1yQ4Jb
z*f0BaNYY5f2HY5gps_@PKUX29-E(oHjIswAvdb?HJK?XjI5U#Cj693$$`8CpUA;xb
za@i%?=xR?^rB{mFV4m9HKWJU3L8g8E-YnNaEaLcc-{o6&KNFmWZ2p9c!??^g#Ni$$
zMxo8T#(EY5gK0
zJo9^?iF&ZiXayl4GQ%Y!V)70(_=PRM8G5}M3k^ySH5Wbp)}k;7N6FHe|+=WCCJ*=o>-fgj1VHXIjlE*6h?EL`iCPsGbv951aR
z8~nw|eKFkW%^+8O9+!D-v|jsG{{4Kdd>)*GBn<+KOxs~B(I(1K>v8H{#M}Ked*tb+
zZ!_fA9T;x}DVLrTgG7^0UCX~av_8M--ULyCSI@P{bC|RHEnm51EC~MwYQdG4=s9JN
zuNNCzzO&oggZO5d=j3TAb;*rc=iF4=Uk<(UKz7zqWhdb>^$}Eiudv0@j|yu$Ln>5)5B-*_wu1_
z3zIZ^QfP%I117lIM5QJ{-8gt?Y{L>$51G;gqr7Znm%lrW%HrQ_3;KjrEpN%0&<=DY
zX;Z>dnzA>Da#JDcf5GSu;X<0YW;EXHlj4FO&wg!3XR7iRBxf;S{X@X26xcD!|0WCF
z!++?Mw=eWQljB}^fz?6CCoBSaADcJ!R2Yewp1ojBs$5lvH)*;r;
zxar}r4`zb&B_inhjEZrGjYLGQg_M?1Y!bO*QR*W3a~!SoZGMJ2!pFv>9$DrkrS=GQ
z8NQ1N22Jf^aXDst*^%BT%F9&=6Da6TQeVkANLtt#BXA8EwGOW-9VRMkG!HB{uC`%!
z1pc)-MtXLSIv_lS15fUL_YFT@
zIo$Myd|$&p(;m}iCqo0T^1tOwQ_dPbnWlQRD?`|R^Kow2*gJ5O85lkF%Pr(i~hMK>F&f|AuTD%3|B?Of1-5{fksrs}|3;leoV
z^cMCsj=?KVcW^&!Fzn#|hry
zK(j%$qsb$-;JZf6B{t(yEz(Io=sGWA
zZFq9cOmVYN{?~6U!y4iO)EUL<3JAQ+3UAx%10FWR@4%Qz3Xs+IOD#8LX?81E7feOq
zyYrW|l(xlB9nfh()Cc*?d&xZ+?715sWQ%K+0>^hQ%a$z%u8^`rgd&@~CP97Rol+0Y
zCAG+=J5!$`u)FFRw1cIDd(;-67gLehbY`1Jkmw$lH(
zA!IhNeYv@w5q^&v{XuQGgE$ylXP;WJHxwM68))pD%u@K%5K6`D;
z{&t9FQ2XdwKm-N~YF7wpBI~2J!MWye+|B0Pge@Z?wpj}o*+L)6l)_=tN}mo(ph^fk
z7=o%xCcQc}mZMotZ~ZmkR5^ouj`XUBu1soz`y20(c$O
literal 0
HcmV?d00001
From dea1fe919171a6c6b2568ce3cc8c2c5fd367ad56 Mon Sep 17 00:00:00 2001
From: Kevin Aleman
Date: Thu, 5 Oct 2023 11:56:04 -0600
Subject: [PATCH 25/61] feat: Save visitor's activity on agent's interaction
(#30222)
---
.changeset/brown-comics-cheat.md | 8 ++
.changeset/khaki-feet-dance.md | 5 +
.changeset/warm-melons-type.md | 7 +
.../app/apps/server/bridges/livechat.ts | 2 +-
.../app/apps/server/converters/rooms.js | 2 +-
.../app/apps/server/converters/visitors.js | 2 +-
.../server/functions/buildRegistrationData.ts | 8 +-
.../app/livechat/imports/server/rest/sms.js | 2 +-
.../app/livechat/server/api/lib/visitors.ts | 2 +-
.../app/livechat/server/api/v1/contact.ts | 2 +-
.../app/livechat/server/api/v1/message.ts | 2 +-
.../meteor/app/livechat/server/api/v1/room.ts | 2 +-
.../app/livechat/server/api/v1/visitor.ts | 6 +-
.../server/hooks/markRoomResponded.ts | 16 ++-
.../app/livechat/server/lib/Livechat.js | 26 ++--
.../app/livechat/server/lib/LivechatTyped.ts | 2 +-
.../app/livechat/server/methods/transfer.ts | 2 +-
.../app/statistics/server/lib/statistics.ts | 32 +++++
.../admin/info/DeploymentCard.stories.tsx | 4 +
.../admin/info/InformationPage.stories.tsx | 4 +
.../views/admin/info/UsageCard.stories.tsx | 4 +
.../hooks/onMessageSentParsePlaceholder.ts | 2 +-
.../hooks/handleNextAgentPreferredEvents.ts | 2 +-
.../server/hooks/resumeOnHold.ts | 2 +-
.../server/lib/VisitorInactivityMonitor.ts | 2 +-
.../server/methods/resumeOnHold.ts | 2 +-
.../EmailInbox/EmailInbox_Incoming.ts | 6 +-
.../server/lib/rooms/roomTypes/livechat.ts | 2 +-
.../meteor/server/models/raw/LivechatRooms.ts | 136 ++++++++++++++++++
.../server/models/raw/LivechatVisitors.ts | 86 ++++++++++-
.../end-to-end/api/livechat/09-visitors.ts | 24 +++-
.../src/OmnichannelTranscript.ts | 3 +-
packages/core-typings/src/ILivechatVisitor.ts | 2 +
packages/core-typings/src/IRoom.ts | 6 +-
packages/core-typings/src/IStats.ts | 5 +
.../core-typings/src/omnichannel/index.ts | 1 +
packages/core-typings/src/omnichannel/mac.ts | 5 +
.../src/models/ILivechatRoomsModel.ts | 12 +-
.../src/models/ILivechatVisitorsModel.ts | 12 ++
39 files changed, 404 insertions(+), 46 deletions(-)
create mode 100644 .changeset/brown-comics-cheat.md
create mode 100644 .changeset/khaki-feet-dance.md
create mode 100644 .changeset/warm-melons-type.md
create mode 100644 packages/core-typings/src/omnichannel/mac.ts
diff --git a/.changeset/brown-comics-cheat.md b/.changeset/brown-comics-cheat.md
new file mode 100644
index 000000000000..a7907979881b
--- /dev/null
+++ b/.changeset/brown-comics-cheat.md
@@ -0,0 +1,8 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/core-typings": patch
+"@rocket.chat/model-typings": patch
+---
+
+chore: Calculate & Store MAC stats
+Added new info to the stats: `omnichannelContactsBySource`, `uniqueContactsOfLastMonth`, `uniqueContactsOfLastWeek`, `uniqueContactsOfYesterday`
diff --git a/.changeset/khaki-feet-dance.md b/.changeset/khaki-feet-dance.md
new file mode 100644
index 000000000000..a419afa34143
--- /dev/null
+++ b/.changeset/khaki-feet-dance.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+feat: Save visitor's activity on agent's interaction
diff --git a/.changeset/warm-melons-type.md b/.changeset/warm-melons-type.md
new file mode 100644
index 000000000000..5b187b8a7f11
--- /dev/null
+++ b/.changeset/warm-melons-type.md
@@ -0,0 +1,7 @@
+---
+"@rocket.chat/meteor": patch
+"@rocket.chat/core-typings": patch
+"@rocket.chat/omnichannel-services": patch
+---
+
+feat: Disable and annonimize visitors instead of removing
diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts
index 71f7387e1aa5..76a0545c8801 100644
--- a/apps/meteor/app/apps/server/bridges/livechat.ts
+++ b/apps/meteor/app/apps/server/bridges/livechat.ts
@@ -223,7 +223,7 @@ export class AppLivechatBridge extends LivechatBridge {
}
return Promise.all(
- (await LivechatVisitors.find(query).toArray()).map(
+ (await LivechatVisitors.findEnabled(query).toArray()).map(
async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor),
),
);
diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js
index ae38feff5eff..905534212836 100644
--- a/apps/meteor/app/apps/server/converters/rooms.js
+++ b/apps/meteor/app/apps/server/converters/rooms.js
@@ -37,7 +37,7 @@ export class AppRoomsConverter {
let v;
if (room.visitor) {
- const visitor = await LivechatVisitors.findOneById(room.visitor.id);
+ const visitor = await LivechatVisitors.findOneEnabledById(room.visitor.id);
const { lastMessageTs, phone } = room.visitorChannelInfo;
diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js
index ba288c96d7b8..a9f5d450efad 100644
--- a/apps/meteor/app/apps/server/converters/visitors.js
+++ b/apps/meteor/app/apps/server/converters/visitors.js
@@ -9,7 +9,7 @@ export class AppVisitorsConverter {
}
async convertById(id) {
- const visitor = await LivechatVisitors.findOneById(id);
+ const visitor = await LivechatVisitors.findOneEnabledById(id);
return this.convertVisitor(visitor);
}
diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
index 2ad8ba29072a..f00718d2e779 100644
--- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
+++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
@@ -32,7 +32,10 @@ export type WorkspaceRegistrationData = {
setupComplete: boolean;
connectionDisable: boolean;
npsEnabled: string;
+ // TODO: Evaluate naming
MAC: number;
+ // activeContactsBillingMonth: number;
+ // activeContactsYesterday: number;
};
export async function buildWorkspaceRegistrationData(contactEmail: T): Promise> {
@@ -80,7 +83,8 @@ export async function buildWorkspaceRegistrationData {
}
const id = await LivechatTyped.registerGuest(data);
- return LivechatVisitors.findOneById(id);
+ return LivechatVisitors.findOneEnabledById(id);
};
const normalizeLocationSharing = (payload) => {
diff --git a/apps/meteor/app/livechat/server/api/lib/visitors.ts b/apps/meteor/app/livechat/server/api/lib/visitors.ts
index e559aecc892e..0abed5197d78 100644
--- a/apps/meteor/app/livechat/server/api/lib/visitors.ts
+++ b/apps/meteor/app/livechat/server/api/lib/visitors.ts
@@ -6,7 +6,7 @@ import { callbacks } from '../../../../../lib/callbacks';
import { canAccessRoomAsync } from '../../../../authorization/server/functions/canAccessRoom';
export async function findVisitorInfo({ visitorId }: { visitorId: IVisitor['_id'] }) {
- const visitor = await LivechatVisitors.findOneById(visitorId);
+ const visitor = await LivechatVisitors.findOneEnabledById(visitorId);
if (!visitor) {
throw new Error('visitor-not-found');
}
diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts
index 517acf33f137..57c1d117f1b0 100644
--- a/apps/meteor/app/livechat/server/api/v1/contact.ts
+++ b/apps/meteor/app/livechat/server/api/v1/contact.ts
@@ -33,7 +33,7 @@ API.v1.addRoute(
contactId: String,
});
- const contact = await LivechatVisitors.findOneById(this.queryParams.contactId);
+ const contact = await LivechatVisitors.findOneEnabledById(this.queryParams.contactId);
return API.v1.success({ contact });
},
diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts
index 2b6f4c00af53..104e2ece94d5 100644
--- a/apps/meteor/app/livechat/server/api/v1/message.ts
+++ b/apps/meteor/app/livechat/server/api/v1/message.ts
@@ -269,7 +269,7 @@ API.v1.addRoute(
guest.connectionData = normalizeHttpHeaderData(this.request.headers);
const visitorId = await LivechatTyped.registerGuest(guest);
- visitor = await LivechatVisitors.findOneById(visitorId);
+ visitor = await LivechatVisitors.findOneEnabledById(visitorId);
}
const sentMessages = await Promise.all(
diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts
index 0fe60248bfba..86629e636bf8 100644
--- a/apps/meteor/app/livechat/server/api/v1/room.ts
+++ b/apps/meteor/app/livechat/server/api/v1/room.ts
@@ -326,7 +326,7 @@ API.v1.addRoute(
throw new Error('This_conversation_is_already_closed');
}
- const guest = await LivechatVisitors.findOneById(room.v?._id);
+ const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
const transferedBy = this.user satisfies TransferByData;
transferData.transferredBy = normalizeTransferredByData(transferedBy, room);
if (transferData.userId) {
diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts
index 012b412639ea..ae9d1ea4fd83 100644
--- a/apps/meteor/app/livechat/server/api/v1/visitor.ts
+++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts
@@ -45,7 +45,7 @@ API.v1.addRoute('livechat/visitor', {
const visitorId = await LivechatTyped.registerGuest(guest);
- let visitor = await VisitorsRaw.findOneById(visitorId, {});
+ let visitor = await VisitorsRaw.findOneEnabledById(visitorId, {});
if (visitor) {
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
// If it's updating an existing visitor, it must also update the roomInfo
@@ -65,7 +65,7 @@ API.v1.addRoute('livechat/visitor', {
}
}
- visitor = await VisitorsRaw.findOneById(visitorId, {});
+ visitor = await VisitorsRaw.findOneEnabledById(visitorId, {});
}
if (!visitor) {
@@ -122,7 +122,7 @@ API.v1.addRoute('livechat/visitor/:token', {
const { _id } = visitor;
const result = await Livechat.removeGuest(_id);
- if (!result) {
+ if (!result.modifiedCount) {
throw new Meteor.Error('error-removing-visitor', 'An error ocurred while deleting visitor');
}
diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
index 5ebf924e7334..ad68fcf5ce5c 100644
--- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
+++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts
@@ -1,6 +1,7 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, isEditedMessage } from '@rocket.chat/core-typings';
-import { LivechatRooms } from '@rocket.chat/models';
+import { LivechatRooms, LivechatVisitors } from '@rocket.chat/models';
+import moment from 'moment';
import { callbacks } from '../../../../lib/callbacks';
@@ -26,6 +27,19 @@ callbacks.add(
return message;
}
+ // Return YYYY-MM from moment
+ const monthYear = moment().format('YYYY-MM');
+ const isVisitorActive = await LivechatVisitors.isVisitorActiveOnPeriod(room.v._id, monthYear);
+ if (!isVisitorActive) {
+ await LivechatVisitors.markVisitorActiveForPeriod(room.v._id, monthYear);
+ }
+
+ await LivechatRooms.markVisitorActiveForPeriod(room._id, monthYear);
+
+ if (room.responseBy) {
+ await LivechatRooms.setAgentLastMessageTs(room._id);
+ }
+
// check if room is yet awaiting for response from visitor
if (!room.waitingResponse) {
// case where agent sends second message or any subsequent message in a room before visitor responds to the first message
diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js
index 52138740e295..ffd3a29b229f 100644
--- a/apps/meteor/app/livechat/server/lib/Livechat.js
+++ b/apps/meteor/app/livechat/server/lib/Livechat.js
@@ -298,7 +298,7 @@ export const Livechat = {
async forwardOpenChats(userId) {
Livechat.logger.debug(`Transferring open chats for user ${userId}`);
for await (const room of LivechatRooms.findOpenByAgent(userId)) {
- const guest = await LivechatVisitors.findOneById(room.v._id);
+ const guest = await LivechatVisitors.findOneEnabledById(room.v._id);
const user = await Users.findOneById(userId);
const { _id, username, name } = user;
const transferredBy = normalizeTransferredByData({ _id, username, name }, room);
@@ -462,7 +462,7 @@ export const Livechat = {
},
async getLivechatRoomGuestInfo(room) {
- const visitor = await LivechatVisitors.findOneById(room.v._id);
+ const visitor = await LivechatVisitors.findOneEnabledById(room.v._id);
const agent = await Users.findOneById(room.servedBy && room.servedBy._id);
const ua = new UAParser();
@@ -604,16 +604,15 @@ export const Livechat = {
},
async removeGuest(_id) {
- check(_id, String);
- const guest = await LivechatVisitors.findOneById(_id, { projection: { _id: 1 } });
+ const guest = await LivechatVisitors.findOneEnabledById(_id, { projection: { _id: 1, token: 1 } });
if (!guest) {
throw new Meteor.Error('error-invalid-guest', 'Invalid guest', {
method: 'livechat:removeGuest',
});
}
- await this.cleanGuestHistory(_id);
- return LivechatVisitors.removeById(_id);
+ await this.cleanGuestHistory(guest);
+ return LivechatVisitors.disableById(_id);
},
async setUserStatusLivechat(userId, status) {
@@ -628,16 +627,13 @@ export const Livechat = {
return user;
},
- async cleanGuestHistory(_id) {
- const guest = await LivechatVisitors.findOneById(_id);
- if (!guest) {
- throw new Meteor.Error('error-invalid-guest', 'Invalid guest', {
- method: 'livechat:cleanGuestHistory',
- });
- }
-
+ async cleanGuestHistory(guest) {
const { token } = guest;
- check(token, String);
+
+ // This shouldn't be possible, but just in case
+ if (!token) {
+ throw new Error('error-invalid-guest');
+ }
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {});
const cursor = LivechatRooms.findByVisitorToken(token, extraQuery);
diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
index 1c60a257d319..c443bc7873c7 100644
--- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
+++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts
@@ -305,7 +305,7 @@ class LivechatClass {
!(await LivechatDepartment.findOneById>(guest.department, { projection: { _id: 1 } }))
) {
await LivechatVisitors.removeDepartmentById(guest._id);
- const tmpGuest = await LivechatVisitors.findOneById(guest._id);
+ const tmpGuest = await LivechatVisitors.findOneEnabledById(guest._id);
if (tmpGuest) {
guest = tmpGuest;
}
diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts
index 2dc796fc6c94..3817b10bf42b 100644
--- a/apps/meteor/app/livechat/server/methods/transfer.ts
+++ b/apps/meteor/app/livechat/server/methods/transfer.ts
@@ -58,7 +58,7 @@ Meteor.methods({
});
}
- const guest = await LivechatVisitors.findOneById(room.v?._id);
+ const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
const user = await Meteor.userAsync();
diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts
index 89b068c11341..c6180c189124 100644
--- a/apps/meteor/app/statistics/server/lib/statistics.ts
+++ b/apps/meteor/app/statistics/server/lib/statistics.ts
@@ -24,8 +24,10 @@ import {
LivechatCustomField,
Subscriptions,
Users,
+ LivechatRooms,
} from '@rocket.chat/models';
import { MongoInternals } from 'meteor/mongo';
+import moment from 'moment';
import { getStatistics as getEnterpriseStatistics } from '../../../../ee/app/license/server/getStatistics';
import { readSecondaryPreferred } from '../../../../server/database/readSecondaryPreferred';
@@ -269,6 +271,36 @@ export const statistics = {
}),
);
+ const defaultValue = { contactsCount: 0, conversationsCount: 0, sources: [] };
+ const billablePeriod = moment.utc().format('YYYY-MM');
+ statsPms.push(
+ LivechatRooms.getMACStatisticsForPeriod(billablePeriod).then(([result]) => {
+ statistics.omnichannelContactsBySource = result || defaultValue;
+ }),
+ );
+
+ const monthAgo = moment.utc().subtract(30, 'days').toDate();
+ const today = moment.utc().toDate();
+ statsPms.push(
+ LivechatRooms.getMACStatisticsBetweenDates(monthAgo, today).then(([result]) => {
+ statistics.uniqueContactsOfLastMonth = result || defaultValue;
+ }),
+ );
+
+ const weekAgo = moment.utc().subtract(7, 'days').toDate();
+ statsPms.push(
+ LivechatRooms.getMACStatisticsBetweenDates(weekAgo, today).then(([result]) => {
+ statistics.uniqueContactsOfLastWeek = result || defaultValue;
+ }),
+ );
+
+ const yesterday = moment.utc().subtract(1, 'days').toDate();
+ statsPms.push(
+ LivechatRooms.getMACStatisticsBetweenDates(yesterday, today).then(([result]) => {
+ statistics.uniqueContactsOfYesterday = result || defaultValue;
+ }),
+ );
+
// Message statistics
statistics.totalChannelMessages = (await Rooms.findByType('c', { projection: { msgs: 1 } }).toArray()).reduce(
function _countChannelMessages(num: number, room: IRoom) {
diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
index 98aa3a7073ff..7570cb71bd1c 100644
--- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
+++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
@@ -170,6 +170,10 @@ export default {
uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 },
+ omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] },
apps: {
engineVersion: 'x.y.z',
enabled: false,
diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
index a6ef0c8e9289..0a8e97710ca5 100644
--- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
+++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
@@ -200,6 +200,10 @@ export default {
uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 },
+ omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] },
apps: {
engineVersion: 'x.y.z',
enabled: false,
diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
index da49ee88fa6b..bfe56b6db2d8 100644
--- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
+++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
@@ -148,6 +148,10 @@ export default {
uniqueOSOfYesterday: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastWeek: { data: [], day: 0, month: 0, year: 0 },
uniqueOSOfLastMonth: { data: [], day: 0, month: 0, year: 0 },
+ omnichannelContactsBySource: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastMonth: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfLastWeek: { contactsCount: 0, conversationsCount: 0, sources: [] },
+ uniqueContactsOfYesterday: { contactsCount: 0, conversationsCount: 0, sources: [] },
apps: {
engineVersion: 'x.y.z',
enabled: false,
diff --git a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts
index 4d13df42c104..1cb016a33be6 100644
--- a/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts
+++ b/apps/meteor/ee/app/canned-responses/server/hooks/onMessageSentParsePlaceholder.ts
@@ -48,7 +48,7 @@ const handleBeforeSaveMessage = async (message: IMessage, room?: IOmnichannelRoo
}
const visitorId = room?.v?._id;
const agent = (await Users.findOneById(agentId, { projection: { name: 1, _id: 1, emails: 1 } })) || {};
- const visitor = visitorId && ((await LivechatVisitors.findOneById(visitorId, {})) || {});
+ const visitor = visitorId && ((await LivechatVisitors.findOneEnabledById(visitorId, {})) || {});
Object.keys(placeholderFields).map((field) => {
const templateKey = `{{${field}}}`;
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts
index b7026e3b2da4..21fae96e3555 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/handleNextAgentPreferredEvents.ts
@@ -90,7 +90,7 @@ callbacks.add(
}
const { _id: guestId } = defaultGuest;
- const guest = await LivechatVisitors.findOneById(guestId, {
+ const guest = await LivechatVisitors.findOneEnabledById(guestId, {
projection: { lastAgent: 1, token: 1, contactManager: 1 },
});
if (!guest) {
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts
index 8c9ce3e65aaf..8a04166e1b72 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts
@@ -12,7 +12,7 @@ const resumeOnHoldCommentAndUser = async (room: IOmnichannelRoom): Promise<{ com
v: { _id: visitorId },
_id: rid,
} = room;
- const visitor = await LivechatVisitors.findOneById>(visitorId, {
+ const visitor = await LivechatVisitors.findOneEnabledById>(visitorId, {
projection: { name: 1, username: 1 },
});
if (!visitor) {
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.ts
index 824296d1e673..12233a9127a8 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/VisitorInactivityMonitor.ts
@@ -148,7 +148,7 @@ export class VisitorInactivityMonitor {
}
private async getDefaultAbandonedCustomMessage(abandonmentAction: 'close' | 'on-hold', visitorId: string) {
- const visitor = await LivechatVisitors.findOneById>(visitorId, {
+ const visitor = await LivechatVisitors.findOneEnabledById>(visitorId, {
projection: {
name: 1,
username: 1,
diff --git a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts
index 2507dd683145..99a80b3b0b88 100644
--- a/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts
+++ b/apps/meteor/ee/app/livechat-enterprise/server/methods/resumeOnHold.ts
@@ -19,7 +19,7 @@ async function resolveOnHoldCommentInfo(options: { clientAction: boolean }, room
const {
v: { _id: visitorId },
} = room;
- const visitor = await LivechatVisitors.findOneById>(visitorId, {
+ const visitor = await LivechatVisitors.findOneEnabledById>(visitorId, {
projection: { name: 1, username: 1 },
});
if (!visitor) {
diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts
index d71190cb0b6d..939d91661650 100644
--- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts
+++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts
@@ -35,7 +35,7 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr
return guest;
}
await LivechatTyped.setDepartmentForGuest({ token: guest.token, department });
- return LivechatVisitors.findOneById(guest._id, {});
+ return LivechatVisitors.findOneEnabledById(guest._id, {});
}
return guest;
}
@@ -47,7 +47,9 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr
department,
});
- const newGuest = await LivechatVisitors.findOneById(userId);
+ const newGuest = await LivechatVisitors.findOneEnabledById(userId);
+ logger.debug(`Guest ${userId} for visitor ${email} created`);
+
if (newGuest) {
return newGuest;
}
diff --git a/apps/meteor/server/lib/rooms/roomTypes/livechat.ts b/apps/meteor/server/lib/rooms/roomTypes/livechat.ts
index 88393088541e..92d722ac2bb0 100644
--- a/apps/meteor/server/lib/rooms/roomTypes/livechat.ts
+++ b/apps/meteor/server/lib/rooms/roomTypes/livechat.ts
@@ -39,7 +39,7 @@ roomCoordinator.add(LivechatRoomType, {
},
async getMsgSender(senderId) {
- return LivechatVisitors.findOneById(senderId);
+ return LivechatVisitors.findOneEnabledById(senderId);
},
getReadReceiptsExtraData(message) {
diff --git a/apps/meteor/server/models/raw/LivechatRooms.ts b/apps/meteor/server/models/raw/LivechatRooms.ts
index 38eab9056586..bf44a51b7f64 100644
--- a/apps/meteor/server/models/raw/LivechatRooms.ts
+++ b/apps/meteor/server/models/raw/LivechatRooms.ts
@@ -8,6 +8,7 @@ import type {
ILivechatPriority,
IOmnichannelServiceLevelAgreements,
ReportResult,
+ MACStats,
} from '@rocket.chat/core-typings';
import { UserStatus } from '@rocket.chat/core-typings';
import type { ILivechatRoomsModel } from '@rocket.chat/model-typings';
@@ -74,6 +75,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive
{ key: { departmentId: 1, ts: 1 }, partialFilterExpression: { departmentId: { $exists: true }, t: 'l' } },
{ key: { 'tags.0': 1, 'ts': 1 }, partialFilterExpression: { 'tags.0': { $exists: true }, 't': 'l' } },
{ key: { servedBy: 1, ts: 1 }, partialFilterExpression: { servedBy: { $exists: true }, t: 'l' } },
+ { key: { 'v.activity': 1, 'ts': 1 }, partialFilterExpression: { 'v.activity': { $exists: true }, 't': 'l' } },
];
}
@@ -2448,6 +2450,140 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive
return this.updateOne(query, update);
}
+ markVisitorActiveForPeriod(rid: string, period: string): Promise {
+ const query = {
+ _id: rid,
+ };
+
+ const update = {
+ $addToSet: {
+ 'v.activity': period,
+ },
+ };
+
+ return this.updateOne(query, update);
+ }
+
+ async getMACStatisticsForPeriod(period: string): Promise {
+ return this.col
+ .aggregate([
+ {
+ $match: {
+ 't': 'l',
+ 'v.activity': period,
+ },
+ },
+ {
+ $group: {
+ _id: {
+ source: {
+ $ifNull: ['$source.alias', '$source.type'],
+ },
+ },
+ contactsCount: {
+ $addToSet: '$v._id',
+ },
+ conversationsCount: {
+ $sum: 1,
+ },
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ sources: {
+ $push: {
+ source: '$_id.source',
+ contactsCount: {
+ $size: '$contactsCount',
+ },
+ conversationsCount: '$conversationsCount',
+ },
+ },
+ totalContactsCount: {
+ $sum: {
+ $size: '$contactsCount',
+ },
+ },
+ totalConversationsCount: {
+ $sum: '$conversationsCount',
+ },
+ },
+ },
+ {
+ $project: {
+ _id: 0,
+ contactsCount: '$totalContactsCount',
+ conversationsCount: '$totalConversationsCount',
+ sources: 1,
+ },
+ },
+ ])
+ .toArray();
+ }
+
+ async getMACStatisticsBetweenDates(start: Date, end: Date): Promise {
+ return this.col
+ .aggregate([
+ {
+ $match: {
+ 't': 'l',
+ 'v.activity': { $exists: true },
+ 'ts': {
+ $gte: start,
+ $lt: end,
+ },
+ },
+ },
+ {
+ $group: {
+ _id: {
+ source: {
+ $ifNull: ['$source.alias', '$source.type'],
+ },
+ },
+ contactsCount: {
+ $addToSet: '$v._id',
+ },
+ conversationsCount: {
+ $sum: 1,
+ },
+ },
+ },
+ {
+ $group: {
+ _id: null,
+ sources: {
+ $push: {
+ source: '$_id.source',
+ contactsCount: {
+ $size: '$contactsCount',
+ },
+ conversationsCount: '$conversationsCount',
+ },
+ },
+ totalContactsCount: {
+ $sum: {
+ $size: '$contactsCount',
+ },
+ },
+ totalConversationsCount: {
+ $sum: '$conversationsCount',
+ },
+ },
+ },
+ {
+ $project: {
+ _id: 0,
+ contactsCount: '$totalContactsCount',
+ conversationsCount: '$totalConversationsCount',
+ sources: 1,
+ },
+ },
+ ])
+ .toArray();
+ }
+
async unsetAllPredictedVisitorAbandonment(): Promise {
throw new Error('Method not implemented.');
}
diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts
index 2df2ae09882b..7b478bab43d6 100644
--- a/apps/meteor/server/models/raw/LivechatVisitors.ts
+++ b/apps/meteor/server/models/raw/LivechatVisitors.ts
@@ -32,6 +32,8 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
{ key: { username: 1 } },
{ key: { 'contactMananger.username': 1 }, sparse: true },
{ key: { 'livechatData.$**': 1 } },
+ { key: { activity: 1 }, partialFilterExpression: { activity: { $exists: true } } },
+ { key: { disabled: 1 }, partialFilterExpression: { disabled: { $exists: true } } },
];
}
@@ -63,9 +65,29 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
return this.find(query, options);
}
+ findEnabled(query: Filter, options?: FindOptions): FindCursor {
+ return this.find(
+ {
+ ...query,
+ disabled: { $ne: true },
+ },
+ options,
+ );
+ }
+
+ findOneEnabledById(_id: string, options?: FindOptions): Promise {
+ const query = {
+ _id,
+ disabled: { $ne: true },
+ };
+
+ return this.findOne(query, options);
+ }
+
findVisitorByToken(token: string): FindCursor {
const query = {
token,
+ disabled: { $ne: true },
};
return this.find(query);
@@ -81,6 +103,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): FindCursor {
const query = {
+ disabled: { $ne: true },
_updatedAt: {
$gte: new Date(start),
$lt: new Date(end),
@@ -166,7 +189,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
options?: FindOptions,
): Promise>> {
if (!emailOrPhone && !nameOrUsername && allowedCustomFields.length === 0) {
- return this.findPaginated({}, options);
+ return this.findPaginated({ disabled: { $ne: true } }, options);
}
const query: Filter = {
@@ -193,6 +216,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
: []),
...allowedCustomFields.map((c: string) => ({ [`livechatData.${c}`]: nameOrUsername })),
],
+ disabled: { $ne: true },
};
return this.findPaginated(query, options);
@@ -204,7 +228,9 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
customFields?: { [key: string]: RegExp },
): Promise {
const query = Object.assign(
- {},
+ {
+ disabled: { $ne: true },
+ },
{
...(email && { visitorEmails: { address: email } }),
...(phone && { phone: { phoneNumber: phone } }),
@@ -212,7 +238,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
},
);
- if (Object.keys(query).length === 0) {
+ if (Object.keys(query).length === 1) {
return null;
}
@@ -365,6 +391,60 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL
},
);
}
+
+ isVisitorActiveOnPeriod(visitorId: string, period: string): Promise {
+ const query = {
+ _id: visitorId,
+ activity: period,
+ };
+
+ return this.findOne(query, { projection: { _id: 1 } }).then(Boolean);
+ }
+
+ markVisitorActiveForPeriod(visitorId: string, period: string): Promise {
+ const query = {
+ _id: visitorId,
+ };
+
+ const update = {
+ $push: {
+ activity: {
+ $each: [period],
+ $slice: -12,
+ },
+ },
+ };
+
+ return this.updateOne(query, update);
+ }
+
+ disableById(_id: string): Promise {
+ return this.updateOne(
+ { _id },
+ {
+ $set: { disabled: true },
+ $unset: {
+ department: 1,
+ contactManager: 1,
+ token: 1,
+ visitorEmails: 1,
+ phone: 1,
+ name: 1,
+ livechatData: 1,
+ lastChat: 1,
+ ip: 1,
+ host: 1,
+ userAgent: 1,
+ },
+ },
+ );
+ }
+
+ countVisitorsOnPeriod(period: string): Promise {
+ return this.countDocuments({
+ activity: period,
+ });
+ }
}
type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable };
diff --git a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
index a3ca544de20c..55ef4402da39 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
@@ -2,7 +2,8 @@ import { faker } from '@faker-js/faker';
import type { ILivechatVisitor } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it } from 'mocha';
-import type { Response } from 'supertest';
+import moment from 'moment';
+import { type Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { createCustomField, deleteCustomField } from '../../../data/livechat/custom-fields';
@@ -334,6 +335,27 @@ describe('LIVECHAT - visitors', function () {
});
});
+ it('should return visitor activity field when visitor was active on month', async () => {
+ // Activity is determined by a conversation in which an agent has engaged (sent a message)
+ // For a visitor to be considered active, they must have had a conversation in the last 30 days
+ const period = moment().format('YYYY-MM');
+ const { visitor, room } = await startANewLivechatRoomAndTakeIt();
+ // agent should send a message on the room
+ await request
+ .post(api('chat.sendMessage'))
+ .set(credentials)
+ .send({
+ message: {
+ rid: room._id,
+ msg: 'test',
+ },
+ });
+
+ const activeVisitor = await getLivechatVisitorByToken(visitor.token);
+ expect(activeVisitor).to.have.property('activity');
+ expect(activeVisitor.activity).to.include(period);
+ });
+
it("should return a 'error-removing-visitor' error when removeGuest's result is false", async () => {
await request
.delete(api('livechat/visitor/123'))
diff --git a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
index ce21e963911b..0e135d5ed263 100644
--- a/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
+++ b/ee/packages/omnichannel-services/src/OmnichannelTranscript.ts
@@ -304,7 +304,8 @@ export class OmnichannelTranscript extends ServiceClass implements IOmnichannelT
const messages = await this.getMessagesFromRoom({ rid: room._id });
const visitor =
- room.v && (await LivechatVisitors.findOneById(room.v._id, { projection: { _id: 1, name: 1, username: 1, visitorEmails: 1 } }));
+ room.v &&
+ (await LivechatVisitors.findOneEnabledById(room.v._id, { projection: { _id: 1, name: 1, username: 1, visitorEmails: 1 } }));
const agent =
room.servedBy && (await Users.findOneAgentById(room.servedBy._id, { projection: { _id: 1, name: 1, username: 1, utcOffset: 1 } }));
diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts
index d22ea36aa7c6..e80d63ab15d0 100644
--- a/packages/core-typings/src/ILivechatVisitor.ts
+++ b/packages/core-typings/src/ILivechatVisitor.ts
@@ -47,6 +47,8 @@ export interface ILivechatVisitor extends IRocketChatRecord {
contactManager?: {
username: string;
};
+ activity?: string[];
+ disabled?: boolean;
}
export interface ILivechatVisitorDTO {
diff --git a/packages/core-typings/src/IRoom.ts b/packages/core-typings/src/IRoom.ts
index 875dea70781e..523450e9594d 100644
--- a/packages/core-typings/src/IRoom.ts
+++ b/packages/core-typings/src/IRoom.ts
@@ -151,7 +151,11 @@ export enum OmnichannelSourceType {
export interface IOmnichannelGenericRoom extends Omit {
t: 'l' | 'v';
- v: Pick & { lastMessageTs?: Date; phone?: string };
+ v: Pick & {
+ lastMessageTs?: Date;
+ phone?: string;
+ activity?: string[];
+ };
email?: {
// Data used when the room is created from an email, via email Integration.
inbox: string;
diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts
index 6bbc2da81b74..70f9f638358f 100644
--- a/packages/core-typings/src/IStats.ts
+++ b/packages/core-typings/src/IStats.ts
@@ -3,6 +3,7 @@ import type { CpuInfo } from 'os';
import type { DeviceSessionAggregationResult, OSSessionAggregationResult, UserSessionAggregationResult } from './ISession';
import type { ISettingStatisticsObject } from './ISetting';
import type { ITeamStats } from './ITeam';
+import type { MACStats } from './omnichannel';
export interface IStats {
_id: string;
@@ -93,6 +94,10 @@ export interface IStats {
mongoStorageEngine: string;
pushQueue: number;
omnichannelSources: { [key: string]: number | string }[];
+ omnichannelContactsBySource: MACStats;
+ uniqueContactsOfLastMonth: MACStats;
+ uniqueContactsOfLastWeek: MACStats;
+ uniqueContactsOfYesterday: MACStats;
departments: number;
archivedDepartments: number;
routingAlgorithm: string;
diff --git a/packages/core-typings/src/omnichannel/index.ts b/packages/core-typings/src/omnichannel/index.ts
index 703cf3b4ca77..c6235175dafc 100644
--- a/packages/core-typings/src/omnichannel/index.ts
+++ b/packages/core-typings/src/omnichannel/index.ts
@@ -2,3 +2,4 @@ export * from './sms';
export * from './routing';
export * from './queue';
export * from './reports';
+export * from './mac';
diff --git a/packages/core-typings/src/omnichannel/mac.ts b/packages/core-typings/src/omnichannel/mac.ts
new file mode 100644
index 000000000000..8591edbb0287
--- /dev/null
+++ b/packages/core-typings/src/omnichannel/mac.ts
@@ -0,0 +1,5 @@
+export type MACStats = {
+ contactsCount: number;
+ conversationsCount: number;
+ sources: { source: string; contactsCount: number; conversationsCount: number }[];
+};
diff --git a/packages/model-typings/src/models/ILivechatRoomsModel.ts b/packages/model-typings/src/models/ILivechatRoomsModel.ts
index 68b72be33ba8..20100cbb4f61 100644
--- a/packages/model-typings/src/models/ILivechatRoomsModel.ts
+++ b/packages/model-typings/src/models/ILivechatRoomsModel.ts
@@ -1,4 +1,11 @@
-import type { IMessage, IOmnichannelRoom, IOmnichannelRoomClosingInfo, ISetting, ILivechatVisitor } from '@rocket.chat/core-typings';
+import type {
+ IMessage,
+ IOmnichannelRoom,
+ IOmnichannelRoomClosingInfo,
+ ISetting,
+ ILivechatVisitor,
+ MACStats,
+} from '@rocket.chat/core-typings';
import type { FindCursor, UpdateResult, AggregationCursor, Document, FindOptions, DeleteResult, Filter } from 'mongodb';
import type { FindPaginated } from '..';
@@ -234,4 +241,7 @@ export interface ILivechatRoomsModel extends IBaseModel {
setVisitorInactivityInSecondsById(roomId: string, visitorInactivity: any): Promise;
changeVisitorByRoomId(roomId: string, visitor: { _id: string; username: string; token: string }): Promise;
unarchiveOneById(roomId: string): Promise;
+ markVisitorActiveForPeriod(rid: string, period: string): Promise;
+ getMACStatisticsForPeriod(period: string): Promise;
+ getMACStatisticsBetweenDates(start: Date, end: Date): Promise;
}
diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts
index 370db511dadf..5c598c6a6a97 100644
--- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts
+++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts
@@ -48,4 +48,16 @@ export interface ILivechatVisitorsModel extends IBaseModel {
updateById(_id: string, update: UpdateFilter): Promise;
saveGuestEmailPhoneById(_id: string, emails: string[], phones: string[]): Promise;
+
+ isVisitorActiveOnPeriod(visitorId: string, period: string): Promise;
+
+ markVisitorActiveForPeriod(visitorId: string, period: string): Promise;
+
+ findOneEnabledById(_id: string, options?: FindOptions): Promise;
+
+ disableById(_id: string): Promise;
+
+ findEnabled(query: Filter, options?: FindOptions): FindCursor;
+
+ countVisitorsOnPeriod(period: string): Promise;
}
From aab18ef654085ee68cc0ef77242039db76995d92 Mon Sep 17 00:00:00 2001
From: Martin Schoeler
Date: Thu, 5 Oct 2023 16:35:38 -0300
Subject: [PATCH 26/61] chore: fix flaky agents & custom fields tables tests
(#30580)
---
.../views/omnichannel/agents/AgentsTable/AgentsTable.tsx | 2 +-
.../views/omnichannel/customFields/CustomFieldsTable.tsx | 6 ++++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
index a636433eaacb..215155e97cca 100644
--- a/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/agents/AgentsTable/AgentsTable.tsx
@@ -100,7 +100,7 @@ const AgentsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
)}
{isSuccess && data?.users.length > 0 && (
<>
-
+
{headers}
{data?.users.map((user) => (
diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
index ce7780181ddc..f9872c710a5e 100644
--- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
+++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx
@@ -43,7 +43,9 @@ const CustomFieldsTable = ({ reload }: { reload: MutableRefObject<() => void> })
);
const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields');
- const { data, isSuccess, isLoading, refetch } = useQuery(['livechat-customFields', query], async () => getCustomFields(query));
+ const { data, isSuccess, isLoading, refetch } = useQuery(['livechat-customFields', query, debouncedFilter], async () =>
+ getCustomFields(query),
+ );
const [defaultQuery] = useState(hashQueryKey([query]));
const queryHasChanged = defaultQuery !== hashQueryKey([query]);
@@ -105,7 +107,7 @@ const CustomFieldsTable = ({ reload }: { reload: MutableRefObject<() => void> })
{isSuccess && data.customFields.length > 0 && (
<>
-
+
{headers}
{data.customFields.map(({ label, _id, scope, visibility }) => (
From ec1b2b9846516b0559fa95fbc35f4503fcc6a9d7 Mon Sep 17 00:00:00 2001
From: Rodrigo Nascimento
Date: Thu, 5 Oct 2023 19:07:40 -0300
Subject: [PATCH 27/61] feat: Deployment Fingerprint (#30411)
---
.changeset/tidy-bears-applaud.md | 10 +++
apps/meteor/app/api/server/v1/misc.ts | 76 ++++++++++++++++++-
.../server/functions/buildRegistrationData.ts | 6 ++
.../app/statistics/server/lib/statistics.ts | 3 +
.../components/FingerprintChangeModal.tsx | 44 +++++++++++
.../FingerprintChangeModalConfirmation.tsx | 47 ++++++++++++
apps/meteor/client/startup/rootUrlChange.ts | 71 +++++++++++++++++
.../admin/info/DeploymentCard.stories.tsx | 2 +
.../admin/info/InformationPage.stories.tsx | 2 +
.../views/admin/info/UsageCard.stories.tsx | 2 +
.../rocketchat-i18n/i18n/en.i18n.json | 11 +++
apps/meteor/server/configureLogLevel.ts | 8 ++
apps/meteor/server/main.ts | 1 +
apps/meteor/server/models/raw/Settings.ts | 19 +++++
apps/meteor/server/settings/misc.ts | 65 +++++++++++++++-
apps/meteor/server/settings/omnichannel.ts | 2 +-
apps/meteor/server/settings/setup-wizard.ts | 2 +-
packages/core-typings/src/ISetting.ts | 2 +-
packages/core-typings/src/IStats.ts | 2 +
.../src/models/ISettingsModel.ts | 5 ++
packages/rest-typings/src/v1/misc.ts | 22 ++++++
21 files changed, 395 insertions(+), 7 deletions(-)
create mode 100644 .changeset/tidy-bears-applaud.md
create mode 100644 apps/meteor/client/components/FingerprintChangeModal.tsx
create mode 100644 apps/meteor/client/components/FingerprintChangeModalConfirmation.tsx
create mode 100644 apps/meteor/server/configureLogLevel.ts
diff --git a/.changeset/tidy-bears-applaud.md b/.changeset/tidy-bears-applaud.md
new file mode 100644
index 000000000000..cff12f3dc7d3
--- /dev/null
+++ b/.changeset/tidy-bears-applaud.md
@@ -0,0 +1,10 @@
+---
+"@rocket.chat/meteor": minor
+"@rocket.chat/core-typings": minor
+"@rocket.chat/model-typings": minor
+"@rocket.chat/rest-typings": minor
+---
+
+Create a deployment fingerprint to identify possible deployment changes caused by database cloning. A question to the admin will confirm if it's a regular deployment change or an intent of a new deployment and correct identification values as needed.
+The fingerprint is composed by `${siteUrl}${dbConnectionString}` and hashed via `sha256` in `base64`.
+An environment variable named `AUTO_ACCEPT_FINGERPRINT`, when set to `true`, can be used to auto-accept an expected fingerprint change as a regular deployment update.
diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts
index dec4da6bf87b..ae5a79719cce 100644
--- a/apps/meteor/app/api/server/v1/misc.ts
+++ b/apps/meteor/app/api/server/v1/misc.ts
@@ -1,13 +1,14 @@
import crypto from 'crypto';
import type { IUser } from '@rocket.chat/core-typings';
-import { Users } from '@rocket.chat/models';
+import { Settings, Users } from '@rocket.chat/models';
import {
isShieldSvgProps,
isSpotlightProps,
isDirectoryProps,
isMethodCallProps,
isMethodCallAnonProps,
+ isFingerprintProps,
isMeteorCall,
validateParamsPwGetPolicyRest,
} from '@rocket.chat/rest-typings';
@@ -16,6 +17,7 @@ import EJSON from 'ejson';
import { check } from 'meteor/check';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { Meteor } from 'meteor/meteor';
+import { v4 as uuidv4 } from 'uuid';
import { i18n } from '../../../../server/lib/i18n';
import { SystemLogger } from '../../../../server/lib/logger/system';
@@ -643,3 +645,75 @@ API.v1.addRoute(
},
},
);
+
+/**
+ * @openapi
+ * /api/v1/fingerprint:
+ * post:
+ * description: Update Fingerprint definition as a new workspace or update of configuration
+ * security:
+ * $ref: '#/security/authenticated'
+ * requestBody:
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * properties:
+ * setDeploymentAs:
+ * type: string
+ * example: |
+ * {
+ * "setDeploymentAs": "new-workspace"
+ * }
+ * responses:
+ * 200:
+ * description: Workspace successfully configured
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/ApiSuccessV1'
+ * default:
+ * description: Unexpected error
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/ApiFailureV1'
+ */
+API.v1.addRoute(
+ 'fingerprint',
+ {
+ authRequired: true,
+ validateParams: isFingerprintProps,
+ },
+ {
+ async post() {
+ check(this.bodyParams, {
+ setDeploymentAs: String,
+ });
+
+ if (this.bodyParams.setDeploymentAs === 'new-workspace') {
+ await Promise.all([
+ Settings.resetValueById('uniqueID', process.env.DEPLOYMENT_ID || uuidv4()),
+ // Settings.resetValueById('Cloud_Url'),
+ Settings.resetValueById('Cloud_Service_Agree_PrivacyTerms'),
+ Settings.resetValueById('Cloud_Workspace_Id'),
+ Settings.resetValueById('Cloud_Workspace_Name'),
+ Settings.resetValueById('Cloud_Workspace_Client_Id'),
+ Settings.resetValueById('Cloud_Workspace_Client_Secret'),
+ Settings.resetValueById('Cloud_Workspace_Client_Secret_Expires_At'),
+ Settings.resetValueById('Cloud_Workspace_Registration_Client_Uri'),
+ Settings.resetValueById('Cloud_Workspace_PublicKey'),
+ Settings.resetValueById('Cloud_Workspace_License'),
+ Settings.resetValueById('Cloud_Workspace_Had_Trial'),
+ Settings.resetValueById('Cloud_Workspace_Access_Token'),
+ Settings.resetValueById('Cloud_Workspace_Access_Token_Expires_At', new Date(0)),
+ Settings.resetValueById('Cloud_Workspace_Registration_State'),
+ ]);
+ }
+
+ await Settings.updateValueById('Deployment_FingerPrint_Verified', true);
+
+ return API.v1.success({});
+ },
+ },
+);
diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
index f00718d2e779..f887c9e6395c 100644
--- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
+++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts
@@ -7,6 +7,8 @@ import { LICENSE_VERSION } from '../license';
export type WorkspaceRegistrationData = {
uniqueId: string;
+ deploymentFingerprintHash: string;
+ deploymentFingerprintVerified: boolean;
workspaceId: string;
address: string;
contactName: string;
@@ -50,6 +52,8 @@ export async function buildWorkspaceRegistrationData('NPS_survey_enabled');
const agreePrivacyTerms = settings.get('Cloud_Service_Agree_PrivacyTerms');
const setupWizardState = settings.get('Show_Setup_Wizard');
+ const deploymentFingerprintHash = settings.get('Deployment_FingerPrint_Hash');
+ const deploymentFingerprintVerified = settings.get('Deployment_FingerPrint_Verified');
const firstUser = await Users.getOldest({ projection: { name: 1, emails: 1 } });
const contactName = firstUser?.name || '';
@@ -59,6 +63,8 @@ export async function buildWorkspaceRegistrationData void;
+ onCancel: () => void;
+ onClose: () => void;
+};
+
+const FingerprintChangeModal = ({ onConfirm, onCancel, onClose }: FingerprintChangeModalProps): ReactElement => {
+ const t = useTranslation();
+ return (
+
+
+
+
+ );
+};
+
+export default FingerprintChangeModal;
diff --git a/apps/meteor/client/components/FingerprintChangeModalConfirmation.tsx b/apps/meteor/client/components/FingerprintChangeModalConfirmation.tsx
new file mode 100644
index 000000000000..77718de0f441
--- /dev/null
+++ b/apps/meteor/client/components/FingerprintChangeModalConfirmation.tsx
@@ -0,0 +1,47 @@
+import { Box } from '@rocket.chat/fuselage';
+import { useTranslation } from '@rocket.chat/ui-contexts';
+import type { ReactElement } from 'react';
+import React from 'react';
+
+import GenericModal from './GenericModal';
+
+type FingerprintChangeModalConfirmationProps = {
+ onConfirm: () => void;
+ onCancel: () => void;
+ newWorkspace: boolean;
+};
+
+const FingerprintChangeModalConfirmation = ({
+ onConfirm,
+ onCancel,
+ newWorkspace,
+}: FingerprintChangeModalConfirmationProps): ReactElement => {
+ const t = useTranslation();
+ return (
+
+
+
+
+ );
+};
+
+export default FingerprintChangeModalConfirmation;
diff --git a/apps/meteor/client/startup/rootUrlChange.ts b/apps/meteor/client/startup/rootUrlChange.ts
index 45f98634a373..4e42874eba4a 100644
--- a/apps/meteor/client/startup/rootUrlChange.ts
+++ b/apps/meteor/client/startup/rootUrlChange.ts
@@ -6,6 +6,8 @@ import { Roles } from '../../app/models/client';
import { settings } from '../../app/settings/client';
import { sdk } from '../../app/utils/client/lib/SDKClient';
import { t } from '../../app/utils/lib/i18n';
+import FingerprintChangeModal from '../components/FingerprintChangeModal';
+import FingerprintChangeModalConfirmation from '../components/FingerprintChangeModalConfirmation';
import UrlChangeModal from '../components/UrlChangeModal';
import { imperativeModal } from '../lib/imperativeModal';
import { dispatchToastMessage } from '../lib/toast';
@@ -58,3 +60,72 @@ Meteor.startup(() => {
return c.stop();
});
});
+
+Meteor.startup(() => {
+ Tracker.autorun((c) => {
+ const userId = Meteor.userId();
+ if (!userId) {
+ return;
+ }
+
+ if (!Roles.ready.get() || !isSyncReady.get()) {
+ return;
+ }
+
+ if (hasRole(userId, 'admin') === false) {
+ return c.stop();
+ }
+
+ const deploymentFingerPrintVerified = settings.get('Deployment_FingerPrint_Verified');
+ if (deploymentFingerPrintVerified == null || deploymentFingerPrintVerified === true) {
+ return;
+ }
+
+ const updateWorkspace = (): void => {
+ imperativeModal.close();
+ void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'updated-configuration' }).then(() => {
+ dispatchToastMessage({ type: 'success', message: t('Configuration_update_confirmed') });
+ });
+ };
+
+ const setNewWorkspace = (): void => {
+ imperativeModal.close();
+ void sdk.rest.post('/v1/fingerprint', { setDeploymentAs: 'new-workspace' }).then(() => {
+ dispatchToastMessage({ type: 'success', message: t('New_workspace_confirmed') });
+ });
+ };
+
+ const openModal = (): void => {
+ imperativeModal.open({
+ component: FingerprintChangeModal,
+ props: {
+ onConfirm: () => {
+ imperativeModal.open({
+ component: FingerprintChangeModalConfirmation,
+ props: {
+ onConfirm: setNewWorkspace,
+ onCancel: openModal,
+ newWorkspace: true,
+ },
+ });
+ },
+ onCancel: () => {
+ imperativeModal.open({
+ component: FingerprintChangeModalConfirmation,
+ props: {
+ onConfirm: updateWorkspace,
+ onCancel: openModal,
+ newWorkspace: false,
+ },
+ });
+ },
+ onClose: imperativeModal.close,
+ },
+ });
+ };
+
+ openModal();
+
+ return c.stop();
+ });
+});
diff --git a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
index 7570cb71bd1c..41709d247f8b 100644
--- a/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
+++ b/apps/meteor/client/views/admin/info/DeploymentCard.stories.tsx
@@ -66,6 +66,8 @@ export default {
_id: '',
wizard: {},
uniqueId: '',
+ deploymentFingerprintHash: '',
+ deploymentFingerprintVerified: true,
installedAt: '',
version: '1.0.0',
tag: '',
diff --git a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
index 0a8e97710ca5..29c0c00d5814 100644
--- a/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
+++ b/apps/meteor/client/views/admin/info/InformationPage.stories.tsx
@@ -96,6 +96,8 @@ export default {
_id: '',
wizard: {},
uniqueId: '',
+ deploymentFingerprintHash: '',
+ deploymentFingerprintVerified: true,
installedAt: '',
version: '',
tag: '',
diff --git a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
index bfe56b6db2d8..a65e645b17d0 100644
--- a/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
+++ b/apps/meteor/client/views/admin/info/UsageCard.stories.tsx
@@ -44,6 +44,8 @@ export default {
_id: '',
wizard: {},
uniqueId: '',
+ deploymentFingerprintHash: '',
+ deploymentFingerprintVerified: true,
installedAt: '',
version: '',
tag: '',
diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
index ebc7039ca74d..45a06c20c116 100644
--- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -1106,8 +1106,14 @@
"Confirm_New_Password_Placeholder": "Please re-enter new password...",
"Confirm_password": "Confirm password",
"Confirm_your_password": "Confirm your password",
+ "Confirm_configuration_update_description": "Identification data and cloud connection data will be retained.
Warning: If this is actually a new workspace, please go back and select new workspace option to avoid communication conflicts.",
+ "Confirm_configuration_update": "Confirm configuration update",
+ "Confirm_new_workspace_description": "Identification data and cloud connection data will be reset.
Warning: License can be affected if changing workspace URL.",
+ "Confirm_new_workspace": "Confirm new workspace",
"Confirmation": "Confirmation",
"Configure_video_conference": "Configure conference call",
+ "Configuration_update_confirmed": "Configuration update confirmed",
+ "Configuration_update": "Configuration update",
"Connect": "Connect",
"Connected": "Connected",
"Connect_SSL_TLS": "Connect with SSL/TLS",
@@ -3652,6 +3658,8 @@
"New_version_available_(s)": "New version available (%s)",
"New_videocall_request": "New Video Call Request",
"New_visitor_navigation": "New Navigation: {{history}}",
+ "New_workspace_confirmed": "New workspace confirmed",
+ "New_workspace": "New workspace",
"Newer_than": "Newer than",
"Newer_than_may_not_exceed_Older_than": "\"Newer than\" may not exceed \"Older than\"",
"Nickname": "Nickname",
@@ -5253,6 +5261,9 @@
"Uninstall": "Uninstall",
"Units": "Units",
"Unit_removed": "Unit Removed",
+ "Unique_ID_change_detected_description": "Information that identifies this workspace has changed. This can happen when the site URL or database connection string are changed or when a new workspace is created from a copy of an existing database.
Would you like to proceed with a configuration update to the existing workspace or create a new workspace and unique ID?",
+ "Unique_ID_change_detected_learn_more_link": "Learn more",
+ "Unique_ID_change_detected": "Unique ID change detected",
"Unknown_Import_State": "Unknown Import State",
"Unknown_User": "Unknown User",
"Unlimited": "Unlimited",
diff --git a/apps/meteor/server/configureLogLevel.ts b/apps/meteor/server/configureLogLevel.ts
new file mode 100644
index 000000000000..b328d79a023a
--- /dev/null
+++ b/apps/meteor/server/configureLogLevel.ts
@@ -0,0 +1,8 @@
+import type { LogLevelSetting } from '@rocket.chat/logger';
+import { logLevel } from '@rocket.chat/logger';
+import { Settings } from '@rocket.chat/models';
+
+const LogLevel = await Settings.getValueById('Log_Level');
+if (LogLevel) {
+ logLevel.emit('changed', LogLevel as LogLevelSetting);
+}
diff --git a/apps/meteor/server/main.ts b/apps/meteor/server/main.ts
index 09edca701540..b9418fe43830 100644
--- a/apps/meteor/server/main.ts
+++ b/apps/meteor/server/main.ts
@@ -1,4 +1,5 @@
import './models/startup';
+import './configureLogLevel';
import './settings/index';
import '../ee/server/models/startup';
import './services/startup';
diff --git a/apps/meteor/server/models/raw/Settings.ts b/apps/meteor/server/models/raw/Settings.ts
index 3a5d150c0158..1154b7dfe630 100644
--- a/apps/meteor/server/models/raw/Settings.ts
+++ b/apps/meteor/server/models/raw/Settings.ts
@@ -69,6 +69,25 @@ export class SettingsRaw extends BaseRaw implements ISettingsModel {
return this.updateOne(query, update);
}
+ async resetValueById(
+ _id: string,
+ value?: (ISetting['value'] extends undefined ? never : ISetting['value']) | null,
+ ): Promise {
+ if (value == null) {
+ const record = await this.findOneById(_id);
+ if (record) {
+ const prop = record.valueSource || 'packageValue';
+ value = record[prop];
+ }
+ }
+
+ if (value == null) {
+ return;
+ }
+
+ return this.updateValueById(_id, value);
+ }
+
async incrementValueById(_id: ISetting['_id'], value = 1): Promise {
return this.updateOne(
{
diff --git a/apps/meteor/server/settings/misc.ts b/apps/meteor/server/settings/misc.ts
index 127d0e6e97ba..fa7b6bbde3d0 100644
--- a/apps/meteor/server/settings/misc.ts
+++ b/apps/meteor/server/settings/misc.ts
@@ -1,11 +1,70 @@
-import { Random } from '@rocket.chat/random';
+import crypto from 'crypto';
-import { settingsRegistry } from '../../app/settings/server';
+import { Logger } from '@rocket.chat/logger';
+import { Settings } from '@rocket.chat/models';
+import { v4 as uuidv4 } from 'uuid';
+
+import { settingsRegistry, settings } from '../../app/settings/server';
+
+const logger = new Logger('FingerPrint');
+
+const generateFingerprint = function () {
+ const siteUrl = settings.get('Site_Url');
+ const dbConnectionString = process.env.MONGO_URL;
+
+ const fingerprint = `${siteUrl}${dbConnectionString}`;
+ return crypto.createHash('sha256').update(fingerprint).digest('base64');
+};
+
+const updateFingerprint = async function (fingerprint: string, verified: boolean) {
+ await Settings.updateValueById('Deployment_FingerPrint_Hash', fingerprint);
+
+ await Settings.updateValueById('Deployment_FingerPrint_Verified', verified);
+};
+
+const verifyFingerPrint = async function () {
+ const DeploymentFingerPrintRecordHash = await Settings.getValueById('Deployment_FingerPrint_Hash');
+
+ const fingerprint = generateFingerprint();
+
+ if (!DeploymentFingerPrintRecordHash) {
+ logger.info('Generating fingerprint for the first time', fingerprint);
+ await updateFingerprint(fingerprint, true);
+ return;
+ }
+
+ if (DeploymentFingerPrintRecordHash === fingerprint) {
+ return;
+ }
+
+ if (process.env.AUTO_ACCEPT_FINGERPRINT === 'true') {
+ logger.info('Updating fingerprint as AUTO_ACCEPT_FINGERPRINT is true', fingerprint);
+ await updateFingerprint(fingerprint, true);
+ }
+
+ logger.warn('Updating fingerprint as pending for admin verification', fingerprint);
+ await updateFingerprint(fingerprint, false);
+};
+
+settings.watch('Site_Url', () => {
+ void verifyFingerPrint();
+});
// Insert server unique id if it doesn't exist
export const createMiscSettings = async () => {
- await settingsRegistry.add('uniqueID', process.env.DEPLOYMENT_ID || Random.id(), {
+ await settingsRegistry.add('uniqueID', process.env.DEPLOYMENT_ID || uuidv4(), {
+ public: true,
+ });
+
+ await settingsRegistry.add('Deployment_FingerPrint_Hash', '', {
+ public: false,
+ readonly: true,
+ });
+
+ await settingsRegistry.add('Deployment_FingerPrint_Verified', false, {
+ type: 'boolean',
public: true,
+ readonly: true,
});
await settingsRegistry.add('Initial_Channel_Created', false, {
diff --git a/apps/meteor/server/settings/omnichannel.ts b/apps/meteor/server/settings/omnichannel.ts
index fe5d27c1e677..cc9da5474862 100644
--- a/apps/meteor/server/settings/omnichannel.ts
+++ b/apps/meteor/server/settings/omnichannel.ts
@@ -778,7 +778,7 @@ await settingsRegistry.addGroup('SMS', async function () {
i18nLabel: 'Mobex_sms_gateway_password',
});
await this.add('SMS_Mobex_from_number', '', {
- type: 'int',
+ type: 'string',
enableQuery: {
_id: 'SMS_Service',
value: 'mobex',
diff --git a/apps/meteor/server/settings/setup-wizard.ts b/apps/meteor/server/settings/setup-wizard.ts
index 62da3f1471cf..9799c2017afd 100644
--- a/apps/meteor/server/settings/setup-wizard.ts
+++ b/apps/meteor/server/settings/setup-wizard.ts
@@ -1270,7 +1270,7 @@ export const createSetupWSettings = () =>
secret: true,
});
- await this.add('Cloud_Workspace_Client_Secret_Expires_At', '', {
+ await this.add('Cloud_Workspace_Client_Secret_Expires_At', 0, {
type: 'int',
hidden: true,
readonly: true,
diff --git a/packages/core-typings/src/ISetting.ts b/packages/core-typings/src/ISetting.ts
index 0766d782980c..7b3aaa5cf2a4 100644
--- a/packages/core-typings/src/ISetting.ts
+++ b/packages/core-typings/src/ISetting.ts
@@ -72,7 +72,7 @@ export interface ISettingBase {
hidden?: boolean;
modules?: Array;
invalidValue?: SettingValue;
- valueSource?: string;
+ valueSource?: 'packageValue' | 'processEnvValue';
secret?: boolean;
i18nDescription?: string;
autocomplete?: boolean;
diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts
index 70f9f638358f..443cbfb23957 100644
--- a/packages/core-typings/src/IStats.ts
+++ b/packages/core-typings/src/IStats.ts
@@ -17,6 +17,8 @@ export interface IStats {
registerServer?: boolean;
};
uniqueId: string;
+ deploymentFingerprintHash: string;
+ deploymentFingerprintVerified: boolean;
installedAt?: string;
version?: string;
tag?: string;
diff --git a/packages/model-typings/src/models/ISettingsModel.ts b/packages/model-typings/src/models/ISettingsModel.ts
index 9dc2005867fa..d382d4853a4b 100644
--- a/packages/model-typings/src/models/ISettingsModel.ts
+++ b/packages/model-typings/src/models/ISettingsModel.ts
@@ -17,6 +17,11 @@ export interface ISettingsModel extends IBaseModel {
value: (ISetting['value'] extends undefined ? never : ISetting['value']) | null,
): Promise;
+ resetValueById(
+ _id: string,
+ value?: (ISetting['value'] extends undefined ? never : ISetting['value']) | null,
+ ): Promise;
+
incrementValueById(_id: ISetting['_id'], value?: number): Promise;
updateOptionsById(
diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts
index 4af37334e287..804b72a763de 100644
--- a/packages/rest-typings/src/v1/misc.ts
+++ b/packages/rest-typings/src/v1/misc.ts
@@ -164,6 +164,22 @@ const MethodCallAnonSchema = {
export const isMethodCallAnonProps = ajv.compile(MethodCallAnonSchema);
+type Fingerprint = { setDeploymentAs: 'new-workspace' | 'updated-configuration' };
+
+const FingerprintSchema = {
+ type: 'object',
+ properties: {
+ setDeploymentAs: {
+ type: 'string',
+ enum: ['new-workspace', 'updated-configuration'],
+ },
+ },
+ required: ['setDeploymentAs'],
+ additionalProperties: false,
+};
+
+export const isFingerprintProps = ajv.compile(FingerprintSchema);
+
type PwGetPolicyReset = { token: string };
const PwGetPolicyResetSchema = {
@@ -229,6 +245,12 @@ export type MiscEndpoints = {
};
};
+ '/v1/fingerprint': {
+ POST: (params: Fingerprint) => {
+ success: boolean;
+ };
+ };
+
'/v1/smtp.check': {
GET: () => {
isSMTPConfigured: boolean;
From 134b71df44d759d29df1f8faf3e915119ac5e3de Mon Sep 17 00:00:00 2001
From: Kevin Aleman
Date: Thu, 5 Oct 2023 16:22:30 -0600
Subject: [PATCH 28/61] feat: Monitors able to forward chats without joining
(#30549)
---
.changeset/selfish-hounds-pay.md | 5 +++++
.../Omnichannel/QuickActions/hooks/useQuickActions.tsx | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 .changeset/selfish-hounds-pay.md
diff --git a/.changeset/selfish-hounds-pay.md b/.changeset/selfish-hounds-pay.md
new file mode 100644
index 000000000000..3ca321bd392f
--- /dev/null
+++ b/.changeset/selfish-hounds-pay.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+fix: Monitors now able to forward a chat without taking it first
diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
index 7f376341992d..54ce71bd80ec 100644
--- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
+++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
@@ -300,8 +300,9 @@ export const useQuickActions = (): {
const manualOnHoldAllowed = useSetting('Livechat_allow_manual_on_hold');
const hasManagerRole = useRole('livechat-manager');
+ const hasMonitorRole = useRole('livechat-monitor');
- const roomOpen = room?.open && (room.u?._id === uid || hasManagerRole) && room?.lastMessage?.t !== 'livechat-close';
+ const roomOpen = room?.open && (room.u?._id === uid || hasManagerRole || hasMonitorRole) && room?.lastMessage?.t !== 'livechat-close';
const canMoveQueue = !!omnichannelRouteConfig?.returnQueue && room?.u !== undefined;
const canForwardGuest = usePermission('transfer-livechat-guest');
const canSendTranscriptEmail = usePermission('send-omnichannel-chat-transcript');
From 6d4618335c125a122a2136082e02132e2d515649 Mon Sep 17 00:00:00 2001
From: Diego Sampaio
Date: Thu, 5 Oct 2023 20:17:34 -0300
Subject: [PATCH 29/61] chore: Improve `groups.create` endpoint for large
amounts of members (#30499)
---
apps/meteor/app/api/server/v1/channels.ts | 10 +-
apps/meteor/app/api/server/v1/groups.ts | 44 +++---
apps/meteor/app/apps/server/bridges/rooms.ts | 6 +-
.../server/methods/createDiscussion.ts | 2 +-
.../server/classes/ImportDataConverter.ts | 6 +-
.../functions/addUserToDefaultChannels.ts | 2 +-
.../app/lib/server/functions/addUserToRoom.ts | 2 +-
.../app/lib/server/functions/createRoom.ts | 140 +++++++++++-------
.../app/lib/server/methods/createChannel.ts | 5 +-
.../lib/server/methods/createPrivateGroup.ts | 28 ++--
.../meteor-accounts-saml/server/lib/SAML.ts | 4 +-
.../app/slashcommands-create/server/server.ts | 8 +-
.../slashcommands-inviteall/server/server.ts | 5 +-
.../utils/lib/getDefaultSubscriptionPref.ts | 4 +-
apps/meteor/ee/server/lib/ldap/Manager.ts | 18 ++-
apps/meteor/ee/server/lib/oauth/Manager.ts | 10 +-
apps/meteor/lib/callbacks.ts | 3 +-
...tSubscriptionAutotranslateDefaultConfig.ts | 27 ++--
apps/meteor/server/lib/roles/addUserRoles.ts | 7 +-
.../meteor/server/methods/addAllUserToRoom.ts | 2 +-
.../server/methods/createDirectMessage.ts | 6 +-
.../meteor/server/models/raw/Subscriptions.ts | 44 +++++-
apps/meteor/server/models/raw/Users.js | 16 ++
.../rocket-chat/adapters/Room.ts | 15 +-
apps/meteor/server/services/room/service.ts | 6 +-
.../core-services/src/types/IRoomService.ts | 1 +
.../src/models/ISubscriptionsModel.ts | 18 ++-
.../model-typings/src/models/IUsersModel.ts | 3 +-
.../src/v1/channels/ChannelsCreateProps.ts | 1 +
.../src/v1/groups/GroupsCreateProps.ts | 1 +
30 files changed, 295 insertions(+), 149 deletions(-)
diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts
index 4a7aec073442..8e0541b8040b 100644
--- a/apps/meteor/app/api/server/v1/channels.ts
+++ b/apps/meteor/app/api/server/v1/channels.ts
@@ -670,7 +670,14 @@ async function createChannelValidator(params: {
async function createChannel(
userId: string,
- params: { name?: string; members?: string[]; customFields?: Record; extraData?: Record; readOnly?: boolean },
+ params: {
+ name?: string;
+ members?: string[];
+ customFields?: Record;
+ extraData?: Record;
+ readOnly?: boolean;
+ excludeSelf?: boolean;
+ },
): Promise<{ channel: IRoom }> {
const readOnly = typeof params.readOnly !== 'undefined' ? params.readOnly : false;
const id = await createChannelMethod(
@@ -680,6 +687,7 @@ async function createChannel(
readOnly,
params.customFields,
params.extraData,
+ params.excludeSelf,
);
return {
diff --git a/apps/meteor/app/api/server/v1/groups.ts b/apps/meteor/app/api/server/v1/groups.ts
index df54b683fda4..ef18d4256348 100644
--- a/apps/meteor/app/api/server/v1/groups.ts
+++ b/apps/meteor/app/api/server/v1/groups.ts
@@ -1,4 +1,4 @@
-import { Team } from '@rocket.chat/core-services';
+import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { check, Match } from 'meteor/check';
@@ -302,10 +302,6 @@ API.v1.addRoute(
{ authRequired: true },
{
async post() {
- if (!(await hasPermissionAsync(this.userId, 'create-p'))) {
- return API.v1.unauthorized();
- }
-
if (!this.bodyParams.name) {
return API.v1.failure('Body param "name" is required');
}
@@ -323,24 +319,32 @@ API.v1.addRoute(
const readOnly = typeof this.bodyParams.readOnly !== 'undefined' ? this.bodyParams.readOnly : false;
- const result = await createPrivateGroupMethod(
- this.userId,
- this.bodyParams.name,
- this.bodyParams.members ? this.bodyParams.members : [],
- readOnly,
- this.bodyParams.customFields,
- this.bodyParams.extraData,
- );
-
- const room = await Rooms.findOneById(result.rid, { projection: API.v1.defaultFieldsToExclude });
+ try {
+ const result = await createPrivateGroupMethod(
+ this.user,
+ this.bodyParams.name,
+ this.bodyParams.members ? this.bodyParams.members : [],
+ readOnly,
+ this.bodyParams.customFields,
+ this.bodyParams.extraData,
+ this.bodyParams.excludeSelf ?? false,
+ );
+
+ const room = await Rooms.findOneById(result.rid, { projection: API.v1.defaultFieldsToExclude });
+ if (!room) {
+ throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
+ }
- if (!room) {
- throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
+ return API.v1.success({
+ group: await composeRoomWithLastMessage(room, this.userId),
+ });
+ } catch (error: unknown) {
+ if (isMeteorError(error) && error.reason === 'error-not-allowed') {
+ return API.v1.unauthorized();
+ }
}
- return API.v1.success({
- group: await composeRoomWithLastMessage(room, this.userId),
- });
+ return API.v1.internalError();
},
},
);
diff --git a/apps/meteor/app/apps/server/bridges/rooms.ts b/apps/meteor/app/apps/server/bridges/rooms.ts
index 481292d61790..91b0049513f0 100644
--- a/apps/meteor/app/apps/server/bridges/rooms.ts
+++ b/apps/meteor/app/apps/server/bridges/rooms.ts
@@ -55,7 +55,11 @@ export class AppRoomBridge extends RoomBridge {
}
private async createPrivateGroup(userId: string, room: ICoreRoom, members: string[]): Promise {
- return (await createPrivateGroupMethod(userId, room.name || '', members, room.ro, room.customFields, this.prepareExtraData(room))).rid;
+ const user = await Users.findOneById(userId);
+ if (!user) {
+ throw new Error('Invalid user');
+ }
+ return (await createPrivateGroupMethod(user, room.name || '', members, room.ro, room.customFields, this.prepareExtraData(room))).rid;
}
protected async getById(roomId: string, appId: string): Promise {
diff --git a/apps/meteor/app/discussion/server/methods/createDiscussion.ts b/apps/meteor/app/discussion/server/methods/createDiscussion.ts
index c08378fd64f6..c3869f8ff963 100644
--- a/apps/meteor/app/discussion/server/methods/createDiscussion.ts
+++ b/apps/meteor/app/discussion/server/methods/createDiscussion.ts
@@ -156,7 +156,7 @@ const create = async ({
const discussion = await createRoom(
type,
name,
- user.username as string,
+ user,
[...new Set(invitedUsers)].filter(Boolean),
false,
false,
diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
index f241879cdc67..1b596d625d9b 100644
--- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
+++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts
@@ -1034,7 +1034,11 @@ export class ImportDataConverter {
return;
}
if (roomData.t === 'p') {
- roomInfo = await createPrivateGroupMethod(startedByUserId, roomData.name, members, false, {}, {}, true);
+ const user = await Users.findOneById(startedByUserId);
+ if (!user) {
+ throw new Error('importer-channel-invalid-creator');
+ }
+ roomInfo = await createPrivateGroupMethod(user, roomData.name, members, false, {}, {}, true);
} else {
roomInfo = await createChannelMethod(startedByUserId, roomData.name, members, false, {}, {}, true);
}
diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
index ad632a3b7dfc..835f59419ad5 100644
--- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts
@@ -12,7 +12,7 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?:
}).toArray();
for await (const room of defaultRooms) {
if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) {
- const autoTranslateConfig = await getSubscriptionAutotranslateDefaultConfig(user);
+ const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user);
// Add a subscription to this user
await Subscriptions.createWithRoomAndUser(room, user, {
ts: new Date(),
diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
index 41000cda2038..4e29576cf3bb 100644
--- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts
+++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts
@@ -71,7 +71,7 @@ export const addUserToRoom = async function (
await callbacks.run('beforeJoinRoom', userToBeAdded, room);
}
- const autoTranslateConfig = await getSubscriptionAutotranslateDefaultConfig(userToBeAdded);
+ const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded);
await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, {
ts: now,
diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts
index 30cf2a593700..312451f54845 100644
--- a/apps/meteor/app/lib/server/functions/createRoom.ts
+++ b/apps/meteor/app/lib/server/functions/createRoom.ts
@@ -10,7 +10,6 @@ import { Apps } from '../../../../ee/server/apps/orchestrator';
import { callbacks } from '../../../../lib/callbacks';
import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreateRoomCallback';
import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
-import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles';
import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
import { createDirectRoom } from './createDirectRoom';
@@ -21,10 +20,90 @@ const isValidName = (name: unknown): name is string => {
const onlyUsernames = (members: unknown): members is string[] =>
Array.isArray(members) && members.every((member) => typeof member === 'string');
+async function createUsersSubscriptions({
+ room,
+ shouldBeHandledByFederation,
+ members,
+ now,
+ owner,
+ options,
+}: {
+ room: IRoom;
+ shouldBeHandledByFederation: boolean;
+ members: string[];
+ now: Date;
+ owner: IUser;
+ options?: ICreateRoomParams['options'];
+}) {
+ if (shouldBeHandledByFederation) {
+ const extra: Partial = options?.subscriptionExtra || {};
+ extra.open = true;
+ extra.ls = now;
+
+ if (room.prid) {
+ extra.prid = room.prid;
+ }
+
+ await Subscriptions.createWithRoomAndUser(room, owner, extra);
+
+ return;
+ }
+
+ const subs = [];
+
+ const memberIds = [];
+
+ const membersCursor = Users.findUsersByUsernames>(members, {
+ projection: { 'username': 1, 'settings.preferences': 1, 'federated': 1, 'roles': 1 },
+ });
+
+ for await (const member of membersCursor) {
+ try {
+ await callbacks.run('federation.beforeAddUserToARoom', { user: member, inviter: owner }, room);
+ await callbacks.run('beforeAddedToRoom', { user: member, inviter: owner });
+ } catch (error) {
+ continue;
+ }
+
+ memberIds.push(member._id);
+
+ const extra: Partial = options?.subscriptionExtra || {};
+
+ extra.open = true;
+
+ if (room.prid) {
+ extra.prid = room.prid;
+ }
+
+ if (member.username === owner.username) {
+ extra.ls = now;
+ extra.roles = ['owner'];
+ }
+
+ const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(member);
+
+ subs.push({
+ user: member,
+ extraData: {
+ ...extra,
+ ...autoTranslateConfig,
+ },
+ });
+ }
+
+ if (!['d', 'l'].includes(room.t)) {
+ await Users.addRoomByUserIds(memberIds, room._id);
+ }
+
+ await Subscriptions.createWithRoomAndManyUsers(room, subs);
+
+ await Rooms.incUsersCountById(room._id, subs.length);
+}
+
export const createRoom = async (
type: T,
name: T extends 'd' ? undefined : string,
- ownerUsername: string | undefined,
+ owner: T extends 'd' ? IUser | undefined : IUser,
members: T extends 'd' ? IUser[] : string[] = [],
excludeSelf?: boolean,
readOnly?: boolean,
@@ -47,7 +126,7 @@ export const createRoom = async (
// options,
});
if (type === 'd') {
- return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || ownerUsername });
+ return createDirectRoom(members as IUser[], extraData, { ...options, creator: options?.creator || owner?.username });
}
if (!onlyUsernames(members)) {
@@ -63,15 +142,13 @@ export const createRoom = async (
});
}
- if (!ownerUsername) {
+ if (!owner) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
function: 'RocketChat.createRoom',
});
}
- const owner = await Users.findOneByUsernameIgnoringCase(ownerUsername, { projection: { username: 1, name: 1 } });
-
- if (!ownerUsername || !owner) {
+ if (!owner?.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
function: 'RocketChat.createRoom',
});
@@ -140,53 +217,12 @@ export const createRoom = async (
if (type === 'c') {
await callbacks.run('beforeCreateChannel', owner, roomProps);
}
- const room = await Rooms.createWithFullRoomData(roomProps);
- const shouldBeHandledByFederation = room.federated === true || ownerUsername.includes(':');
- if (shouldBeHandledByFederation) {
- const extra: Partial = options?.subscriptionExtra || {};
- extra.open = true;
- extra.ls = now;
- if (room.prid) {
- extra.prid = room.prid;
- }
-
- await Subscriptions.createWithRoomAndUser(room, owner, extra);
- } else {
- for await (const username of [...new Set(members)]) {
- const member = await Users.findOneByUsername(username, {
- projection: { 'username': 1, 'settings.preferences': 1, 'federated': 1, 'roles': 1 },
- });
- if (!member) {
- continue;
- }
-
- try {
- await callbacks.run('federation.beforeAddUserToARoom', { user: member, inviter: owner }, room);
- await callbacks.run('beforeAddedToRoom', { user: member, inviter: owner });
- } catch (error) {
- continue;
- }
-
- const extra: Partial = options?.subscriptionExtra || {};
-
- extra.open = true;
-
- if (room.prid) {
- extra.prid = room.prid;
- }
-
- if (username === owner.username) {
- extra.ls = now;
- }
-
- const autoTranslateConfig = await getSubscriptionAutotranslateDefaultConfig(member);
+ const room = await Rooms.createWithFullRoomData(roomProps);
- await Subscriptions.createWithRoomAndUser(room, member, { ...extra, ...autoTranslateConfig });
- }
- }
+ const shouldBeHandledByFederation = room.federated === true || owner.username.includes(':');
- await addUserRolesAsync(owner._id, ['owner'], room._id);
+ await createUsersSubscriptions({ room, members, now, owner, options, shouldBeHandledByFederation });
if (type === 'c') {
if (room.teamId) {
@@ -195,7 +231,7 @@ export const createRoom = async (
await Message.saveSystemMessage('user-added-room-to-team', team.roomId, room.name || '', owner);
}
}
- await callbacks.run('afterCreateChannel', owner, room);
+ callbacks.runAsync('afterCreateChannel', owner, room);
} else if (type === 'p') {
callbacks.runAsync('afterCreatePrivateGroup', owner, room);
}
diff --git a/apps/meteor/app/lib/server/methods/createChannel.ts b/apps/meteor/app/lib/server/methods/createChannel.ts
index ff8182cec8c9..98cea517bed4 100644
--- a/apps/meteor/app/lib/server/methods/createChannel.ts
+++ b/apps/meteor/app/lib/server/methods/createChannel.ts
@@ -35,8 +35,7 @@ export const createChannelMethod = async (
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'createChannel' });
}
- const user = await Users.findOneById(userId, { projection: { username: 1 } });
-
+ const user = await Users.findOneById(userId);
if (!user?.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'createChannel' });
}
@@ -44,7 +43,7 @@ export const createChannelMethod = async (
if (!(await hasPermissionAsync(userId, 'create-c'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'createChannel' });
}
- return createRoom('c', name, user.username, members, excludeSelf, readOnly, {
+ return createRoom('c', name, user, members, excludeSelf, readOnly, {
customFields,
...extraData,
});
diff --git a/apps/meteor/app/lib/server/methods/createPrivateGroup.ts b/apps/meteor/app/lib/server/methods/createPrivateGroup.ts
index 65298949a345..75097b5c89b8 100644
--- a/apps/meteor/app/lib/server/methods/createPrivateGroup.ts
+++ b/apps/meteor/app/lib/server/methods/createPrivateGroup.ts
@@ -1,4 +1,4 @@
-import type { ICreatedRoom } from '@rocket.chat/core-typings';
+import type { ICreatedRoom, IUser } from '@rocket.chat/core-typings';
import { Users } from '@rocket.chat/models';
import type { ServerMethods } from '@rocket.chat/ui-contexts';
import { Match, check } from 'meteor/check';
@@ -21,7 +21,7 @@ declare module '@rocket.chat/ui-contexts' {
}
export const createPrivateGroupMethod = async (
- userId: string,
+ user: IUser,
name: string,
members: string[],
readOnly = false,
@@ -35,23 +35,12 @@ export const createPrivateGroupMethod = async (
> => {
check(name, String);
check(members, Match.Optional([String]));
- if (!userId) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user', {
- method: 'createPrivateGroup',
- });
- }
- const user = await Users.findOneById(userId, { projection: { username: 1 } });
- if (!user) {
- throw new Meteor.Error('error-invalid-user', 'Invalid user', {
- method: 'createPrivateGroup',
- });
- }
- if (!(await hasPermissionAsync(userId, 'create-p'))) {
+ if (!(await hasPermissionAsync(user._id, 'create-p'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'createPrivateGroup' });
}
- return createRoom('p', name, user.username, members, excludeSelf, readOnly, {
+ return createRoom('p', name, user, members, excludeSelf, readOnly, {
customFields,
...extraData,
});
@@ -67,6 +56,13 @@ Meteor.methods({
});
}
- return createPrivateGroupMethod(uid, name, members, readOnly, customFields, extraData);
+ const user = await Users.findOneById(uid);
+ if (!user) {
+ throw new Meteor.Error('error-invalid-user', 'Invalid user', {
+ method: 'createPrivateGroup',
+ });
+ }
+
+ return createPrivateGroupMethod(user, name, members, readOnly, customFields, extraData);
},
});
diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts
index 06c3014a8a56..f62ab71f2302 100644
--- a/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts
+++ b/apps/meteor/app/meteor-accounts-saml/server/lib/SAML.ts
@@ -480,7 +480,6 @@ export class SAML {
continue;
}
- const room = await Rooms.findOneByNameAndType(roomName, 'c', {});
const privRoom = await Rooms.findOneByNameAndType(roomName, 'p', {});
if (privRoom && includePrivateChannelsInUpdate === true) {
@@ -488,6 +487,7 @@ export class SAML {
continue;
}
+ const room = await Rooms.findOneByNameAndType(roomName, 'c', {});
if (room) {
await addUserToRoom(room._id, user);
continue;
@@ -496,7 +496,7 @@ export class SAML {
if (!room && !privRoom) {
// If the user doesn't have an username yet, we can't create new rooms for them
if (user.username) {
- await createRoom('c', roomName, user.username);
+ await createRoom('c', roomName, user);
}
}
}
diff --git a/apps/meteor/app/slashcommands-create/server/server.ts b/apps/meteor/app/slashcommands-create/server/server.ts
index a3c70f012fa1..104d50c56926 100644
--- a/apps/meteor/app/slashcommands-create/server/server.ts
+++ b/apps/meteor/app/slashcommands-create/server/server.ts
@@ -1,6 +1,6 @@
import { api } from '@rocket.chat/core-services';
import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings';
-import { Rooms } from '@rocket.chat/models';
+import { Rooms, Users } from '@rocket.chat/models';
import { i18n } from '../../../server/lib/i18n';
import { createChannelMethod } from '../../lib/server/methods/createChannel';
@@ -50,7 +50,11 @@ slashCommands.add({
}
if (getParams(params).indexOf('private') > -1) {
- await createPrivateGroupMethod(userId, channelStr, []);
+ const user = await Users.findOneById(userId);
+ if (!user) {
+ return;
+ }
+ await createPrivateGroupMethod(user, channelStr, []);
return;
}
diff --git a/apps/meteor/app/slashcommands-inviteall/server/server.ts b/apps/meteor/app/slashcommands-inviteall/server/server.ts
index 9917775aca06..5376bd6ae64b 100644
--- a/apps/meteor/app/slashcommands-inviteall/server/server.ts
+++ b/apps/meteor/app/slashcommands-inviteall/server/server.ts
@@ -37,6 +37,9 @@ function inviteAll(type: T): SlashCommand['callback'] {
}
const user = await Users.findOneById(userId);
+ if (!user) {
+ return;
+ }
const lng = user?.language || settings.get('Language') || 'en';
const baseChannel = type === 'to' ? await Rooms.findOneById(message.rid) : await Rooms.findOneByName(channel);
@@ -69,7 +72,7 @@ function inviteAll(type: T): SlashCommand['callback'] {
const users = (await cursor.toArray()).map((s: ISubscription) => s.u.username).filter(isTruthy);
if (!targetChannel && ['c', 'p'].indexOf(baseChannel.t) > -1) {
- baseChannel.t === 'c' ? await createChannelMethod(userId, channel, users) : await createPrivateGroupMethod(userId, channel, users);
+ baseChannel.t === 'c' ? await createChannelMethod(userId, channel, users) : await createPrivateGroupMethod(user, channel, users);
void api.broadcast('notify.ephemeralMessage', userId, message.rid, {
msg: i18n.t('Channel_created', {
postProcess: 'sprintf',
diff --git a/apps/meteor/app/utils/lib/getDefaultSubscriptionPref.ts b/apps/meteor/app/utils/lib/getDefaultSubscriptionPref.ts
index a388548c18a8..adb4c2ab1ae9 100644
--- a/apps/meteor/app/utils/lib/getDefaultSubscriptionPref.ts
+++ b/apps/meteor/app/utils/lib/getDefaultSubscriptionPref.ts
@@ -1,4 +1,4 @@
-import type { ISubscription, IUser } from '@rocket.chat/core-typings';
+import type { AtLeast, ISubscription, IUser } from '@rocket.chat/core-typings';
/**
* @type {(userPref: Pick) => {
@@ -7,7 +7,7 @@ import type { ISubscription, IUser } from '@rocket.chat/core-typings';
* emailPrefOrigin: 'user';
* }}
*/
-export const getDefaultSubscriptionPref = (userPref: IUser) => {
+export const getDefaultSubscriptionPref = (userPref: AtLeast) => {
const subscription: Partial = {};
const { desktopNotifications, pushNotifications, emailNotificationMode, highlights } = userPref.settings?.preferences || {};
diff --git a/apps/meteor/ee/server/lib/ldap/Manager.ts b/apps/meteor/ee/server/lib/ldap/Manager.ts
index deb6cdcec666..6c04574ad557 100644
--- a/apps/meteor/ee/server/lib/ldap/Manager.ts
+++ b/apps/meteor/ee/server/lib/ldap/Manager.ts
@@ -1,6 +1,6 @@
import { Team } from '@rocket.chat/core-services';
import type { ILDAPEntry, IUser, IRoom, IRole, IImportUser, IImportRecord } from '@rocket.chat/core-typings';
-import { Users as UsersRaw, Roles, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models';
+import { Users, Roles, Subscriptions as SubscriptionsRaw, Rooms } from '@rocket.chat/models';
import type ldapjs from 'ldapjs';
import type {
@@ -271,10 +271,12 @@ export class LDAPEEManager extends LDAPManager {
logger.debug(`Channel '${channel}' doesn't exist, creating it.`);
const roomOwner = settings.get('LDAP_Sync_User_Data_Channels_Admin') || '';
- // #ToDo: Remove typecastings when createRoom is converted to ts.
- const room = await createRoom('c', channel, roomOwner, [], false, false, {
+
+ const user = await Users.findOneByUsernameIgnoringCase(roomOwner);
+
+ const room = await createRoom('c', channel, user, [], false, false, {
customFields: { ldap: true },
- } as any);
+ });
if (!room?.rid) {
logger.error(`Unable to auto-create channel '${channel}' during ldap sync.`);
return;
@@ -574,7 +576,7 @@ export class LDAPEEManager extends LDAPManager {
}
private static async updateExistingUsers(ldap: LDAPConnection, converter: LDAPDataConverter): Promise {
- const users = await UsersRaw.findLDAPUsers().toArray();
+ const users = await Users.findLDAPUsers().toArray();
for await (const user of users) {
const ldapUser = await this.findLDAPUser(ldap, user);
@@ -586,7 +588,7 @@ export class LDAPEEManager extends LDAPManager {
}
private static async updateUserAvatars(ldap: LDAPConnection): Promise {
- const users = await UsersRaw.findLDAPUsers().toArray();
+ const users = await Users.findLDAPUsers().toArray();
for await (const user of users) {
const ldapUser = await this.findLDAPUser(ldap, user);
if (!ldapUser) {
@@ -615,7 +617,7 @@ export class LDAPEEManager extends LDAPManager {
}
private static async logoutDeactivatedUsers(ldap: LDAPConnection): Promise {
- const users = await UsersRaw.findConnectedLDAPUsers().toArray();
+ const users = await Users.findConnectedLDAPUsers().toArray();
for await (const user of users) {
const ldapUser = await this.findLDAPUser(ldap, user);
@@ -624,7 +626,7 @@ export class LDAPEEManager extends LDAPManager {
}
if (this.isUserDeactivated(ldapUser)) {
- await UsersRaw.unsetLoginTokens(user._id);
+ await Users.unsetLoginTokens(user._id);
}
}
}
diff --git a/apps/meteor/ee/server/lib/oauth/Manager.ts b/apps/meteor/ee/server/lib/oauth/Manager.ts
index b24d7436a784..b75c8aa9a7a5 100644
--- a/apps/meteor/ee/server/lib/oauth/Manager.ts
+++ b/apps/meteor/ee/server/lib/oauth/Manager.ts
@@ -1,6 +1,6 @@
import type { IUser } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
-import { Roles, Rooms } from '@rocket.chat/models';
+import { Roles, Rooms, Users } from '@rocket.chat/models';
import { addUserToRoom } from '../../../../app/lib/server/functions/addUserToRoom';
import { createRoom } from '../../../../app/lib/server/functions/createRoom';
@@ -20,6 +20,12 @@ export class OAuthEEManager {
if (channelsMap && user && identity && groupClaimName) {
const groupsFromSSO = identity[groupClaimName] || [];
+ const userChannelAdmin = await Users.findOneByUsernameIgnoringCase(channelsAdmin);
+ if (!userChannelAdmin) {
+ logger.error(`could not create channel, user not found: ${channelsAdmin}`);
+ return;
+ }
+
for await (const ssoGroup of Object.keys(channelsMap)) {
if (typeof ssoGroup === 'string') {
let channels = channelsMap[ssoGroup];
@@ -30,7 +36,7 @@ export class OAuthEEManager {
const name = await getValidRoomName(channel.trim(), undefined, { allowDuplicates: true });
let room = await Rooms.findOneByNonValidatedName(name);
if (!room) {
- const createdRoom = await createRoom('c', channel, channelsAdmin, [], false, false);
+ const createdRoom = await createRoom('c', channel, userChannelAdmin, [], false, false);
if (!createdRoom?.rid) {
logger.error(`could not create channel ${channel}`);
return;
diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts
index 9c7333a355b3..2d683bf27e2a 100644
--- a/apps/meteor/lib/callbacks.ts
+++ b/apps/meteor/lib/callbacks.ts
@@ -17,6 +17,7 @@ import type {
InquiryWithAgentInfo,
ILivechatTagRecord,
TransferData,
+ AtLeast,
} from '@rocket.chat/core-typings';
import type { FilterOperators } from 'mongodb';
@@ -56,7 +57,7 @@ interface EventLikeCallbackSignatures {
'livechat.afterTakeInquiry': (inq: InquiryWithAgentInfo, agent: { agentId: string; username: string }) => void;
'livechat.afterAgentRemoved': (params: { agent: Pick }) => void;
'afterAddedToRoom': (params: { user: IUser; inviter?: IUser }, room: IRoom) => void;
- 'beforeAddedToRoom': (params: { user: IUser; inviter: IUser }) => void;
+ 'beforeAddedToRoom': (params: { user: AtLeast; inviter: IUser }) => void;
'afterCreateDirectRoom': (params: IRoom, second: { members: IUser[]; creatorId: IUser['_id'] }) => void;
'beforeDeleteRoom': (params: IRoom) => void;
'beforeJoinDefaultChannels': (user: IUser) => void;
diff --git a/apps/meteor/server/lib/getSubscriptionAutotranslateDefaultConfig.ts b/apps/meteor/server/lib/getSubscriptionAutotranslateDefaultConfig.ts
index 13540246f0e6..92e76d8c2ec1 100644
--- a/apps/meteor/server/lib/getSubscriptionAutotranslateDefaultConfig.ts
+++ b/apps/meteor/server/lib/getSubscriptionAutotranslateDefaultConfig.ts
@@ -1,28 +1,23 @@
-import type { IUser } from '@rocket.chat/core-typings';
-import { Settings } from '@rocket.chat/models';
+import type { AtLeast, IUser } from '@rocket.chat/core-typings';
-export const getSubscriptionAutotranslateDefaultConfig = async (
- user: IUser,
-): Promise<
+import { settings } from '../../app/settings/server';
+
+export function getSubscriptionAutotranslateDefaultConfig(user: AtLeast):
| {
autoTranslate: boolean;
autoTranslateLanguage: string;
}
- | undefined
-> => {
- const [autoEnableSetting, languageSetting] = await Promise.all([
- Settings.findOneById('AutoTranslate_AutoEnableOnJoinRoom'),
- Settings.findOneById('Language'),
- ]);
- const { language: userLanguage } = user.settings?.preferences || {};
-
- if (!autoEnableSetting?.value) {
+ | undefined {
+ if (!settings.get('AutoTranslate_AutoEnableOnJoinRoom')) {
return;
}
- if (!userLanguage || userLanguage === 'default' || languageSetting?.value === userLanguage) {
+ const languageSetting = settings.get('Language');
+
+ const { language: userLanguage } = user.settings?.preferences || {};
+ if (!userLanguage || userLanguage === 'default' || languageSetting === userLanguage) {
return;
}
return { autoTranslate: true, autoTranslateLanguage: userLanguage };
-};
+}
diff --git a/apps/meteor/server/lib/roles/addUserRoles.ts b/apps/meteor/server/lib/roles/addUserRoles.ts
index 395056903ae4..a064553f5cb4 100644
--- a/apps/meteor/server/lib/roles/addUserRoles.ts
+++ b/apps/meteor/server/lib/roles/addUserRoles.ts
@@ -1,6 +1,6 @@
import { MeteorError } from '@rocket.chat/core-services';
import type { IRole, IUser, IRoom } from '@rocket.chat/core-typings';
-import { Users, Roles } from '@rocket.chat/models';
+import { Roles } from '@rocket.chat/models';
import { validateRoleList } from './validateRoleList';
@@ -9,11 +9,6 @@ export const addUserRolesAsync = async (userId: IUser['_id'], roleIds: IRole['_i
return false;
}
- const user = await Users.findOneById(userId, { projection: { _id: 1 } });
- if (!user) {
- throw new MeteorError('error-invalid-user', 'Invalid user');
- }
-
if (!(await validateRoleList(roleIds))) {
throw new MeteorError('error-invalid-role', 'Invalid role');
}
diff --git a/apps/meteor/server/methods/addAllUserToRoom.ts b/apps/meteor/server/methods/addAllUserToRoom.ts
index acba1bed406b..11232908b847 100644
--- a/apps/meteor/server/methods/addAllUserToRoom.ts
+++ b/apps/meteor/server/methods/addAllUserToRoom.ts
@@ -56,7 +56,7 @@ Meteor.methods({
continue;
}
await callbacks.run('beforeJoinRoom', user, room);
- const autoTranslateConfig = await getSubscriptionAutotranslateDefaultConfig(user);
+ const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user);
await Subscriptions.createWithRoomAndUser(room, user, {
ts: now,
open: true,
diff --git a/apps/meteor/server/methods/createDirectMessage.ts b/apps/meteor/server/methods/createDirectMessage.ts
index d92c7e46292e..ccbfe8916cae 100644
--- a/apps/meteor/server/methods/createDirectMessage.ts
+++ b/apps/meteor/server/methods/createDirectMessage.ts
@@ -104,7 +104,11 @@ export async function createDirectMessage(
} catch (error) {
throw new Meteor.Error((error as any)?.message);
}
- const { _id: rid, inserted, ...room } = await createRoom('d', undefined, undefined, roomUsers as IUser[], false, undefined, {}, options);
+ const {
+ _id: rid,
+ inserted,
+ ...room
+ } = await createRoom<'d'>('d', undefined, undefined, roomUsers as IUser[], false, undefined, {}, options);
return {
// @ts-expect-error - room type is already defined in the `createRoom` return type
diff --git a/apps/meteor/server/models/raw/Subscriptions.ts b/apps/meteor/server/models/raw/Subscriptions.ts
index 4b42367bad05..c4ba44bdd7f9 100644
--- a/apps/meteor/server/models/raw/Subscriptions.ts
+++ b/apps/meteor/server/models/raw/Subscriptions.ts
@@ -1,4 +1,13 @@
-import type { IRole, IRoom, ISubscription, IUser, RocketChatRecordDeleted, RoomType, SpotlightUser } from '@rocket.chat/core-typings';
+import type {
+ AtLeast,
+ IRole,
+ IRoom,
+ ISubscription,
+ IUser,
+ RocketChatRecordDeleted,
+ RoomType,
+ SpotlightUser,
+} from '@rocket.chat/core-typings';
import type { ISubscriptionsModel } from '@rocket.chat/model-typings';
import { Rooms, Users } from '@rocket.chat/models';
import { escapeRegExp } from '@rocket.chat/string-helpers';
@@ -17,6 +26,7 @@ import type {
IndexDescription,
UpdateFilter,
InsertOneResult,
+ InsertManyResult,
} from 'mongodb';
import { getDefaultSubscriptionPref } from '../../../app/utils/lib/getDefaultSubscriptionPref';
@@ -1605,6 +1615,38 @@ export class SubscriptionsRaw extends BaseRaw implements ISubscri
return result;
}
+ async createWithRoomAndManyUsers(
+ room: IRoom,
+ users: { user: AtLeast; extraData: Record }[] = [],
+ ): Promise> {
+ const subscriptions = users.map(({ user, extraData }) => ({
+ open: false,
+ alert: false,
+ unread: 0,
+ userMentions: 0,
+ groupMentions: 0,
+ ts: room.ts,
+ rid: room._id,
+ name: room.name,
+ fname: room.fname,
+ ...(room.customFields && { customFields: room.customFields }),
+ t: room.t,
+ u: {
+ _id: user._id,
+ username: user.username,
+ name: user.name,
+ },
+ ...(room.prid && { prid: room.prid }),
+ ...getDefaultSubscriptionPref(user),
+ ...extraData,
+ }));
+
+ // @ts-expect-error - types not good :(
+ const result = await this.insertMany(subscriptions);
+
+ return result;
+ }
+
// REMOVE
async removeByUserId(userId: string): Promise {
const query = {
diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js
index 0663bbdcda28..113f18ea83da 100644
--- a/apps/meteor/server/models/raw/Users.js
+++ b/apps/meteor/server/models/raw/Users.js
@@ -384,6 +384,10 @@ export class UsersRaw extends BaseRaw {
}
findOneByUsernameIgnoringCase(username, options) {
+ if (!username) {
+ throw new Error('invalid username');
+ }
+
const query = { username };
return this.findOne(query, {
@@ -1488,6 +1492,18 @@ export class UsersRaw extends BaseRaw {
);
}
+ addRoomByUserIds(uids, rid) {
+ return this.updateMany(
+ {
+ _id: { $in: uids },
+ __rooms: { $ne: rid },
+ },
+ {
+ $addToSet: { __rooms: rid },
+ },
+ );
+ }
+
removeRoomByRoomIds(rids) {
return this.updateMany(
{
diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
index 018a5f87704c..c4aee8bcf2aa 100644
--- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
+++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Room.ts
@@ -58,7 +58,12 @@ export class RocketChatRoomAdapter {
.trim()
.replace(/ /g, '-'),
);
- const { rid, _id } = await createRoom(federatedRoom.getRoomType(), roomName, usernameOrId);
+ const owner = await Users.findOneByUsernameIgnoringCase(usernameOrId);
+ if (!owner) {
+ throw new Error('Cannot create a room without a creator');
+ }
+
+ const { rid, _id } = await createRoom(federatedRoom.getRoomType(), roomName, owner);
const roomId = rid || _id;
await MatrixBridgedRoom.createOrUpdateByLocalRoomId(
roomId,
@@ -90,10 +95,16 @@ export class RocketChatRoomAdapter {
const readonly = false;
const excludeSelf = false;
const extraData = undefined;
+
+ const owner = await Users.findOneByUsernameIgnoringCase(usernameOrId);
+ if (!owner) {
+ throw new Error('Cannot create a room without a creator');
+ }
+
const { rid, _id } = await createRoom(
federatedRoom.getRoomType(),
federatedRoom.getDisplayName(),
- usernameOrId,
+ owner,
federatedRoom.getMembersUsernames(),
excludeSelf,
readonly,
diff --git a/apps/meteor/server/services/room/service.ts b/apps/meteor/server/services/room/service.ts
index 61b5bfeee504..7b9b85cecbd0 100644
--- a/apps/meteor/server/services/room/service.ts
+++ b/apps/meteor/server/services/room/service.ts
@@ -23,15 +23,13 @@ export class RoomService extends ServiceClassInternal implements IRoomService {
throw new Error('no-permission');
}
- const user = await Users.findOneById>(uid, {
- projection: { username: 1 },
- });
+ const user = await Users.findOneById(uid);
if (!user?.username) {
throw new Error('User not found');
}
// TODO convert `createRoom` function to "raw" and move to here
- return createRoom(type, name, user.username, members, false, readOnly, extraData, options) as unknown as IRoom;
+ return createRoom(type, name, user, members, false, readOnly, extraData, options) as unknown as IRoom;
}
async createDirectMessage({ to, from }: { to: string; from: string }): Promise<{ rid: string }> {
diff --git a/packages/core-services/src/types/IRoomService.ts b/packages/core-services/src/types/IRoomService.ts
index d9eee82029af..f7be69ce2a7c 100644
--- a/packages/core-services/src/types/IRoomService.ts
+++ b/packages/core-services/src/types/IRoomService.ts
@@ -4,6 +4,7 @@ export interface ISubscriptionExtraData {
open: boolean;
ls?: Date;
prid?: string;
+ roles?: string[];
}
interface ICreateRoomOptions extends Partial> {
diff --git a/packages/model-typings/src/models/ISubscriptionsModel.ts b/packages/model-typings/src/models/ISubscriptionsModel.ts
index aebda87c78cb..53b0a69ec232 100644
--- a/packages/model-typings/src/models/ISubscriptionsModel.ts
+++ b/packages/model-typings/src/models/ISubscriptionsModel.ts
@@ -1,5 +1,15 @@
-import type { ISubscription, IRole, IUser, IRoom, RoomType, SpotlightUser } from '@rocket.chat/core-typings';
-import type { FindOptions, FindCursor, UpdateResult, DeleteResult, Document, AggregateOptions, Filter, InsertOneResult } from 'mongodb';
+import type { ISubscription, IRole, IUser, IRoom, RoomType, SpotlightUser, AtLeast } from '@rocket.chat/core-typings';
+import type {
+ FindOptions,
+ FindCursor,
+ UpdateResult,
+ DeleteResult,
+ Document,
+ AggregateOptions,
+ Filter,
+ InsertOneResult,
+ InsertManyResult,
+} from 'mongodb';
import type { IBaseModel } from './IBaseModel';
@@ -216,6 +226,10 @@ export interface ISubscriptionsModel extends IBaseModel {
): Promise;
removeByUserId(userId: string): Promise;
createWithRoomAndUser(room: IRoom, user: IUser, extraData?: Record): Promise>;
+ createWithRoomAndManyUsers(
+ room: IRoom,
+ users: { user: AtLeast; extraData: Record }[],
+ ): Promise>;
removeByRoomIdsAndUserId(rids: string[], userId: string): Promise;
removeByRoomIdAndUserId(roomId: string, userId: string): Promise;
diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts
index 1ee2a432c3df..f14f5bc90d0d 100644
--- a/packages/model-typings/src/models/IUsersModel.ts
+++ b/packages/model-typings/src/models/IUsersModel.ts
@@ -239,6 +239,7 @@ export interface IUsersModel extends IBaseModel {
removeAllRoomsByUserId(userId: string): Promise;
removeRoomByUserId(userId: string, rid: string): Promise;
addRoomByUserId(userId: string, rid: string): Promise;
+ addRoomByUserIds(uids: string[], rid: string): Promise;
removeRoomByRoomIds(rids: string[]): Promise;
getLoginTokensByUserId(userId: string): FindCursor;
addPersonalAccessTokenToUser(data: { userId: string; loginTokenObject: IPersonalAccessToken }): Promise;
@@ -317,7 +318,7 @@ export interface IUsersModel extends IBaseModel {
findByUsernameNameOrEmailAddress(nameOrUsernameOrEmail: string, options?: FindOptions): FindCursor;
findCrowdUsers(options?: FindOptions