mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge pull request #608 from Unleash/feat/create-feature
feat: create and edit feature screen (NEW)
This commit is contained in:
		
						commit
						a382744fd0
					
				@ -24,7 +24,9 @@ const BreadcrumbNav = () => {
 | 
			
		||||
                item !== 'strategies' &&
 | 
			
		||||
                item !== 'features' &&
 | 
			
		||||
                item !== 'features2' && 
 | 
			
		||||
                item !== 'create-toggle'
 | 
			
		||||
                item !== 'create-toggle'&& 
 | 
			
		||||
                item !== 'settings'  
 | 
			
		||||
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@ interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 | 
			
		||||
    onChange: (e: any) => any;
 | 
			
		||||
    onFocus?: (e: any) => any;
 | 
			
		||||
    onBlur?: (e: any) => any;
 | 
			
		||||
    multiline?: boolean;
 | 
			
		||||
    rows?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Input = ({
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,105 @@
 | 
			
		||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
 | 
			
		||||
import { useHistory } from 'react-router-dom';
 | 
			
		||||
import FeatureForm from '../FeatureForm/FeatureForm';
 | 
			
		||||
import useFeatureForm from '../hooks/useFeatureForm';
 | 
			
		||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import useToast from '../../../../hooks/useToast';
 | 
			
		||||
import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
 | 
			
		||||
import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions';
 | 
			
		||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
 | 
			
		||||
import { CF_CREATE_BTN_ID } from '../../../../testIds';
 | 
			
		||||
 | 
			
		||||
const CreateFeature = () => {
 | 
			
		||||
    /* @ts-ignore */
 | 
			
		||||
    const { setToastData, setToastApiError } = useToast();
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
    const history = useHistory();
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        type,
 | 
			
		||||
        setType,
 | 
			
		||||
        name,
 | 
			
		||||
        setName,
 | 
			
		||||
        project,
 | 
			
		||||
        setProject,
 | 
			
		||||
        description,
 | 
			
		||||
        setDescription,
 | 
			
		||||
        getTogglePayload,
 | 
			
		||||
        validateName,
 | 
			
		||||
        clearErrors,
 | 
			
		||||
        errors,
 | 
			
		||||
    } = useFeatureForm();
 | 
			
		||||
 | 
			
		||||
    const { createFeatureToggle, loading } = useFeatureApi();
 | 
			
		||||
 | 
			
		||||
    const handleSubmit = async (e: Event) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        clearErrors();
 | 
			
		||||
        await validateName(name);
 | 
			
		||||
        const payload = getTogglePayload();
 | 
			
		||||
        try {
 | 
			
		||||
            await createFeatureToggle(project, payload);
 | 
			
		||||
            history.push(`/projects/${project}/features2/${name}`);
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: 'Toggle created successfully',
 | 
			
		||||
                text: 'Now you can start using your toggle.',
 | 
			
		||||
                confetti: true,
 | 
			
		||||
                type: 'success',
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e: any) {
 | 
			
		||||
            setToastApiError(e.toString());
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    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(getTogglePayload(), undefined, 2)}'`;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleCancel = () => {
 | 
			
		||||
        history.goBack();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <FormTemplate
 | 
			
		||||
            loading={loading}
 | 
			
		||||
            title="Create Feature toggle"
 | 
			
		||||
            description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing.
 | 
			
		||||
            The feature toggle is disabled when created and you decide when to enable"
 | 
			
		||||
            documentationLink="https://docs.getunleash.io/"
 | 
			
		||||
            formatApiCode={formatApiCode}
 | 
			
		||||
        >
 | 
			
		||||
            <FeatureForm
 | 
			
		||||
                type={type}
 | 
			
		||||
                name={name}
 | 
			
		||||
                project={project}
 | 
			
		||||
                description={description}
 | 
			
		||||
                setType={setType}
 | 
			
		||||
                setName={setName}
 | 
			
		||||
                setProject={setProject}
 | 
			
		||||
                setDescription={setDescription}
 | 
			
		||||
                errors={errors}
 | 
			
		||||
                handleSubmit={handleSubmit}
 | 
			
		||||
                handleCancel={handleCancel}
 | 
			
		||||
                mode="Create"
 | 
			
		||||
                clearErrors={clearErrors}
 | 
			
		||||
            >
 | 
			
		||||
                <PermissionButton
 | 
			
		||||
                    onClick={handleSubmit}
 | 
			
		||||
                    permission={CREATE_FEATURE}
 | 
			
		||||
                    projectId={project}
 | 
			
		||||
                    type="submit"
 | 
			
		||||
                    data-test={CF_CREATE_BTN_ID}
 | 
			
		||||
                >
 | 
			
		||||
                    Create toggle
 | 
			
		||||
                </PermissionButton>
 | 
			
		||||
            </FeatureForm>
 | 
			
		||||
        </FormTemplate>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default CreateFeature;
 | 
			
		||||
@ -0,0 +1,114 @@
 | 
			
		||||
import FormTemplate from '../../../common/FormTemplate/FormTemplate';
 | 
			
		||||
import { useHistory, useParams } from 'react-router-dom';
 | 
			
		||||
import FeatureForm from '../FeatureForm/FeatureForm';
 | 
			
		||||
import useFeatureForm from '../hooks/useFeatureForm';
 | 
			
		||||
import useUiConfig from '../../../../hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import useToast from '../../../../hooks/useToast';
 | 
			
		||||
import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
 | 
			
		||||
import useFeature from '../../../../hooks/api/getters/useFeature/useFeature';
 | 
			
		||||
import { IFeatureViewParams } from '../../../../interfaces/params';
 | 
			
		||||
import * as jsonpatch from 'fast-json-patch';
 | 
			
		||||
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
 | 
			
		||||
import { UPDATE_FEATURE } from '../../../providers/AccessProvider/permissions';
 | 
			
		||||
 | 
			
		||||
const EditFeature = () => {
 | 
			
		||||
    /* @ts-ignore */
 | 
			
		||||
    const { setToastData, setToastApiError } = useToast();
 | 
			
		||||
    const { uiConfig } = useUiConfig();
 | 
			
		||||
    const history = useHistory();
 | 
			
		||||
    const { projectId, featureId } = useParams<IFeatureViewParams>();
 | 
			
		||||
    const { patchFeatureToggle, loading } = useFeatureApi();
 | 
			
		||||
    const { feature } = useFeature(projectId, featureId);
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        type,
 | 
			
		||||
        setType,
 | 
			
		||||
        name,
 | 
			
		||||
        setName,
 | 
			
		||||
        project,
 | 
			
		||||
        setProject,
 | 
			
		||||
        description,
 | 
			
		||||
        setDescription,
 | 
			
		||||
        clearErrors,
 | 
			
		||||
        errors,
 | 
			
		||||
    } = useFeatureForm(
 | 
			
		||||
        feature?.name,
 | 
			
		||||
        feature?.type,
 | 
			
		||||
        feature?.project,
 | 
			
		||||
        feature?.description
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const createPatch = () => {
 | 
			
		||||
        const comparison = { ...feature, type, description };
 | 
			
		||||
        const patch = jsonpatch.compare(feature, comparison);
 | 
			
		||||
        return patch;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleSubmit = async (e: Event) => {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        clearErrors();
 | 
			
		||||
        const patch = createPatch();
 | 
			
		||||
        try {
 | 
			
		||||
            await patchFeatureToggle(project, featureId, patch);
 | 
			
		||||
            history.push(`/projects/${project}/features2/${name}`);
 | 
			
		||||
            setToastData({
 | 
			
		||||
                title: 'Toggle updated successfully',
 | 
			
		||||
                text: 'Now you can start using your toggle.',
 | 
			
		||||
                type: 'success',
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e: any) {
 | 
			
		||||
            setToastApiError(e.toString());
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const formatApiCode = () => {
 | 
			
		||||
        return `curl --location --request PATCH '${
 | 
			
		||||
            uiConfig.unleashUrl
 | 
			
		||||
        }/api/admin/projects/${projectId}/features/${featureId}' \\
 | 
			
		||||
    --header 'Authorization: INSERT_API_KEY' \\
 | 
			
		||||
    --header 'Content-Type: application/json' \\
 | 
			
		||||
    --data-raw '${JSON.stringify(createPatch(), undefined, 2)}'`;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleCancel = () => {
 | 
			
		||||
        history.goBack();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <FormTemplate
 | 
			
		||||
            loading={loading}
 | 
			
		||||
            title="Create Feature toggle"
 | 
			
		||||
            description="Feature toggles support different use cases, each with their own specific needs such as simple static routing or more complex routing.
 | 
			
		||||
            The feature toggle is disabled when created and you decide when to enable"
 | 
			
		||||
            documentationLink="https://docs.getunleash.io/"
 | 
			
		||||
            formatApiCode={formatApiCode}
 | 
			
		||||
        >
 | 
			
		||||
            <FeatureForm
 | 
			
		||||
                type={type}
 | 
			
		||||
                name={name}
 | 
			
		||||
                project={project}
 | 
			
		||||
                description={description}
 | 
			
		||||
                setType={setType}
 | 
			
		||||
                setName={setName}
 | 
			
		||||
                setProject={setProject}
 | 
			
		||||
                setDescription={setDescription}
 | 
			
		||||
                errors={errors}
 | 
			
		||||
                handleSubmit={handleSubmit}
 | 
			
		||||
                handleCancel={handleCancel}
 | 
			
		||||
                mode="Edit"
 | 
			
		||||
                clearErrors={clearErrors}
 | 
			
		||||
            >
 | 
			
		||||
                <PermissionButton
 | 
			
		||||
                    onClick={handleSubmit}
 | 
			
		||||
                    permission={UPDATE_FEATURE}
 | 
			
		||||
                    projectId={project}
 | 
			
		||||
                    type="submit"
 | 
			
		||||
                >
 | 
			
		||||
                    Edit toggle
 | 
			
		||||
                </PermissionButton>
 | 
			
		||||
            </FeatureForm>
 | 
			
		||||
        </FormTemplate>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default EditFeature;
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
import { makeStyles } from '@material-ui/core/styles';
 | 
			
		||||
 | 
			
		||||
export const useStyles = makeStyles(theme => ({
 | 
			
		||||
    container: {
 | 
			
		||||
        maxWidth: '400px',
 | 
			
		||||
    },
 | 
			
		||||
    form: {
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        flexDirection: 'column',
 | 
			
		||||
        height: '100%',
 | 
			
		||||
    },
 | 
			
		||||
    input: { width: '100%', marginBottom: '1rem' },
 | 
			
		||||
    selectInput: {
 | 
			
		||||
        marginBottom: '1rem',
 | 
			
		||||
        minWidth: '400px',
 | 
			
		||||
        [theme.breakpoints.down(600)]: {
 | 
			
		||||
            minWidth: '379px',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    label: {
 | 
			
		||||
        minWidth: '300px',
 | 
			
		||||
        [theme.breakpoints.down(600)]: {
 | 
			
		||||
            minWidth: 'auto',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    buttonContainer: {
 | 
			
		||||
        marginTop: 'auto',
 | 
			
		||||
        display: 'flex',
 | 
			
		||||
        justifyContent: 'flex-end',
 | 
			
		||||
    },
 | 
			
		||||
    cancelButton: {
 | 
			
		||||
        marginLeft: '1.5rem',
 | 
			
		||||
    },
 | 
			
		||||
    inputDescription: {
 | 
			
		||||
        marginBottom: '0.5rem',
 | 
			
		||||
    },
 | 
			
		||||
    typeDescription: {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
        color: theme.palette.grey[600],
 | 
			
		||||
        top: '-13px',
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
    },
 | 
			
		||||
    formHeader: {
 | 
			
		||||
        fontWeight: 'normal',
 | 
			
		||||
        marginTop: '0',
 | 
			
		||||
    },
 | 
			
		||||
    header: {
 | 
			
		||||
        fontWeight: 'normal',
 | 
			
		||||
    },
 | 
			
		||||
    permissionErrorContainer: {
 | 
			
		||||
        position: 'relative',
 | 
			
		||||
    },
 | 
			
		||||
    errorMessage: {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        fontSize: theme.fontSizes.smallBody,
 | 
			
		||||
        color: theme.palette.error.main,
 | 
			
		||||
        position: 'absolute',
 | 
			
		||||
        top: '-8px',
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
@ -0,0 +1,148 @@
 | 
			
		||||
import { CREATE_FEATURE } from '../../../providers/AccessProvider/permissions';
 | 
			
		||||
import Input from '../../../common/Input/Input';
 | 
			
		||||
import { Button } from '@material-ui/core';
 | 
			
		||||
import { useStyles } from './FeatureForm.styles';
 | 
			
		||||
import FeatureTypeSelect from '../../FeatureView2/FeatureSettings/FeatureSettingsMetadata/FeatureTypeSelect/FeatureTypeSelect';
 | 
			
		||||
import {
 | 
			
		||||
    CF_DESC_ID,
 | 
			
		||||
    CF_NAME_ID,
 | 
			
		||||
    CF_TYPE_ID,
 | 
			
		||||
} from '../../../../testIds';
 | 
			
		||||
import useFeatureTypes from '../../../../hooks/api/getters/useFeatureTypes/useFeatureTypes';
 | 
			
		||||
import { KeyboardArrowDownOutlined } from '@material-ui/icons';
 | 
			
		||||
import useUser from '../../../../hooks/api/getters/useUser/useUser';
 | 
			
		||||
import { projectFilterGenerator } from '../../../../utils/project-filter-generator';
 | 
			
		||||
import FeatureProjectSelect from '../../FeatureView2/FeatureSettings/FeatureSettingsProject/FeatureProjectSelect/FeatureProjectSelect';
 | 
			
		||||
import ConditionallyRender from '../../../common/ConditionallyRender';
 | 
			
		||||
import { trim } from '../../../common/util';
 | 
			
		||||
 | 
			
		||||
interface IFeatureToggleForm {
 | 
			
		||||
    type: string;
 | 
			
		||||
    name: string;
 | 
			
		||||
    description: string;
 | 
			
		||||
    project: string;
 | 
			
		||||
    setType: React.Dispatch<React.SetStateAction<string>>;
 | 
			
		||||
    setName: React.Dispatch<React.SetStateAction<string>>;
 | 
			
		||||
    setDescription: React.Dispatch<React.SetStateAction<string>>;
 | 
			
		||||
    setProject: React.Dispatch<React.SetStateAction<string>>;
 | 
			
		||||
    handleSubmit: (e: any) => void;
 | 
			
		||||
    handleCancel: () => void;
 | 
			
		||||
    errors: { [key: string]: string };
 | 
			
		||||
    mode: string;
 | 
			
		||||
    clearErrors: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FeatureForm: React.FC<IFeatureToggleForm> = ({
 | 
			
		||||
    children,
 | 
			
		||||
    type,
 | 
			
		||||
    name,
 | 
			
		||||
    description,
 | 
			
		||||
    project,
 | 
			
		||||
    setType,
 | 
			
		||||
    setName,
 | 
			
		||||
    setDescription,
 | 
			
		||||
    setProject,
 | 
			
		||||
    handleSubmit,
 | 
			
		||||
    handleCancel,
 | 
			
		||||
    errors,
 | 
			
		||||
    mode,
 | 
			
		||||
    clearErrors,
 | 
			
		||||
}) => {
 | 
			
		||||
    const styles = useStyles();
 | 
			
		||||
    const { featureTypes } = useFeatureTypes();
 | 
			
		||||
    const { permissions } = useUser();
 | 
			
		||||
    const editable = mode !== 'Edit';
 | 
			
		||||
 | 
			
		||||
    const renderToggleDescription = () => {
 | 
			
		||||
        return featureTypes.find(toggle => toggle.id === type)?.description;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <form onSubmit={handleSubmit} className={styles.form}>
 | 
			
		||||
            <div className={styles.container}>
 | 
			
		||||
                <p className={styles.inputDescription}>
 | 
			
		||||
                    What kind of feature toggle do you want to create?
 | 
			
		||||
                </p>
 | 
			
		||||
                <FeatureTypeSelect
 | 
			
		||||
                    value={type}
 | 
			
		||||
                    onChange={(e: React.SyntheticEvent) =>
 | 
			
		||||
                        setType(e.target.value)
 | 
			
		||||
                    }
 | 
			
		||||
                    label={'Toggle type'}
 | 
			
		||||
                    id="feature-type-select"
 | 
			
		||||
                    editable
 | 
			
		||||
                    inputProps={{
 | 
			
		||||
                        'data-test': CF_TYPE_ID,
 | 
			
		||||
                    }}
 | 
			
		||||
                    IconComponent={KeyboardArrowDownOutlined}
 | 
			
		||||
                    className={styles.selectInput}
 | 
			
		||||
                />
 | 
			
		||||
                <p className={styles.typeDescription}>
 | 
			
		||||
                    {renderToggleDescription()}
 | 
			
		||||
                </p>
 | 
			
		||||
 | 
			
		||||
                <p className={styles.inputDescription}>
 | 
			
		||||
                    What would you like to call your toggle?
 | 
			
		||||
                </p>
 | 
			
		||||
                <Input
 | 
			
		||||
                    disabled={mode === 'Edit'}
 | 
			
		||||
                    className={styles.input}
 | 
			
		||||
                    label="Name"
 | 
			
		||||
                    error={Boolean(errors.name)}
 | 
			
		||||
                    errorText={errors.name}
 | 
			
		||||
                    onFocus={() => clearErrors()}
 | 
			
		||||
                    value={name}
 | 
			
		||||
                    onChange={e => setName(trim(e.target.value))}
 | 
			
		||||
                    inputProps={{
 | 
			
		||||
                        'data-test': CF_NAME_ID,
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
                <ConditionallyRender
 | 
			
		||||
                    condition={editable}
 | 
			
		||||
                    show={
 | 
			
		||||
                        <p className={styles.inputDescription}>
 | 
			
		||||
                            In which project do you want to save the toggle?
 | 
			
		||||
                        </p>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
                <FeatureProjectSelect
 | 
			
		||||
                    value={project}
 | 
			
		||||
                    onChange={e => setProject(e.target.value)}
 | 
			
		||||
                    enabled={editable}
 | 
			
		||||
                    label="Project"
 | 
			
		||||
                    filter={projectFilterGenerator(
 | 
			
		||||
                        { permissions },
 | 
			
		||||
                        CREATE_FEATURE
 | 
			
		||||
                    )}
 | 
			
		||||
                    IconComponent={KeyboardArrowDownOutlined}
 | 
			
		||||
                    className={styles.selectInput}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <p className={styles.inputDescription}>
 | 
			
		||||
                    How would you describe your feature toggle?
 | 
			
		||||
                </p>
 | 
			
		||||
                <Input
 | 
			
		||||
                    className={styles.input}
 | 
			
		||||
                    multiline
 | 
			
		||||
                    rows={4}
 | 
			
		||||
                    label="Description"
 | 
			
		||||
                    placeholder="A short description of the feature toggle"
 | 
			
		||||
                    value={description}
 | 
			
		||||
                    inputProps={{
 | 
			
		||||
                        'data-test': CF_DESC_ID,
 | 
			
		||||
                    }}
 | 
			
		||||
                    onChange={e => setDescription(e.target.value)}
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div className={styles.buttonContainer}>
 | 
			
		||||
                {children}
 | 
			
		||||
                <Button onClick={handleCancel} className={styles.cancelButton}>
 | 
			
		||||
                    Cancel
 | 
			
		||||
                </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default FeatureForm;
 | 
			
		||||
@ -0,0 +1,90 @@
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { useParams } from 'react-router-dom';
 | 
			
		||||
import useFeatureApi from '../../../../hooks/api/actions/useFeatureApi/useFeatureApi';
 | 
			
		||||
import useQueryParams from '../../../../hooks/useQueryParams';
 | 
			
		||||
import { IFeatureViewParams } from '../../../../interfaces/params';
 | 
			
		||||
 | 
			
		||||
const useFeatureForm = (
 | 
			
		||||
    initialName = '',
 | 
			
		||||
    initialType = 'release',
 | 
			
		||||
    initialProject = 'default',
 | 
			
		||||
    initialDescription = ''
 | 
			
		||||
) => {
 | 
			
		||||
    const { projectId } = useParams<IFeatureViewParams>();
 | 
			
		||||
    const params = useQueryParams();
 | 
			
		||||
    const { validateFeatureToggleName } = useFeatureApi();
 | 
			
		||||
    const toggleQueryName = params.get('name');
 | 
			
		||||
    const [type, setType] = useState(initialType);
 | 
			
		||||
    const [name, setName] = useState(toggleQueryName || initialName);
 | 
			
		||||
    const [project, setProject] = useState(projectId || initialProject);
 | 
			
		||||
    const [description, setDescription] = useState(initialDescription);
 | 
			
		||||
    const [errors, setErrors] = useState({});
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setType(initialType);
 | 
			
		||||
    }, [initialType]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!toggleQueryName) setName(initialName);
 | 
			
		||||
        else setName(toggleQueryName);
 | 
			
		||||
    }, [initialName, toggleQueryName]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!projectId) setProject(initialProject);
 | 
			
		||||
        else setProject(projectId);
 | 
			
		||||
    }, [initialProject, projectId]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setDescription(initialDescription);
 | 
			
		||||
    }, [initialDescription]);
 | 
			
		||||
 | 
			
		||||
    const getTogglePayload = () => {
 | 
			
		||||
        return {
 | 
			
		||||
            type: type,
 | 
			
		||||
            name: name,
 | 
			
		||||
            projectId: project,
 | 
			
		||||
            description: description,
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const validateName = async (name: string) => {
 | 
			
		||||
        if (name.length === 0) {
 | 
			
		||||
            setErrors(prev => ({ ...prev, name: 'Name can not be empty.' }));
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (name.length > 0) {
 | 
			
		||||
            try {
 | 
			
		||||
                await validateFeatureToggleName(name);
 | 
			
		||||
            } catch (err: any) {
 | 
			
		||||
                setErrors(prev => ({
 | 
			
		||||
                    ...prev,
 | 
			
		||||
                    name:
 | 
			
		||||
                        err && err.message
 | 
			
		||||
                            ? err.message
 | 
			
		||||
                            : 'Could not check name',
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const clearErrors = () => {
 | 
			
		||||
        setErrors({});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        type,
 | 
			
		||||
        setType,
 | 
			
		||||
        name,
 | 
			
		||||
        setName,
 | 
			
		||||
        project,
 | 
			
		||||
        setProject,
 | 
			
		||||
        description,
 | 
			
		||||
        setDescription,
 | 
			
		||||
        getTogglePayload,
 | 
			
		||||
        validateName,
 | 
			
		||||
        clearErrors,
 | 
			
		||||
        errors,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useFeatureForm;
 | 
			
		||||
@ -134,6 +134,7 @@ const FeatureOverviewEnvironment = ({
 | 
			
		||||
                                                        name
 | 
			
		||||
                                                    )}
 | 
			
		||||
                                                    arrow
 | 
			
		||||
                                                    key={name}
 | 
			
		||||
                                                >
 | 
			
		||||
                                                    <div
 | 
			
		||||
                                                        className={
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,15 @@ Array [
 | 
			
		||||
    "title": "Copy",
 | 
			
		||||
    "type": "protected",
 | 
			
		||||
  },
 | 
			
		||||
  Object {
 | 
			
		||||
    "component": [Function],
 | 
			
		||||
    "layout": "main",
 | 
			
		||||
    "menu": Object {},
 | 
			
		||||
    "parent": "/projects",
 | 
			
		||||
    "path": "/projects/:projectId/features2/:featureId/settings",
 | 
			
		||||
    "title": "Edit Feature",
 | 
			
		||||
    "type": "protected",
 | 
			
		||||
  },
 | 
			
		||||
  Object {
 | 
			
		||||
    "component": [Function],
 | 
			
		||||
    "flags": "E",
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,6 @@ import RedirectFeatureViewPage from '../../page/features/redirect';
 | 
			
		||||
import RedirectArchive from '../feature/RedirectArchive/RedirectArchive';
 | 
			
		||||
import EnvironmentList from '../environments/EnvironmentList/EnvironmentList';
 | 
			
		||||
import FeatureView2 from '../feature/FeatureView2/FeatureView2';
 | 
			
		||||
import FeatureCreate from '../feature/FeatureCreate/FeatureCreate';
 | 
			
		||||
import ProjectRoles from '../admin/project-roles/ProjectRoles/ProjectRoles';
 | 
			
		||||
import CreateProjectRole from '../admin/project-roles/CreateProjectRole/CreateProjectRole';
 | 
			
		||||
import EditProjectRole from '../admin/project-roles/EditProjectRole/EditProjectRole';
 | 
			
		||||
@ -46,6 +45,8 @@ import EditTagType from '../tagTypes/EditTagType/EditTagType';
 | 
			
		||||
import CreateTagType from '../tagTypes/CreateTagType/CreateTagType';
 | 
			
		||||
import EditProject from '../project/Project/EditProject/EditProject';
 | 
			
		||||
import CreateProject from '../project/Project/CreateProject/CreateProject';
 | 
			
		||||
import CreateFeature from '../feature/CreateFeature/CreateFeature/CreateFeature';
 | 
			
		||||
import EditFeature from '../feature/CreateFeature/EditFeature/EditFeature';
 | 
			
		||||
 | 
			
		||||
export const routes = [
 | 
			
		||||
    // Project
 | 
			
		||||
@ -95,6 +96,15 @@ export const routes = [
 | 
			
		||||
        layout: 'main',
 | 
			
		||||
        menu: {},
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/projects/:projectId/features2/:featureId/settings',
 | 
			
		||||
        parent: '/projects',
 | 
			
		||||
        title: 'Edit Feature',
 | 
			
		||||
        component: EditFeature,
 | 
			
		||||
        type: 'protected',
 | 
			
		||||
        layout: 'main',
 | 
			
		||||
        menu: {},
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: '/projects/:projectId/features2/:featureId',
 | 
			
		||||
        parent: '/projects',
 | 
			
		||||
@ -118,7 +128,7 @@ export const routes = [
 | 
			
		||||
        path: '/projects/:projectId/create-toggle',
 | 
			
		||||
        parent: '/projects/:id/features',
 | 
			
		||||
        title: 'Create feature toggle',
 | 
			
		||||
        component: FeatureCreate,
 | 
			
		||||
        component: CreateFeature,
 | 
			
		||||
        type: 'protected',
 | 
			
		||||
        layout: 'main',
 | 
			
		||||
        menu: {},
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,7 @@ const useFeatureApi = () => {
 | 
			
		||||
        propagateErrors: true,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const validateFeatureToggleName = async (
 | 
			
		||||
        name: string,
 | 
			
		||||
    ) => {
 | 
			
		||||
    const validateFeatureToggleName = async (name: string) => {
 | 
			
		||||
        const path = `api/admin/features/validate`;
 | 
			
		||||
        const req = createRequest(path, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
@ -26,10 +24,9 @@ const useFeatureApi = () => {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const createFeatureToggle = async (
 | 
			
		||||
        projectId: string,
 | 
			
		||||
        featureToggle: IFeatureToggleDTO,
 | 
			
		||||
        featureToggle: IFeatureToggleDTO
 | 
			
		||||
    ) => {
 | 
			
		||||
        const path = `api/admin/projects/${projectId}/features`;
 | 
			
		||||
        const req = createRequest(path, {
 | 
			
		||||
@ -183,7 +180,11 @@ const useFeatureApi = () => {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const patchFeatureVariants = async (projectId: string, featureId: string, patchPayload: Operation[]) => {
 | 
			
		||||
    const patchFeatureVariants = async (
 | 
			
		||||
        projectId: string,
 | 
			
		||||
        featureId: string,
 | 
			
		||||
        patchPayload: Operation[]
 | 
			
		||||
    ) => {
 | 
			
		||||
        const path = `api/admin/projects/${projectId}/features/${featureId}/variants`;
 | 
			
		||||
        const req = createRequest(path, {
 | 
			
		||||
            method: 'PATCH',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user