mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Bulk enabled disable (#3797)
This commit is contained in:
		
							parent
							
								
									0335934bf0
								
							
						
					
					
						commit
						2487b990bd
					
				| @ -0,0 +1,85 @@ | ||||
| import { useState } from 'react'; | ||||
| import { Box, styled, Typography } from '@mui/material'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import type { FeatureSchema } from 'openapi'; | ||||
| 
 | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useProject from 'hooks/api/getters/useProject/useProject'; | ||||
| 
 | ||||
| interface IExportDialogProps { | ||||
|     showExportDialog: boolean; | ||||
|     data: Pick<FeatureSchema, 'name'>[]; | ||||
|     onClose: () => void; | ||||
|     onConfirm?: () => void; | ||||
|     environments: string[]; | ||||
|     projectId: string; | ||||
| } | ||||
| 
 | ||||
| const StyledSelect = styled(GeneralSelect)(({ theme }) => ({ | ||||
|     minWidth: '250px', | ||||
|     marginTop: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| export const BulkDisableDialog = ({ | ||||
|     showExportDialog, | ||||
|     data, | ||||
|     onClose, | ||||
|     onConfirm, | ||||
|     environments, | ||||
|     projectId, | ||||
| }: IExportDialogProps) => { | ||||
|     const [selected, setSelected] = useState(environments[0]); | ||||
|     const { bulkToggleFeaturesEnvironmentOff } = useFeatureApi(); | ||||
|     const { refetch } = useProject(projectId); | ||||
|     const { setToastApiError } = useToast(); | ||||
| 
 | ||||
|     const getOptions = () => | ||||
|         environments.map(env => ({ | ||||
|             key: env, | ||||
|             label: env, | ||||
|         })); | ||||
| 
 | ||||
|     const onClick = async () => { | ||||
|         try { | ||||
|             await bulkToggleFeaturesEnvironmentOff( | ||||
|                 projectId, | ||||
|                 data.map(feature => feature.name), | ||||
|                 selected | ||||
|             ); | ||||
|             refetch(); | ||||
|             onClose(); | ||||
|             onConfirm?.(); | ||||
|         } catch (e: unknown) { | ||||
|             setToastApiError(formatUnknownError(e)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             open={showExportDialog} | ||||
|             title="Disable feature toggles" | ||||
|             onClose={onClose} | ||||
|             onClick={onClick} | ||||
|             primaryButtonText="Disable toggles" | ||||
|             secondaryButtonText="Cancel" | ||||
|         > | ||||
|             <Box> | ||||
|                 You have selected <b>{data.length}</b> feature toggles to | ||||
|                 disable. | ||||
|                 <br /> | ||||
|                 <br /> | ||||
|                 <Typography> | ||||
|                     Select which environment to disable the features for: | ||||
|                 </Typography> | ||||
|                 <StyledSelect | ||||
|                     options={getOptions()} | ||||
|                     value={selected} | ||||
|                     onChange={(option: string) => setSelected(option)} | ||||
|                 /> | ||||
|             </Box> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,85 @@ | ||||
| import { useState } from 'react'; | ||||
| import { Box, styled, Typography } from '@mui/material'; | ||||
| import { Dialogue } from 'component/common/Dialogue/Dialogue'; | ||||
| import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import type { FeatureSchema } from 'openapi'; | ||||
| 
 | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useProject from 'hooks/api/getters/useProject/useProject'; | ||||
| 
 | ||||
| interface IExportDialogProps { | ||||
|     showExportDialog: boolean; | ||||
|     data: Pick<FeatureSchema, 'name'>[]; | ||||
|     onClose: () => void; | ||||
|     onConfirm?: () => void; | ||||
|     environments: string[]; | ||||
|     projectId: string; | ||||
| } | ||||
| 
 | ||||
| const StyledSelect = styled(GeneralSelect)(({ theme }) => ({ | ||||
|     minWidth: '250px', | ||||
|     marginTop: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| export const BulkEnableDialog = ({ | ||||
|     showExportDialog, | ||||
|     data, | ||||
|     onClose, | ||||
|     onConfirm, | ||||
|     environments, | ||||
|     projectId, | ||||
| }: IExportDialogProps) => { | ||||
|     const [selected, setSelected] = useState(environments[0]); | ||||
|     const { bulkToggleFeaturesEnvironmentOn } = useFeatureApi(); | ||||
|     const { refetch } = useProject(projectId); | ||||
|     const { setToastApiError } = useToast(); | ||||
| 
 | ||||
|     const getOptions = () => | ||||
|         environments.map(env => ({ | ||||
|             key: env, | ||||
|             label: env, | ||||
|         })); | ||||
| 
 | ||||
|     const onClick = async () => { | ||||
|         try { | ||||
|             await bulkToggleFeaturesEnvironmentOn( | ||||
|                 projectId, | ||||
|                 data.map(feature => feature.name), | ||||
|                 selected | ||||
|             ); | ||||
|             refetch(); | ||||
|             onClose(); | ||||
|             onConfirm?.(); | ||||
|         } catch (e: unknown) { | ||||
|             setToastApiError(formatUnknownError(e)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <Dialogue | ||||
|             open={showExportDialog} | ||||
|             title="Enable feature toggles" | ||||
|             onClose={onClose} | ||||
|             onClick={onClick} | ||||
|             primaryButtonText="Enable toggles" | ||||
|             secondaryButtonText="Cancel" | ||||
|         > | ||||
|             <Box> | ||||
|                 You have selected <b>{data.length}</b> feature toggles to | ||||
|                 enable. | ||||
|                 <br /> | ||||
|                 <br /> | ||||
|                 <Typography> | ||||
|                     Select which environment to enable the features for: | ||||
|                 </Typography> | ||||
|                 <StyledSelect | ||||
|                     options={getOptions()} | ||||
|                     value={selected} | ||||
|                     onChange={(option: string) => setSelected(option)} | ||||
|                 /> | ||||
|             </Box> | ||||
|         </Dialogue> | ||||
|     ); | ||||
| }; | ||||
| @ -1,12 +1,13 @@ | ||||
| import { FC, useMemo, useState } from 'react'; | ||||
| import { Button } from '@mui/material'; | ||||
| import type { FeatureSchema } from 'openapi'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog'; | ||||
| import { ArchiveButton } from './ArchiveButton'; | ||||
| import { MoreActions } from './MoreActions'; | ||||
| import { ManageTags } from './ManageTags'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| import { BulkDisableDialog } from 'component/feature/FeatureToggleList/BulkDisableDialog'; | ||||
| import { BulkEnableDialog } from 'component/feature/FeatureToggleList/BulkEnableDialog'; | ||||
| 
 | ||||
| interface IProjectFeaturesBatchActionsProps { | ||||
|     selectedIds: string[]; | ||||
| @ -17,8 +18,9 @@ interface IProjectFeaturesBatchActionsProps { | ||||
| export const ProjectFeaturesBatchActions: FC< | ||||
|     IProjectFeaturesBatchActionsProps | ||||
| > = ({ selectedIds, data, projectId }) => { | ||||
|     const { uiConfig } = useUiConfig(); | ||||
|     const [showExportDialog, setShowExportDialog] = useState(false); | ||||
|     const [showBulkEnableDialog, setShowBulkEnableDialog] = useState(false); | ||||
|     const [showBulkDisableDialog, setShowBulkDisableDialog] = useState(false); | ||||
|     const { trackEvent } = usePlausibleTracker(); | ||||
|     const selectedData = useMemo( | ||||
|         () => data.filter(d => selectedIds.includes(d.name)), | ||||
| @ -40,9 +42,37 @@ export const ProjectFeaturesBatchActions: FC< | ||||
|             }, | ||||
|         }); | ||||
|     }; | ||||
|     const trackBulkEnabled = () => { | ||||
|         trackEvent('batch_operations', { | ||||
|             props: { | ||||
|                 eventType: 'features enabled', | ||||
|             }, | ||||
|         }); | ||||
|     }; | ||||
|     const trackBulkDisabled = () => { | ||||
|         trackEvent('batch_operations', { | ||||
|             props: { | ||||
|                 eventType: 'features disabled', | ||||
|             }, | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <Button | ||||
|                 variant="outlined" | ||||
|                 size="small" | ||||
|                 onClick={() => setShowBulkEnableDialog(true)} | ||||
|             > | ||||
|                 Enable | ||||
|             </Button> | ||||
|             <Button | ||||
|                 variant="outlined" | ||||
|                 size="small" | ||||
|                 onClick={() => setShowBulkDisableDialog(true)} | ||||
|             > | ||||
|                 Disable | ||||
|             </Button> | ||||
|             <ArchiveButton projectId={projectId} features={selectedIds} /> | ||||
|             <Button | ||||
|                 variant="outlined" | ||||
| @ -60,6 +90,22 @@ export const ProjectFeaturesBatchActions: FC< | ||||
|                 environments={environments} | ||||
|                 onConfirm={trackExport} | ||||
|             /> | ||||
|             <BulkEnableDialog | ||||
|                 showExportDialog={showBulkEnableDialog} | ||||
|                 data={selectedData} | ||||
|                 onClose={() => setShowBulkEnableDialog(false)} | ||||
|                 environments={environments} | ||||
|                 projectId={projectId} | ||||
|                 onConfirm={trackBulkEnabled} | ||||
|             /> | ||||
|             <BulkDisableDialog | ||||
|                 showExportDialog={showBulkDisableDialog} | ||||
|                 data={selectedData} | ||||
|                 onClose={() => setShowBulkDisableDialog(false)} | ||||
|                 environments={environments} | ||||
|                 projectId={projectId} | ||||
|                 onConfirm={trackBulkDisabled} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -75,6 +75,62 @@ const useFeatureApi = () => { | ||||
|         [createRequest, makeRequest] | ||||
|     ); | ||||
| 
 | ||||
|     const bulkToggleFeaturesEnvironmentOn = useCallback( | ||||
|         async ( | ||||
|             projectId: string, | ||||
|             featureIds: string[], | ||||
|             environmentId: string, | ||||
|             shouldActivateDisabledStrategies = false | ||||
|         ) => { | ||||
|             const path = `api/admin/projects/${projectId}/bulk_features/environments/${environmentId}/on?shouldActivateDisabledStrategies=${shouldActivateDisabledStrategies}`; | ||||
|             const req = createRequest( | ||||
|                 path, | ||||
|                 { | ||||
|                     method: 'POST', | ||||
|                     body: JSON.stringify({ features: featureIds }), | ||||
|                 }, | ||||
|                 'bulkToggleFeaturesEnvironmentOn' | ||||
|             ); | ||||
| 
 | ||||
|             try { | ||||
|                 const res = await makeRequest(req.caller, req.id); | ||||
| 
 | ||||
|                 return res; | ||||
|             } catch (e) { | ||||
|                 throw e; | ||||
|             } | ||||
|         }, | ||||
|         [createRequest, makeRequest] | ||||
|     ); | ||||
| 
 | ||||
|     const bulkToggleFeaturesEnvironmentOff = useCallback( | ||||
|         async ( | ||||
|             projectId: string, | ||||
|             featureIds: string[], | ||||
|             environmentId: string, | ||||
|             shouldActivateDisabledStrategies = false | ||||
|         ) => { | ||||
|             const path = `api/admin/projects/${projectId}/bulk_features/environments/${environmentId}/off?shouldActivateDisabledStrategies=${shouldActivateDisabledStrategies}`; | ||||
|             const req = createRequest( | ||||
|                 path, | ||||
|                 { | ||||
|                     method: 'POST', | ||||
|                     body: JSON.stringify({ features: featureIds }), | ||||
|                 }, | ||||
|                 'bulkToggleFeaturesEnvironmentOff' | ||||
|             ); | ||||
| 
 | ||||
|             try { | ||||
|                 const res = await makeRequest(req.caller, req.id); | ||||
| 
 | ||||
|                 return res; | ||||
|             } catch (e) { | ||||
|                 throw e; | ||||
|             } | ||||
|         }, | ||||
|         [createRequest, makeRequest] | ||||
|     ); | ||||
| 
 | ||||
|     const toggleFeatureEnvironmentOff = useCallback( | ||||
|         async (projectId: string, featureId: string, environmentId: string) => { | ||||
|             const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/off`; | ||||
| @ -307,6 +363,8 @@ const useFeatureApi = () => { | ||||
|         overrideVariantsInEnvironments, | ||||
|         cloneFeatureToggle, | ||||
|         loading, | ||||
|         bulkToggleFeaturesEnvironmentOn, | ||||
|         bulkToggleFeaturesEnvironmentOff, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user