1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: bulk archive usage warning (#4448)

Adds a warning when about to archive features that have lastSeenAt of
less than a week (green usage)

Closes #
[1-224](https://linear.app/unleash/issue/1-1224/bulk-edit-show-last-seen-usage-warning)
![Screenshot 2023-08-08 at 15 10
26](https://github.com/Unleash/unleash/assets/104830839/7783c751-dcdf-4d80-a6fb-b441fb034b70)

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
andreas-unleash 2023-08-08 16:46:11 +03:00 committed by GitHub
parent 839c36d547
commit 32954e8168
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 4 deletions

View File

@ -5,6 +5,9 @@ import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import { Alert, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
interface IFeatureArchiveDialogProps {
isOpen: boolean;
@ -12,18 +15,61 @@ interface IFeatureArchiveDialogProps {
onClose: () => void;
projectId: string;
featureIds: string[];
featuresWithUsage?: string[];
}
const UsageWarning = ({
ids,
projectId,
}: {
ids?: string[];
projectId: string;
}) => {
const formatPath = (id: string) => {
return `/projects/${projectId}/features/${id}`;
};
if (ids) {
return (
<Alert
severity={'warning'}
sx={{ m: theme => theme.spacing(2, 0) }}
>
<Typography
fontWeight={'bold'}
variant={'body2'}
display="inline"
>
{`${ids.length} feature toggles `}
</Typography>
<span>
have usage from applications. If you archive these feature
toggles they will not be available to Client SDKs:
</span>
<ul>
{ids?.map(id => (
<li key={id}>
{<Link to={formatPath(id)}>{id}</Link>}
</li>
))}
</ul>
</Alert>
);
}
return null;
};
export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
isOpen,
onClose,
onConfirm,
projectId,
featureIds,
featuresWithUsage,
}) => {
const { archiveFeatureToggle } = useFeatureApi();
const { archiveFeatures } = useProjectApi();
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const isBulkArchive = featureIds?.length > 1;
const archiveToggle = async () => {
@ -82,6 +128,19 @@ export const FeatureArchiveDialog: VFC<IFeatureArchiveDialogProps> = ({
<strong>{featureIds?.length}</strong> feature
toggles?
</p>
<ConditionallyRender
condition={Boolean(
uiConfig.flags.lastSeenByEnvironment &&
featuresWithUsage &&
featuresWithUsage?.length > 0
)}
show={
<UsageWarning
ids={featuresWithUsage}
projectId={projectId}
/>
}
/>
<ConditionallyRender
condition={featureIds?.length <= 5}
show={

View File

@ -1,24 +1,46 @@
import { useState, VFC } from 'react';
import { useMemo, useState, VFC } from 'react';
import { Button } from '@mui/material';
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
import { DELETE_FEATURE } from 'component/providers/AccessProvider/permissions';
import useProject from 'hooks/api/getters/useProject/useProject';
import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { FeatureSchema } from 'openapi';
import { addDays, isBefore } from 'date-fns';
interface IArchiveButtonProps {
projectId: string;
features: string[];
featureIds: string[];
features: FeatureSchema[];
}
const DEFAULT_USAGE_THRESHOLD_DAYS = 7;
const isFeatureInUse = (feature?: FeatureSchema): boolean => {
const aWeekAgo = addDays(new Date(), -DEFAULT_USAGE_THRESHOLD_DAYS);
return !!(
feature &&
feature.lastSeenAt &&
isBefore(new Date(feature.lastSeenAt), aWeekAgo)
);
};
export const ArchiveButton: VFC<IArchiveButtonProps> = ({
projectId,
featureIds,
features,
}) => {
const { refetch } = useProject(projectId);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const { trackEvent } = usePlausibleTracker();
const featuresWithUsage = useMemo(() => {
return featureIds.filter(name => {
const feature = features.find(f => f.name === name);
return isFeatureInUse(feature);
});
}, [JSON.stringify(features), featureIds]);
const onConfirm = async () => {
setIsDialogOpen(false);
await refetch();
@ -45,7 +67,8 @@ export const ArchiveButton: VFC<IArchiveButtonProps> = ({
</PermissionHOC>
<FeatureArchiveDialog
projectId={projectId}
featureIds={features}
featureIds={featureIds}
featuresWithUsage={featuresWithUsage}
onConfirm={onConfirm}
isOpen={isDialogOpen}
onClose={() => setIsDialogOpen(false)}

View File

@ -88,7 +88,11 @@ export const ProjectFeaturesBatchActions: FC<
</Button>
}
/>
<ArchiveButton projectId={projectId} features={selectedIds} />
<ArchiveButton
projectId={projectId}
featureIds={selectedIds}
features={data}
/>
<Button
variant="outlined"
size="small"