mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +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 { FC, useMemo, useState } from 'react';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import type { FeatureSchema } from 'openapi';
|
import type { FeatureSchema } from 'openapi';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
|
import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog';
|
||||||
import { ArchiveButton } from './ArchiveButton';
|
import { ArchiveButton } from './ArchiveButton';
|
||||||
import { MoreActions } from './MoreActions';
|
import { MoreActions } from './MoreActions';
|
||||||
import { ManageTags } from './ManageTags';
|
import { ManageTags } from './ManageTags';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { BulkDisableDialog } from 'component/feature/FeatureToggleList/BulkDisableDialog';
|
||||||
|
import { BulkEnableDialog } from 'component/feature/FeatureToggleList/BulkEnableDialog';
|
||||||
|
|
||||||
interface IProjectFeaturesBatchActionsProps {
|
interface IProjectFeaturesBatchActionsProps {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
@ -17,8 +18,9 @@ interface IProjectFeaturesBatchActionsProps {
|
|||||||
export const ProjectFeaturesBatchActions: FC<
|
export const ProjectFeaturesBatchActions: FC<
|
||||||
IProjectFeaturesBatchActionsProps
|
IProjectFeaturesBatchActionsProps
|
||||||
> = ({ selectedIds, data, projectId }) => {
|
> = ({ selectedIds, data, projectId }) => {
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
const [showExportDialog, setShowExportDialog] = useState(false);
|
const [showExportDialog, setShowExportDialog] = useState(false);
|
||||||
|
const [showBulkEnableDialog, setShowBulkEnableDialog] = useState(false);
|
||||||
|
const [showBulkDisableDialog, setShowBulkDisableDialog] = useState(false);
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const selectedData = useMemo(
|
const selectedData = useMemo(
|
||||||
() => data.filter(d => selectedIds.includes(d.name)),
|
() => 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 (
|
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} />
|
<ArchiveButton projectId={projectId} features={selectedIds} />
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@ -60,6 +90,22 @@ export const ProjectFeaturesBatchActions: FC<
|
|||||||
environments={environments}
|
environments={environments}
|
||||||
onConfirm={trackExport}
|
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]
|
[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(
|
const toggleFeatureEnvironmentOff = useCallback(
|
||||||
async (projectId: string, featureId: string, environmentId: string) => {
|
async (projectId: string, featureId: string, environmentId: string) => {
|
||||||
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/off`;
|
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/off`;
|
||||||
@ -307,6 +363,8 @@ const useFeatureApi = () => {
|
|||||||
overrideVariantsInEnvironments,
|
overrideVariantsInEnvironments,
|
||||||
cloneFeatureToggle,
|
cloneFeatureToggle,
|
||||||
loading,
|
loading,
|
||||||
|
bulkToggleFeaturesEnvironmentOn,
|
||||||
|
bulkToggleFeaturesEnvironmentOff,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user