feat: Adds new admin feature preview setting management (#33212)

Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/33339/head^2
Lucas Pelegrino 1 year ago committed by Guilherme Gazzo
parent d94a159126
commit 2f9eea03d2
  1. 7
      .changeset/quick-rings-wave.md
  2. 4
      apps/meteor/client/NavBarV2/NavBarSettingsToolbar/UserMenu/hooks/useAccountItems.tsx
  3. 28
      apps/meteor/client/hooks/useFeaturePreviewEnableQuery.ts
  4. 4
      apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx
  5. 21
      apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx
  6. 44
      apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx
  7. 5
      apps/meteor/client/views/account/sidebarItems.tsx
  8. 127
      apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewPage.tsx
  9. 26
      apps/meteor/client/views/admin/featurePreview/AdminFeaturePreviewRoute.tsx
  10. 9
      apps/meteor/client/views/admin/routes.tsx
  11. 8
      apps/meteor/client/views/admin/sidebarItems.ts
  12. BIN
      apps/meteor/public/images/featurePreview/enhanced-navigation.png
  13. BIN
      apps/meteor/public/images/featurePreview/resizable-contextual-bar.png
  14. BIN
      apps/meteor/public/images/featurePreview/timestamp.png
  15. 5
      apps/meteor/server/settings/accounts.ts
  16. 1
      apps/meteor/tests/end-to-end/api/miscellaneous.ts
  17. 16
      packages/i18n/src/locales/en.i18n.json
  18. 6
      packages/i18n/src/locales/hi-IN.i18n.json
  19. 21
      packages/ui-client/src/components/FeaturePreview/FeaturePreviewBadge.tsx
  20. 2
      packages/ui-client/src/components/FeaturePreview/index.ts
  21. 2
      packages/ui-client/src/components/index.ts
  22. 12
      packages/ui-client/src/hooks/useDefaultSettingFeaturePreviewList.ts
  23. 5
      packages/ui-client/src/hooks/useFeaturePreview.ts
  24. 32
      packages/ui-client/src/hooks/useFeaturePreviewList.ts
  25. 13
      packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.spec.tsx
  26. 16
      packages/ui-client/src/hooks/usePreferenceFeaturePreviewList.ts
  27. 2
      packages/ui-client/src/index.ts

@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/i18n": minor
"@rocket.chat/ui-client": minor
---
Added new Admin Feature Preview management view, this will allow the workspace admins to both enable feature previewing in the workspace as well as define which feature previews are enabled by default for the users in the workspace.

@ -1,7 +1,7 @@
import { Badge } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { defaultFeaturesPreview, usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client';
import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
@ -9,7 +9,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => {
const t = useTranslation();
const router = useRouter();
const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList();
const handleMyAccount = useEffectEvent(() => {
router.navigate('/account');

@ -0,0 +1,28 @@
import type { FeaturePreviewProps } from '@rocket.chat/ui-client';
import { useMemo } from 'react';
const handleFeaturePreviewEnableQuery = (item: FeaturePreviewProps, _: any, features: FeaturePreviewProps[]) => {
if (item.enableQuery) {
const expected = item.enableQuery.value;
const received = features.find((el) => el.name === item.enableQuery?.name)?.value;
if (expected !== received) {
item.disabled = true;
item.value = false;
} else {
item.disabled = false;
}
}
return item;
};
const groupFeaturePreview = (features: FeaturePreviewProps[]) =>
Object.entries(
features.reduce((result, currentValue) => {
(result[currentValue.group] = result[currentValue.group] || []).push(currentValue);
return result;
}, {} as Record<FeaturePreviewProps['group'], FeaturePreviewProps[]>),
);
export const useFeaturePreviewEnableQuery = (features: FeaturePreviewProps[]) => {
return useMemo(() => groupFeaturePreview(features.map(handleFeaturePreviewEnableQuery)), [features]);
};

@ -1,7 +1,7 @@
import { Badge } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { defaultFeaturesPreview, usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client';
import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
@ -9,7 +9,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => {
const t = useTranslation();
const router = useRouter();
const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList();
const handleMyAccount = useMutableCallback(() => {
router.navigate('/account');

@ -1,21 +0,0 @@
import { Badge } from '@rocket.chat/fuselage';
import { useFeaturePreviewList } from '@rocket.chat/ui-client';
import React from 'react';
import { useTranslation } from 'react-i18next';
const AccountFeaturePreviewBadge = () => {
const { t } = useTranslation();
const { unseenFeatures } = useFeaturePreviewList();
if (!unseenFeatures) {
return null;
}
return (
<Badge variant='primary' aria-label={t('Unseen_features')}>
{unseenFeatures}
</Badge>
);
};
export default AccountFeaturePreviewBadge;

@ -1,4 +1,3 @@
import { css } from '@rocket.chat/css-in-js';
import {
ButtonGroup,
Button,
@ -13,9 +12,10 @@ import {
FieldLabel,
FieldRow,
FieldHint,
Callout,
Margins,
} from '@rocket.chat/fuselage';
import type { FeaturePreviewProps } from '@rocket.chat/ui-client';
import { useFeaturePreviewList } from '@rocket.chat/ui-client';
import { usePreferenceFeaturePreviewList } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
@ -23,26 +23,12 @@ import React, { useEffect, Fragment } from 'react';
import { useForm } from 'react-hook-form';
import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../../components/Page';
import { useFeaturePreviewEnableQuery } from '../../../hooks/useFeaturePreviewEnableQuery';
const handleEnableQuery = (features: FeaturePreviewProps[]) => {
return features.map((item) => {
if (item.enableQuery) {
const expected = item.enableQuery.value;
const received = features.find((el) => el.name === item.enableQuery?.name)?.value;
if (expected !== received) {
item.disabled = true;
item.value = false;
} else {
item.disabled = false;
}
}
return item;
});
};
const AccountFeaturePreviewPage = () => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const { features, unseenFeatures } = useFeaturePreviewList();
const { features, unseenFeatures } = usePreferenceFeaturePreviewList();
const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
@ -85,12 +71,7 @@ const AccountFeaturePreviewPage = () => {
setValue('featuresPreview', updated, { shouldDirty: true });
};
const grouppedFeaturesPreview = Object.entries(
handleEnableQuery(featuresPreview).reduce((result, currentValue) => {
(result[currentValue.group] = result[currentValue.group] || []).push(currentValue);
return result;
}, {} as Record<FeaturePreviewProps['group'], FeaturePreviewProps[]>),
);
const grouppedFeaturesPreview = useFeaturePreviewEnableQuery(featuresPreview);
return (
<Page>
@ -105,14 +86,11 @@ const AccountFeaturePreviewPage = () => {
)}
{featuresPreview.length > 0 && (
<>
<Box
className={css`
white-space: break-spaces;
`}
pbe={24}
fontScale='p1'
>
{t('Feature_preview_page_description')}
<Box>
<Margins block={24}>
<Box fontScale='p1'>{t('Feature_preview_page_description')}</Box>
<Callout>{t('Feature_preview_page_callout')}</Callout>
</Margins>
</Box>
<Accordion>
{grouppedFeaturesPreview?.map(([group, features], index) => (

@ -1,10 +1,9 @@
import { defaultFeaturesPreview } from '@rocket.chat/ui-client';
import { defaultFeaturesPreview, FeaturePreviewBadge } from '@rocket.chat/ui-client';
import React from 'react';
import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/client';
import { settings } from '../../../app/settings/client';
import { createSidebarItems } from '../../lib/createSidebarItems';
import AccountFeaturePreviewBadge from './featurePreview/AccountFeaturePreviewBadge';
export const {
registerSidebarItem: registerAccountSidebarItem,
@ -54,7 +53,7 @@ export const {
href: '/account/feature-preview',
i18nLabel: 'Feature_preview',
icon: 'flask',
badge: () => <AccountFeaturePreviewBadge />,
badge: () => <FeaturePreviewBadge />,
permissionGranted: () => settings.get('Accounts_AllowFeaturePreview') && defaultFeaturesPreview?.length > 0,
},
{

@ -0,0 +1,127 @@
import {
ButtonGroup,
Button,
Box,
ToggleSwitch,
Accordion,
Field,
FieldGroup,
FieldLabel,
FieldRow,
FieldHint,
Callout,
Margins,
} from '@rocket.chat/fuselage';
import { useDefaultSettingFeaturePreviewList } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useToastMessageDispatch, useTranslation, useSettingsDispatch } from '@rocket.chat/ui-contexts';
import type { ChangeEvent } from 'react';
import React, { Fragment } from 'react';
import { useForm } from 'react-hook-form';
import { Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '../../../components/Page';
import { useFeaturePreviewEnableQuery } from '../../../hooks/useFeaturePreviewEnableQuery';
import { useEditableSetting } from '../EditableSettingsContext';
import Setting from '../settings/Setting';
import SettingsGroupPageSkeleton from '../settings/SettingsGroupPage/SettingsGroupPageSkeleton';
const AdminFeaturePreviewPage = () => {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const allowFeaturePreviewSetting = useEditableSetting('Accounts_AllowFeaturePreview');
const { features } = useDefaultSettingFeaturePreviewList();
const {
watch,
formState: { isDirty },
setValue,
handleSubmit,
reset,
} = useForm({
defaultValues: { featuresPreview: features },
});
const { featuresPreview } = watch();
const dispatch = useSettingsDispatch();
const handleSave = async () => {
try {
const featuresToBeSaved = featuresPreview.map((feature) => ({ name: feature.name, value: feature.value }));
await dispatch([
{ _id: allowFeaturePreviewSetting!._id, value: allowFeaturePreviewSetting!.value },
{ _id: 'Accounts_Default_User_Preferences_featuresPreview', value: JSON.stringify(featuresToBeSaved) },
]);
dispatchToastMessage({ type: 'success', message: t('Preferences_saved') });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
reset({ featuresPreview });
}
};
const handleFeatures = (e: ChangeEvent<HTMLInputElement>) => {
const updated = featuresPreview.map((item) => (item.name === e.target.name ? { ...item, value: e.target.checked } : item));
setValue('featuresPreview', updated, { shouldDirty: true });
};
const grouppedFeaturesPreview = useFeaturePreviewEnableQuery(featuresPreview);
if (!allowFeaturePreviewSetting) {
// TODO: Implement FeaturePreviewSkeleton component
return <SettingsGroupPageSkeleton />;
}
return (
<Page>
<PageHeader title={t('Feature_preview')} />
<PageScrollableContentWithShadow>
<Box maxWidth='x600' w='full' alignSelf='center'>
<Box>
<Margins block={24}>
<Box fontScale='p1'>{t('Feature_preview_admin_page_description')}</Box>
<Callout>{t('Feature_preview_page_callout')}</Callout>
<Callout>{t('Feature_preview_admin_page_callout')}</Callout>
<Setting settingId='Accounts_AllowFeaturePreview' sectionChanged={allowFeaturePreviewSetting.changed} />
</Margins>
</Box>
<Accordion>
{grouppedFeaturesPreview?.map(([group, features], index) => (
<Accordion.Item defaultExpanded={index === 0} key={group} title={t(group as TranslationKey)}>
<FieldGroup>
{features.map((feature) => (
<Fragment key={feature.name}>
<Field>
<FieldRow>
<FieldLabel htmlFor={feature.name}>{t(feature.i18n)}</FieldLabel>
<ToggleSwitch
id={feature.name}
checked={feature.value}
name={feature.name}
onChange={handleFeatures}
disabled={feature.disabled || !allowFeaturePreviewSetting.value}
/>
</FieldRow>
{feature.description && <FieldHint mbs={12}>{t(feature.description)}</FieldHint>}
</Field>
{feature.imageUrl && <Box is='img' width='100%' height='auto' mbs={16} src={feature.imageUrl} alt='' />}
</Fragment>
))}
</FieldGroup>
</Accordion.Item>
))}
</Accordion>
</Box>
</PageScrollableContentWithShadow>
<PageFooter isDirty={isDirty || allowFeaturePreviewSetting.changed}>
<ButtonGroup>
<Button onClick={() => reset({ featuresPreview: features })}>{t('Cancel')}</Button>
<Button primary disabled={!(isDirty || allowFeaturePreviewSetting.changed)} onClick={handleSubmit(handleSave)}>
{t('Save_changes')}
</Button>
</ButtonGroup>
</PageFooter>
</Page>
);
};
export default AdminFeaturePreviewPage;

@ -0,0 +1,26 @@
import { usePermission } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { memo } from 'react';
import SettingsProvider from '../../../providers/SettingsProvider';
import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage';
import EditableSettingsProvider from '../settings/EditableSettingsProvider';
import AdminFeaturePreviewPage from './AdminFeaturePreviewPage';
const AdminFeaturePreviewRoute = (): ReactElement => {
const canViewFeaturesPreview = usePermission('manage-cloud');
if (!canViewFeaturesPreview) {
return <NotAuthorizedPage />;
}
return (
<SettingsProvider privileged>
<EditableSettingsProvider>
<AdminFeaturePreviewPage />
</EditableSettingsProvider>
</SettingsProvider>
);
};
export default memo(AdminFeaturePreviewRoute);

@ -104,6 +104,10 @@ declare module '@rocket.chat/ui-contexts' {
pathname: `/admin/subscription`;
pattern: '/admin/subscription';
};
'admin-feature-preview': {
pathname: '/admin/feature-preview';
pattern: '/admin/feature-preview';
};
}
}
@ -237,3 +241,8 @@ registerAdminRoute('/subscription', {
name: 'subscription',
component: lazy(() => import('./subscription/SubscriptionRoute')),
});
registerAdminRoute('/feature-preview', {
name: 'admin-feature-preview',
component: lazy(() => import('./featurePreview/AdminFeaturePreviewRoute')),
});

@ -1,3 +1,5 @@
import { defaultFeaturesPreview } from '@rocket.chat/ui-client';
import { hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../app/authorization/client';
import { createSidebarItems } from '../../lib/createSidebarItems';
@ -129,6 +131,12 @@ export const {
icon: 'emoji',
permissionGranted: (): boolean => hasPermission('manage-emoji'),
},
{
href: '/admin/feature-preview',
i18nLabel: 'Feature_preview',
icon: 'flask',
permissionGranted: () => defaultFeaturesPreview?.length > 0,
},
{
href: '/admin/settings',
i18nLabel: 'Settings',

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@ -743,6 +743,11 @@ export const createAccountSettings = () =>
i18nLabel: 'Sidebar_Sections_Order',
i18nDescription: 'Sidebar_Sections_Order_Description',
});
await this.add('Accounts_Default_User_Preferences_featuresPreview', '[]', {
type: 'string',
public: true,
});
});
await this.section('Avatar', async function () {

@ -186,6 +186,7 @@ describe('miscellaneous', () => {
'muteFocusedConversations',
'notifyCalendarEvents',
'enableMobileRinging',
'featuresPreview',
].filter((p) => Boolean(p));
expect(res.body).to.have.property('success', true);

@ -85,6 +85,7 @@
"Accounts_AllowEmailChange": "Allow Email Change",
"Accounts_AllowEmailNotifications": "Allow Email Notifications",
"Accounts_AllowFeaturePreview": "Allow Feature Preview",
"Accounts_AllowFeaturePreview_Description": "Make feature preview available to all workspace members.",
"Accounts_AllowPasswordChange": "Allow Password Change",
"Accounts_AllowPasswordChangeForOAuthUsers": "Allow Password Change for OAuth Users",
"Accounts_AllowRealNameChange": "Allow Name Change",
@ -1125,8 +1126,8 @@
"Common_Access": "Common Access",
"Commit": "Commit",
"Community": "Community",
"Contextualbar_resizable": "Contextual bar resizable",
"Contextualbar_resizable_description": "Allows you to adjust the size of the contextual bar by simply dragging, giving you instant customization and flexibility",
"Contextualbar_resizable": "Resizable contextual bar",
"Contextualbar_resizable_description": "Adjust the size of the contextual bar by clicking and dragging the edge, giving you instant customization and flexibility.",
"Free_Edition": "Free edition",
"Composer_not_available_phone_calls": "Messages are not available on phone calls",
"Condensed": "Condensed",
@ -1949,8 +1950,8 @@
"Enable_Password_History": "Enable Password History",
"Enable_Password_History_Description": "When enabled, users won't be able to update their passwords to some of their most recently used passwords.",
"Enable_Svg_Favicon": "Enable SVG favicon",
"Enable_timestamp": "Enable timestamp parsing in messages",
"Enable_timestamp_description": "Enable timestamps to be parsed in messages",
"Enable_timestamp": "Timestamp in messages",
"Enable_timestamp_description": "Render Unix timestamps inside messages in your local (system) timezone.",
"Enable_to_bypass_email_verification": "Enable to bypass email verification",
"Enable_two-factor_authentication": "Enable two-factor authentication via TOTP",
"Enable_two-factor_authentication_email": "Enable two-factor authentication via Email",
@ -2284,7 +2285,10 @@
"Favorite_Rooms": "Enable Favorite Rooms",
"Favorites": "Favorites",
"Feature_preview": "Feature preview",
"Feature_preview_page_description": "Welcome to the features preview page! Here, you can enable the latest cutting-edge features that are currently under development and not yet officially released.\n\nPlease note that these configurations are still in the testing phase and may not be stable or fully functional.",
"Feature_preview_page_description": "Enable the latest features that are currently under development.",
"Feature_preview_page_callout": "Feature previews are being tested and may not be stable or fully functional. Features may become premium capabilities once officially released.",
"Feature_preview_admin_page_description": "Choose what feature previews to make available to workspace members.",
"Feature_preview_admin_page_callout": "Features enabled here will be enabled to each user in their feature preview preferences.",
"featured": "featured",
"Featured": "Featured",
"Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings (Admin -> Video Conference).",
@ -4363,7 +4367,7 @@
"Queue_Time": "Queue Time",
"Queue_management": "Queue Management",
"Quick_reactions": "Quick reactions",
"Quick_reactions_description": "The three most used reactions get an easy access while your mouse is over the message",
"Quick_reactions_description": "Easily access your most used and most recent emoji message reactions by hovering on a message.",
"quote": "quote",
"Quote": "Quote",
"Random": "Random",

@ -2169,7 +2169,6 @@
"Favorite_Rooms": "पस कमर सकषम कर",
"Favorites": "पस",
"Feature_preview": "फचर पवलकन",
"Feature_preview_page_description": "फचर पवलकन पठ पर आपकगत ह! यह, आप नवनतम अतिक सि सकषम कर सकत वरतमन मिस क अधन ह और अभ तक आधििक तर पर ज नह गई ह।\n\nकपयन दििगरशन अभ परषण चरण म और सिर य तरह कमक नह सकत।",
"featured": "परदरित",
"Featured": "परदरित",
"Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "यह सिरशसन सिस (एडमिन -> विस) स सकषम हिए उपरत चयनित कल परद पर निभर करत।",
@ -4128,7 +4127,6 @@
"Queue_Time": "कतर समय",
"Queue_management": "कतर परबधन",
"Quick_reactions": "तवरित परतिि",
"Quick_reactions_description": "जब आपकउस सश पर ह सबस अधिक उपयग कन परतिि तक आसन पहच मिलत",
"quote": "उदधरण",
"Quote": "उदधरण",
"Random": "Random",
@ -4987,7 +4985,7 @@
"The_application_will_be_able_to": "<1>{{appName}}</1> यह करन सकषम ह:",
"The_channel_name_is_required": "चनल कम आवशयक ह",
"The_emails_are_being_sent": "ईमल भ रह.",
"The_empty_room__roomName__will_be_removed_automatically": "ख कमर <span style=\"font-weight: bold;\">{{roomName}}</span> सवचित रप स हटिएग।",
"The_empty_room__roomName__will_be_removed_automatically": "ख कमर <span style=\"font-weight: bold;\">{{roomName}}</span> सवचित रप स हटिएग।",
"The_image_resize_will_not_work_because_we_can_not_detect_ImageMagick_or_GraphicsMagick_installed_in_your_server": "छवि आकर बदलनम नह करि हम आपक सरवर पर सित ImageMagick यिसMagick क पत नह लग सकत।",
"The_message_is_a_discussion_you_will_not_be_able_to_recover": "सश एक चर आप सनरत नह कर प!",
"The_mobile_notifications_were_disabled_to_all_users_go_to_Admin_Push_to_enable_the_Push_Gateway_again": "मइल सचन सभ उपयगकरिए अकषम कर द गई थ, पश गटविर स सकषम करनिए \"एडमिन > पश\" पर ज",
@ -6134,4 +6132,4 @@
"Unlimited_seats": "असित स",
"Unlimited_MACs": "असित एमएस",
"Unlimited_seats_MACs": "असित स और एमएस"
}
}

@ -0,0 +1,21 @@
import { Badge } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import { usePreferenceFeaturePreviewList } from '../../hooks/usePreferenceFeaturePreviewList';
const FeaturePreviewBadge = () => {
const t = useTranslation();
const { unseenFeatures } = usePreferenceFeaturePreviewList();
if (!unseenFeatures) {
return null;
}
return (
<Badge variant='primary' aria-label={t('Unseen_features')}>
{unseenFeatures}
</Badge>
);
};
export default FeaturePreviewBadge;

@ -0,0 +1,2 @@
export { FeaturePreview, FeaturePreviewOn, FeaturePreviewOff } from './FeaturePreview';
export { default as FeaturePreviewBadge } from './FeaturePreviewBadge';

@ -11,7 +11,7 @@ export * as UserStatus from './UserStatus';
export * from './Header';
export * from './HeaderV2';
export * from './MultiSelectCustom/MultiSelectCustom';
export * from './FeaturePreview/FeaturePreview';
export * from './FeaturePreview';
export * from './RoomBanner';
export { default as UserAutoComplete } from './UserAutoComplete';
export * from './GenericMenu';

@ -0,0 +1,12 @@
import { useSetting } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { parseSetting, useFeaturePreviewList } from './useFeaturePreviewList';
export const useDefaultSettingFeaturePreviewList = () => {
const featurePreviewSettingJSON = useSetting<string>('Accounts_Default_User_Preferences_featuresPreview');
const settingFeaturePreview = useMemo(() => parseSetting(featurePreviewSettingJSON), [featurePreviewSettingJSON]);
return useFeaturePreviewList(settingFeaturePreview ?? []);
};

@ -1,7 +1,8 @@
import { type FeaturesAvailable, useFeaturePreviewList } from './useFeaturePreviewList';
import { type FeaturesAvailable } from './useFeaturePreviewList';
import { usePreferenceFeaturePreviewList } from './usePreferenceFeaturePreviewList';
export const useFeaturePreview = (featureName: FeaturesAvailable) => {
const { features } = useFeaturePreviewList();
const { features } = usePreferenceFeaturePreviewList();
const currentFeature = features?.find((feature) => feature.name === featureName);

@ -1,5 +1,4 @@
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts';
export type FeaturesAvailable =
| 'quickReactions'
@ -24,6 +23,7 @@ export type FeaturePreviewProps = {
};
};
// TODO: Move the features preview array to another directory to be accessed from both BE and FE.
export const defaultFeaturesPreview: FeaturePreviewProps[] = [
{
name: 'quickReactions',
@ -47,6 +47,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [
i18n: 'Enable_timestamp',
description: 'Enable_timestamp_description',
group: 'Message',
imageUrl: 'images/featurePreview/timestamp.png',
value: false,
enabled: true,
},
@ -55,6 +56,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [
i18n: 'Contextualbar_resizable',
description: 'Contextualbar_resizable_description',
group: 'Navigation',
imageUrl: 'images/featurePreview/resizable-contextual-bar.png',
value: false,
enabled: true,
},
@ -63,6 +65,7 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [
i18n: 'New_navigation',
description: 'New_navigation_description',
group: 'Navigation',
imageUrl: 'images/featurePreview/enhanced-navigation.png',
value: false,
enabled: true,
},
@ -82,22 +85,27 @@ export const defaultFeaturesPreview: FeaturePreviewProps[] = [
export const enabledDefaultFeatures = defaultFeaturesPreview.filter((feature) => feature.enabled);
export const useFeaturePreviewList = () => {
const featurePreviewEnabled = useSetting<boolean>('Accounts_AllowFeaturePreview');
const userFeaturesPreview = useUserPreference<FeaturePreviewProps[]>('featuresPreview');
if (!featurePreviewEnabled) {
return { unseenFeatures: 0, features: [] as FeaturePreviewProps[], featurePreviewEnabled };
// TODO: Remove this logic after we have a way to store object settings.
export const parseSetting = (setting?: FeaturePreviewProps[] | string) => {
if (typeof setting === 'string') {
try {
return JSON.parse(setting) as FeaturePreviewProps[];
} catch (_) {
return;
}
}
return setting;
};
export const useFeaturePreviewList = (featuresList: Pick<FeaturePreviewProps, 'name' | 'value'>[]) => {
const unseenFeatures = enabledDefaultFeatures.filter(
(feature) => !userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name),
(defaultFeature) => !featuresList?.find((feature) => feature.name === defaultFeature.name),
).length;
const mergedFeatures = enabledDefaultFeatures.map((feature) => {
const userFeature = userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name);
return { ...feature, ...userFeature };
const mergedFeatures = enabledDefaultFeatures.map((defaultFeature) => {
const features = featuresList?.find((feature) => feature.name === defaultFeature.name);
return { ...defaultFeature, ...features };
});
return { unseenFeatures, features: mergedFeatures, featurePreviewEnabled };
return { unseenFeatures, features: mergedFeatures };
};

@ -1,10 +1,11 @@
import { mockAppRoot } from '@rocket.chat/mock-providers';
import { renderHook } from '@testing-library/react';
import { useFeaturePreviewList, enabledDefaultFeatures } from './useFeaturePreviewList';
import { enabledDefaultFeatures } from './useFeaturePreviewList';
import { usePreferenceFeaturePreviewList } from './usePreferenceFeaturePreviewList';
it('should return the number of unseen features and Accounts_AllowFeaturePreview enabled ', () => {
const { result } = renderHook(() => useFeaturePreviewList(), {
const { result } = renderHook(() => usePreferenceFeaturePreviewList(), {
legacyRoot: true,
wrapper: mockAppRoot().withSetting('Accounts_AllowFeaturePreview', true).build(),
});
@ -18,7 +19,7 @@ it('should return the number of unseen features and Accounts_AllowFeaturePreview
});
it('should return the number of unseen features and Accounts_AllowFeaturePreview disabled ', () => {
const { result } = renderHook(() => useFeaturePreviewList(), {
const { result } = renderHook(() => usePreferenceFeaturePreviewList(), {
legacyRoot: true,
wrapper: mockAppRoot().withSetting('Accounts_AllowFeaturePreview', false).build(),
});
@ -32,7 +33,7 @@ it('should return the number of unseen features and Accounts_AllowFeaturePreview
});
it('should return 0 unseen features', () => {
const { result } = renderHook(() => useFeaturePreviewList(), {
const { result } = renderHook(() => usePreferenceFeaturePreviewList(), {
legacyRoot: true,
wrapper: mockAppRoot()
.withSetting('Accounts_AllowFeaturePreview', true)
@ -49,7 +50,7 @@ it('should return 0 unseen features', () => {
});
it('should ignore removed feature previews', () => {
const { result } = renderHook(() => useFeaturePreviewList(), {
const { result } = renderHook(() => usePreferenceFeaturePreviewList(), {
legacyRoot: true,
wrapper: mockAppRoot()
.withSetting('Accounts_AllowFeaturePreview', true)
@ -72,7 +73,7 @@ it('should ignore removed feature previews', () => {
});
it('should turn off ignored feature previews', async () => {
const { result } = renderHook(() => useFeaturePreviewList(), {
const { result } = renderHook(() => usePreferenceFeaturePreviewList(), {
legacyRoot: true,
wrapper: mockAppRoot()
.withSetting('Accounts_AllowFeaturePreview', true)

@ -0,0 +1,16 @@
import { useSetting, useUserPreference } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';
import { FeaturePreviewProps, parseSetting, useFeaturePreviewList } from './useFeaturePreviewList';
export const usePreferenceFeaturePreviewList = () => {
const featurePreviewEnabled = useSetting<boolean>('Accounts_AllowFeaturePreview');
const userFeaturesPreviewPreference = useUserPreference<FeaturePreviewProps[]>('featuresPreview');
const userFeaturesPreview = useMemo(() => parseSetting(userFeaturesPreviewPreference), [userFeaturesPreviewPreference]);
const { unseenFeatures, features } = useFeaturePreviewList(userFeaturesPreview ?? []);
if (!featurePreviewEnabled) {
return { unseenFeatures: 0, features: [] as FeaturePreviewProps[], featurePreviewEnabled };
}
return { unseenFeatures, features, featurePreviewEnabled };
};

@ -1,5 +1,7 @@
export * from './components';
export * from './hooks/useFeaturePreview';
export * from './hooks/useDefaultSettingFeaturePreviewList';
export * from './hooks/useFeaturePreviewList';
export * from './hooks/usePreferenceFeaturePreviewList';
export * from './hooks/useDocumentTitle';
export * from './helpers';

Loading…
Cancel
Save