Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds new admin feature preview setting management #33212

Merged
merged 53 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4d288ae
feat: adds Accounts_Default_User_Preferences_featuresPreview setting
lucas-a-pelegrino Aug 27, 2024
2fc5007
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Aug 28, 2024
7c9cea8
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Aug 28, 2024
7d81db2
chore: changes feature preview settings type to string
lucas-a-pelegrino Aug 28, 2024
be87e0f
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 2, 2024
f63cf0c
chore: improved useFeaturePreviewList hook to use default settings or…
lucas-a-pelegrino Sep 2, 2024
da6358e
feat: move FeaturePreviewBadge to ui-client for reusability
lucas-a-pelegrino Sep 2, 2024
486f19f
feat: adds AdminFeaturePreviewPage view to manage feature previews
lucas-a-pelegrino Sep 2, 2024
6494a47
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 3, 2024
95e0f75
chore: adds new callout compoenents and texts displayed in page
lucas-a-pelegrino Sep 3, 2024
b39372b
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 3, 2024
ca067db
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 4, 2024
2b0b225
chore: starts implementation of save settings management
lucas-a-pelegrino Sep 4, 2024
b019142
chore: adds saving mechanisms to handle feature preview default settings
lucas-a-pelegrino Sep 4, 2024
5345fe4
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 5, 2024
2b4c4d7
chore: updates feature preview information and images
lucas-a-pelegrino Sep 5, 2024
06dbe0a
chore: changes Accounts_Default_User_Preferences_featurePreview locat…
lucas-a-pelegrino Sep 5, 2024
0cca4d2
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 5, 2024
d8815e8
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 5, 2024
50fb5f6
chore: improves the useFeaturePreviewList hook
lucas-a-pelegrino Sep 5, 2024
5877e49
docs: adds changeset
lucas-a-pelegrino Sep 5, 2024
fbd850d
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 6, 2024
3b9d7ae
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 6, 2024
e603fa6
fix: merge conflicts
lucas-a-pelegrino Sep 16, 2024
4c2e7b0
fix: fixes useTranslation import
lucas-a-pelegrino Sep 16, 2024
bcbadca
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 17, 2024
4290fa3
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 17, 2024
6925692
fix: fixes feature preview badge not showing when it should
lucas-a-pelegrino Sep 17, 2024
6894250
Merge branch 'develop' into feat/CORE-654
scuciatto Sep 18, 2024
5d54866
fix: applies Box component usage instead of FieldGroup
lucas-a-pelegrino Sep 18, 2024
fd2ed8a
Merge branch 'feat/CORE-654' of github.com:RocketChat/Rocket.Chat int…
lucas-a-pelegrino Sep 18, 2024
af740a4
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 18, 2024
81e5f6a
fix: fixes linting errors
lucas-a-pelegrino Sep 18, 2024
3fb3b91
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 18, 2024
9c8786c
fix: fixes tests and linting error
lucas-a-pelegrino Sep 18, 2024
a30a1aa
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 18, 2024
cc40ba8
chore: adds new hook to handle EnableQuery
lucas-a-pelegrino Sep 18, 2024
e211ffd
fix: fixes linting errors
lucas-a-pelegrino Sep 18, 2024
527e8d6
tests: attempts to fix tests related to feature previews
lucas-a-pelegrino Sep 19, 2024
cff3e50
chore: removes setUserPreference() from handleSave()
lucas-a-pelegrino Sep 19, 2024
6338977
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 19, 2024
f3c5e08
Merge branch 'feat/CORE-654' of github.com:RocketChat/Rocket.Chat int…
lucas-a-pelegrino Sep 19, 2024
9ae739e
chore: removes unseen features badge from admin sidebar
lucas-a-pelegrino Sep 19, 2024
4aee659
Merge branch 'develop' into feat/CORE-654
lucas-a-pelegrino Sep 19, 2024
e180b5f
chore: updates changeset to add more information
lucas-a-pelegrino Sep 19, 2024
48c9854
chore: adds PR requested changes
lucas-a-pelegrino Sep 19, 2024
150a653
Merge branch 'feat/CORE-654' of github.com:RocketChat/Rocket.Chat int…
lucas-a-pelegrino Sep 19, 2024
4f8d5b5
chore: removes unnecessary error handling
lucas-a-pelegrino Sep 19, 2024
f2d572f
chore: adds early return to useFeaturePreviewEnableQuery hook
lucas-a-pelegrino Sep 19, 2024
900a833
chore: modifies admin/sidebarItems extension back to .ts
lucas-a-pelegrino Sep 19, 2024
3eb4b42
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
lucas-a-pelegrino Sep 20, 2024
e312a23
tests: adds e2e testing for AdminFeaturePreviewPage
lucas-a-pelegrino Sep 23, 2024
d607c08
removing
ggazzo Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/quick-rings-wave.md
lucas-a-pelegrino marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/i18n": patch
"@rocket.chat/ui-client": patch
lucas-a-pelegrino marked this conversation as resolved.
Show resolved Hide resolved
---

Added new Admin Feature Preview management view
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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';

export const useAccountItems = (): GenericMenuItemProps[] => {
const t = useTranslation();
const router = useRouter();

const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList();

const handleMyAccount = useEffectEvent(() => {
router.navigate('/account');
Expand Down
24 changes: 24 additions & 0 deletions apps/meteor/client/hooks/useFeaturePreviewEnableQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { FeaturePreviewProps } from '@rocket.chat/ui-client';

export const useFeaturePreviewEnableQuery = (features: FeaturePreviewProps[]) => {
ggazzo marked this conversation as resolved.
Show resolved Hide resolved
return Object.entries(
features
.map((item) => {
if (item.enableQuery) {
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
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;
})
.reduce((result, currentValue) => {
(result[currentValue.group] = result[currentValue.group] || []).push(currentValue);
return result;
}, {} as Record<FeaturePreviewProps['group'], FeaturePreviewProps[]>),
);
};
4 changes: 2 additions & 2 deletions apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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';

export const useAccountItems = (): GenericMenuItemProps[] => {
const t = useTranslation();
const router = useRouter();

const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
const { unseenFeatures, featurePreviewEnabled } = usePreferenceFeaturePreviewList();

const handleMyAccount = useMutableCallback(() => {
router.navigate('/account');
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { css } from '@rocket.chat/css-in-js';
import {
ButtonGroup,
Button,
Expand All @@ -13,36 +12,23 @@ 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';
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');

Expand Down Expand Up @@ -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>
Expand All @@ -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) => (
Expand Down
5 changes: 2 additions & 3 deletions apps/meteor/client/views/account/sidebarItems.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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 {
if (!allowFeaturePreviewSetting) {
throw Error(`AdminFeaturePreviewPage-handleSave-SettingNotFound`);
lucas-a-pelegrino marked this conversation as resolved.
Show resolved Hide resolved
}
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;
Original file line number Diff line number Diff line change
@@ -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);
9 changes: 9 additions & 0 deletions apps/meteor/client/views/admin/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
};
}
}

Expand Down Expand Up @@ -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')),
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { defaultFeaturesPreview } from '@rocket.chat/ui-client';

import { hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../app/authorization/client';
import { createSidebarItems } from '../../lib/createSidebarItems';

Expand Down Expand Up @@ -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',
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading