import { formatUnknownError } from 'utils/formatUnknownError';
import useToast from 'hooks/useToast';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import {
type ReactNode,
useState,
useContext,
type FormEvent,
useMemo,
} from 'react';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useNavigate } from 'react-router-dom';
import { Dialog, styled } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { Limit } from 'component/common/Limit/Limit';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import UIContext from 'contexts/UIContext';
import useFeatureForm from 'component/feature/hooks/useFeatureForm';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import FlagIcon from '@mui/icons-material/Flag';
import ImpressionDataIcon from '@mui/icons-material/AltRoute';
import { useFlagLimits } from 'component/feature/CreateFeature/CreateFeature';
import { useGlobalFeatureSearch } from 'component/feature/FeatureToggleList/useGlobalFeatureSearch';
import useProjectOverview, {
featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import type { FeatureTypeSchema } from 'openapi';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import useFeatureTypes from 'hooks/api/getters/useFeatureTypes/useFeatureTypes';
import { DialogFormTemplate } from 'component/common/DialogFormTemplate/DialogFormTemplate';
import { SingleSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/SingleSelectConfigButton';
import useAllTags from 'hooks/api/getters/useAllTags/useAllTags';
import Label from '@mui/icons-material/Label';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { MultiSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton';
import type { ITag } from 'interfaces/tags';
import { ToggleConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/ToggleConfigButton';
interface ICreateFeatureDialogProps {
open: boolean;
onClose: () => void;
}
const StyledDialog = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-paper': {
borderRadius: theme.shape.borderRadiusLarge,
maxWidth: theme.spacing(170),
width: '100%',
backgroundColor: 'transparent',
},
padding: 0,
'& .MuiPaper-root > section': {
overflowX: 'hidden',
},
}));
const configButtonData = {
project: {
icon: ,
text: 'Projects allow you to group feature flags together in the Unleash admin UI and in SDK payloads.',
},
tags: {
icon: ,
text: 'Tags are used to label flags in Unleash. They can be used when filtering flags in the UI. Additionally, they are used by some integrations.',
},
type: {
icon: ,
text: "A flag's type conveys its purpose. All types have the same capabilities, but choosing the right type signals what kind of flag it is. You can change this at any time.",
},
impressionData: {
icon: ,
text: `Impression data is used to track how your flag is performing. When enabled, you can subscribe to 'impression events' in the SDK and process them according to your needs.`,
},
};
export const CreateFeatureDialog = ({
open,
onClose,
}: ICreateFeatureDialogProps) => {
if (open) {
// wrap the inner component so that we only fetch data etc
// when the dialog is actually open.
return ;
}
};
const CreateFeatureDialogContent = ({
open,
onClose,
}: ICreateFeatureDialogProps) => {
const { setToastData, setToastApiError } = useToast();
const { setShowFeedback } = useContext(UIContext);
const { uiConfig, isOss } = useUiConfig();
const navigate = useNavigate();
const {
type,
setType,
tags,
setTags,
name,
setName,
project,
setProject,
description,
setDescription,
validateToggleName,
impressionData,
setImpressionData,
getTogglePayload,
clearErrors,
errors,
} = useFeatureForm();
const { createFeatureToggle, loading } = useFeatureApi();
const generalDocumentation: {
icon: ReactNode;
text: string;
link?: { url: string; label: string };
} = {
icon: ,
text: 'Feature flags are at the core of Unleash. Use them to control your feature rollouts.',
link: {
url: 'https://docs.getunleash.io/reference/feature-toggles',
label: 'Feature flags documentation',
},
};
const [documentation, setDocumentation] = useState(generalDocumentation);
const clearDocumentationOverride = () =>
setDocumentation(generalDocumentation);
const flagPayload = getTogglePayload();
const formatApiCode = () => {
return `curl --location --request POST '${
uiConfig.unleashUrl
}/api/admin/projects/${project}/features' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(flagPayload, undefined, 2)}'`;
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
clearErrors();
const validToggleName = await validateToggleName();
if (validToggleName) {
const payload = getTogglePayload();
try {
await createFeatureToggle(project, payload);
navigate(`/projects/${project}/features/${name}`);
setToastData({
title: 'Flag created successfully',
text: 'Now you can start using your flag.',
confetti: true,
type: 'success',
});
setShowFeedback(true);
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
}
};
const { total: totalFlags, loading: loadingTotalFlagCount } =
useGlobalFeatureSearch(1);
const { project: projectInfo } = useProjectOverview(project);
const { tags: allTags } = useAllTags();
const resourceLimitsEnabled = useUiFlag('resourceLimits');
const { globalFlagLimitReached, projectFlagLimitReached, limitMessage } =
useFlagLimits({
global: {
limit: uiConfig.resourceLimits.featureFlags,
count: totalFlags ?? 0,
},
project: {
limit: projectInfo.featureLimit,
count: featuresCount(projectInfo),
},
});
const { projects } = useProjects();
const { featureTypes } = useFeatureTypes();
const FeatureTypeIcon = getFeatureTypeIcons(type);
const longestFeatureTypeName = featureTypes.reduce(
(prev: number, type: { name: string }) =>
prev >= type.name.length ? prev : type.name.length,
0,
);
const currentProjectName = useMemo(() => {
const projectObject = projects.find((p) => p.id === project);
return projectObject?.name;
}, [project, projects]);
return (
}
validateName={validateToggleName}
Limit={
}
/>
}
name={name}
onClose={onClose}
resource={'feature flag'}
setDescription={setDescription}
setName={setName}
configButtons={
<>
({
label: project.name,
value: project.id,
}))}
onChange={(value: any) => {
setProject(value);
}}
button={{
label:
currentProjectName ?? project,
icon: configButtonData.project.icon,
labelWidth: '20ch',
}}
search={{
label: 'Filter projects',
placeholder: 'Select project',
}}
onOpen={() =>
setDocumentation(
configButtonData.project,
)
}
onClose={clearDocumentationOverride}
/>
}
/>
tooltip={{
header: 'Select tags',
}}
description={configButtonData.tags.text}
selectedOptions={tags}
options={allTags.map((tag) => ({
label: `${tag.type}:${tag.value}`,
value: tag,
}))}
onChange={setTags}
button={{
label:
tags.size > 0
? `${tags.size} selected`
: 'Tags',
labelWidth: `${'nn selected'.length}ch`,
icon: ,
}}
search={{
label: 'Filter tags',
placeholder: 'Select tags',
}}
onOpen={() =>
setDocumentation(configButtonData.tags)
}
onClose={clearDocumentationOverride}
/>
({
label: type.name,
value: type.id,
}),
)}
onChange={(value: any) => {
setType(value);
}}
button={{
label:
featureTypes.find((t) => t.id === type)
?.name || 'Select flag type',
icon: ,
labelWidth: `${longestFeatureTypeName}ch`,
}}
search={{
label: 'Filter flag types',
placeholder: 'Select flag type',
}}
onOpen={() =>
setDocumentation({
text: configButtonData.type.text,
icon: ,
})
}
onClose={clearDocumentationOverride}
/>
setImpressionData(!impressionData)
}
label={`Impression data ${impressionData ? 'on' : 'off'}`}
icon={}
labelWidth={`${'impression data off'.length}ch`}
/>
>
}
/>
);
};