feat: Adds new admin feature preview setting management (#33212)
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>pull/33339/head^2
parent
d94a159126
commit
2f9eea03d2
@ -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. |
||||
@ -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,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; |
||||
@ -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); |
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 50 KiB |
@ -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'; |
||||
@ -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 ?? []); |
||||
}; |
||||
@ -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…
Reference in new issue