1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: add create and edit environment screen (NEW) (#605)

* feat: add create and edit environment screen

* fix: remove environment success screen

Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
Youssef Khedher 2022-01-18 11:23:24 +01:00 committed by GitHub
parent 114542803d
commit 1b097f85d6
21 changed files with 476 additions and 501 deletions

View File

@ -1,31 +0,0 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
helperText: { marginBottom: '1rem' },
formHeader: {
fontWeight: 'bold',
fontSize: '1rem',
marginTop: '2rem',
},
radioGroup: {
flexDirection: 'row',
},
environmentDetailsContainer: {
display: 'flex',
flexDirection: 'column',
margin: '1rem 0',
},
submitButton: {
marginTop: '1rem',
width: '150px',
marginRight: '1rem',
},
btnContainer: {
display: 'flex',
justifyContent: 'center',
},
inputField: {
width: '100%',
marginTop: '1rem',
},
}));

View File

@ -1,99 +1,82 @@
import React, { useState } from 'react';
import { FormControl, Button } from '@material-ui/core';
import HeaderTitle from '../../common/HeaderTitle';
import PageContent from '../../common/PageContent';
import { useStyles } from './CreateEnvironment.styles';
import { useHistory } from 'react-router-dom';
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import ConditionallyRender from '../../common/ConditionallyRender';
import CreateEnvironmentSuccess from './CreateEnvironmentSuccess/CreateEnvironmentSuccess';
import useLoading from '../../../hooks/useLoading';
import useEnvironmentForm from '../hooks/useEnvironmentForm';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../hooks/useToast';
import EnvironmentTypeSelector from '../form/EnvironmentTypeSelector/EnvironmentTypeSelector';
import Input from '../../common/Input/Input';
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
import FormTemplate from '../../common/FormTemplate/FormTemplate';
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import { Alert } from '@material-ui/lab';
import { Button } from '@material-ui/core';
import ConditionallyRender from '../../common/ConditionallyRender';
import PageContent from '../../common/PageContent';
import HeaderTitle from '../../common/HeaderTitle';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
const NAME_EXISTS_ERROR = 'Error: Environment';
const CreateEnvironment = () => {
const [type, setType] = useState('development');
const [envName, setEnvName] = useState('');
const [nameError, setNameError] = useState('');
const [createSuccess, setCreateSucceess] = useState(false);
/* @ts-ignore */
const { setToastApiError, setToastData } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const styles = useStyles();
const { validateEnvName, createEnvironment, loading } = useEnvironmentApi();
const { environments } = useEnvironments();
const ref = useLoading(loading);
const { setToastApiError } = useToast();
const { refetch } = useProjectRolePermissions();
const handleTypeChange = (event: React.FormEvent<HTMLInputElement>) => {
setType(event.currentTarget.value);
};
const handleEnvNameChange = (e: React.FormEvent<HTMLInputElement>) => {
setEnvName(e.currentTarget.value);
};
const goBack = () => history.goBack();
const canCreateMoreEnvs = environments.length < 7;
const validateEnvironmentName = async () => {
if (envName.length === 0) {
setNameError('Environment Id can not be empty.');
return false;
}
try {
await validateEnvName(envName);
} catch (e) {
if (e.toString().includes(NAME_EXISTS_ERROR)) {
setNameError('Name already exists');
}
return false;
}
return true;
};
const clearNameError = () => setNameError('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const validName = await validateEnvironmentName();
if (validName) {
const environment = {
name: envName,
const { createEnvironment, loading } = useEnvironmentApi();
const { refetch } = useProjectRolePermissions();
const {
name,
setName,
type,
};
setType,
getEnvPayload,
validateEnvironmentName,
clearErrors,
errors,
} = useEnvironmentForm();
const handleSubmit = async (e: Event) => {
e.preventDefault();
clearErrors();
const validName = await validateEnvironmentName();
if (validName) {
const payload = getEnvPayload();
try {
await createEnvironment(environment);
await createEnvironment(payload);
refetch();
setCreateSucceess(true);
} catch (e) {
setToastData({
title: 'Environment created',
type: 'success',
confetti: true,
});
history.push('/environments');
} catch (e: any) {
setToastApiError(e.toString());
}
}
};
const formatApiCode = () => {
return `curl --location --request POST '${
uiConfig.unleashUrl
}/api/admin/environments' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(getEnvPayload(), undefined, 2)}'`;
};
const handleCancel = () => {
history.goBack();
};
return (
<PageContent headerContent={<HeaderTitle title="Create environment" />}>
<ConditionallyRender
condition={createSuccess}
show={<CreateEnvironmentSuccess name={envName} type={type} />}
elseShow={
<ConditionallyRender
condition={canCreateMoreEnvs}
show={
<div ref={ref}>
<p className={styles.helperText} data-loading>
Environments allow you to manage your
<FormTemplate
loading={loading}
title="Create Environment"
description="Environments allow you to manage your
product lifecycle from local development
through production. Your projects and
feature toggles are accessible in all your
@ -102,92 +85,57 @@ const CreateEnvironment = () => {
that you can enable a feature toggle in a
development or test environment without
enabling the feature toggle in the
production environment.
</p>
<form onSubmit={handleSubmit}>
<FormControl component="fieldset">
<h3
className={styles.formHeader}
data-loading
production environment."
documentationLink="https://docs.getunleash.io/user_guide/environments"
formatApiCode={formatApiCode}
>
Environment Id and name
</h3>
<div
data-loading
className={
styles.environmentDetailsContainer
}
<EnvironmentForm
errors={errors}
handleSubmit={handleSubmit}
handleCancel={handleCancel}
validateEnvironmentName={validateEnvironmentName}
name={name}
type={type}
setName={setName}
setType={setType}
mode="Create"
clearErrors={clearErrors}
>
<p>
Unique env name for SDK
configurations.
</p>
<Input
label="Environment Id"
onFocus={clearNameError}
placeholder="A unique name for your environment"
onBlur={validateEnvironmentName}
error={Boolean(nameError)}
errorText={nameError}
value={envName}
onChange={handleEnvNameChange}
className={styles.inputField}
/>
</div>
<EnvironmentTypeSelector
onChange={handleTypeChange}
value={type}
/>
</FormControl>
<div className={styles.btnContainer}>
<Button
className={styles.submitButton}
variant="contained"
color="primary"
<PermissionButton
onClick={handleSubmit}
permission={ADMIN}
type="submit"
data-loading
>
Submit
</Button>{' '}
<Button
className={styles.submitButton}
variant="outlined"
color="secondary"
onClick={goBack}
data-loading
>
Cancel
</Button>
</div>
</form>
</div>
Create environment
</PermissionButton>
</EnvironmentForm>
</FormTemplate>
}
elseShow={
<>
<PageContent
headerContent={
<HeaderTitle title="Create environment" />
}
>
<Alert severity="error">
<p>
Currently Unleash does not support more
than 5 environments. If you need more
please reach out.
Currently Unleash does not support more than 7
environments. If you need more please reach out.
</p>
</Alert>
<br />
<Button
onClick={goBack}
onClick={handleCancel}
variant="contained"
color="primary"
>
Go back
</Button>
</PageContent>
</>
}
/>
}
/>
</PageContent>
);
};

View File

@ -1,41 +0,0 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
subheader: {
fontSize: theme.fontSizes.subHeader,
fontWeight: 'normal',
marginTop: '2rem',
},
container: {
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center',
},
nextSteps: {
display: 'flex',
},
step: { maxWidth: '350px', margin: '0 1.5rem', position: 'relative' },
stepBadge: {
backgroundColor: theme.palette.primary.main,
width: '30px',
height: '30px',
borderRadius: '25px',
color: '#fff',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontWeight: 'bold',
margin: '2rem auto',
},
stepParagraph: {
marginBottom: '1rem',
},
button: {
marginTop: '2.5rem',
minWidth: '150px',
},
link: {
color: theme.palette.primary.main,
},
}));

View File

@ -1,89 +0,0 @@
/* eslint-disable react/jsx-no-target-blank */
import { Button } from '@material-ui/core';
import { useHistory } from 'react-router-dom';
import CheckMarkBadge from '../../../common/CheckmarkBadge/CheckMarkBadge';
import { useStyles } from './CreateEnvironmentSuccess.styles';
import CreateEnvironmentSuccessCard from './CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard';
export interface ICreateEnvironmentSuccessProps {
name: string;
type: string;
}
const CreateEnvironmentSuccess = ({
name,
type,
}: ICreateEnvironmentSuccessProps) => {
const history = useHistory();
const styles = useStyles();
const navigateToEnvironmentList = () => {
history.push('/environments');
};
return (
<div className={styles.container}>
<CheckMarkBadge />
<h2 className={styles.subheader}>Environment created</h2>
<CreateEnvironmentSuccessCard
name={name}
type={type}
/>
<h2 className={styles.subheader}>Next steps</h2>
<div className={styles.nextSteps}>
<div className={styles.step}>
<div>
<div className={styles.stepBadge}>1</div>
<h3 className={styles.subheader}>
Update SDK version and provide the environment id to
the SDK
</h3>
<p className={styles.stepParagraph}>
By providing the environment id in the SDK the SDK
will only retrieve activation strategies for
specified environment
</p>
<a
href="https://docs.getunleash.io/user_guide/environments"
target="_blank"
className={styles.link}
>
Learn more
</a>
</div>
</div>
<div className={styles.step}>
<div>
<div className={styles.stepBadge}>2</div>
<h3 className={styles.subheader}>
Add environment specific activation strategies
</h3>
<p className={styles.stepParagraph}>
You can now select this environment when you are
adding new activation strategies on feature toggles.
</p>
<a
href="https://docs.getunleash.io/user_guide/environments"
target="_blank"
className={styles.link}
>
Learn more
</a>
</div>
</div>
</div>
<Button
variant="contained"
color="primary"
className={styles.button}
onClick={navigateToEnvironmentList}
>
Got it!
</Button>
</div>
);
};
export default CreateEnvironmentSuccess;

View File

@ -1,54 +0,0 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
container: {
minWidth: '300px',
position: 'absolute',
right: '80px',
bottom: '-475px',
zIndex: 9999,
opacity: 0,
transform: 'translateY(100px)',
},
inputField: {
width: '100%',
},
header: {
fontSize: theme.fontSizes.subHeader,
fontWeight: 'normal',
borderBottom: `1px solid ${theme.palette.grey[300]}`,
padding: '1rem',
},
body: { padding: '1rem' },
subheader: {
display: 'flex',
alignItems: 'center',
fontSize: theme.fontSizes.bodySize,
fontWeight: 'normal',
},
icon: {
marginRight: '0.5rem',
fill: theme.palette.grey[600],
},
formHeader: {
fontSize: theme.fontSizes.bodySize,
},
buttonGroup: {
marginTop: '2rem',
display: 'flex',
justifyContent: 'space-between',
},
editEnvButton: {
width: '150px',
},
fadeInBottomEnter: {
transform: 'translateY(0)',
opacity: '1',
transition: 'transform 0.4s ease, opacity .4s ease',
},
fadeInBottomLeave: {
transform: 'translateY(100px)',
opacity: '0',
transition: 'transform 0.4s ease, opacity 0.4s ease',
},
}));

View File

@ -1,108 +1,99 @@
import { CloudCircle } from '@material-ui/icons';
import { useEffect, useState } from 'react';
import EnvironmentTypeSelector from '../form/EnvironmentTypeSelector/EnvironmentTypeSelector';
import { useStyles } from './EditEnvironment.styles';
import { IEnvironment } from '../../../interfaces/environments';
import { useHistory, useParams } from 'react-router-dom';
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import useLoading from '../../../hooks/useLoading';
import useEnvironments from '../../../hooks/api/getters/useEnvironments/useEnvironments';
import Dialogue from '../../common/Dialogue';
import useEnvironment from '../../../hooks/api/getters/useEnvironment/useEnvironment';
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
import useToast from '../../../hooks/useToast';
import FormTemplate from '../../common/FormTemplate/FormTemplate';
import PermissionButton from '../../common/PermissionButton/PermissionButton';
import { ADMIN } from '../../providers/AccessProvider/permissions';
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
import useEnvironmentForm from '../hooks/useEnvironmentForm';
interface IEditEnvironmentProps {
env: IEnvironment;
setEditEnvironment: React.Dispatch<React.SetStateAction<boolean>>;
editEnvironment: boolean;
setToastData: React.Dispatch<React.SetStateAction<any>>;
}
const EditEnvironment = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const EditEnvironment = ({
env,
setEditEnvironment,
editEnvironment,
setToastData,
}: IEditEnvironmentProps) => {
const styles = useStyles();
const [type, setType] = useState(env.type);
const { updateEnvironment, loading } = useEnvironmentApi();
const { refetch } = useEnvironments();
const ref = useLoading(loading);
const { id } = useParams<{ id: string }>();
const { environment } = useEnvironment(id);
const { updateEnvironment } = useEnvironmentApi();
useEffect(() => {
setType(env.type);
}, [env.type]);
const history = useHistory();
const { name, type, setName, setType, errors, clearErrors } =
useEnvironmentForm(environment.name, environment.type);
const { refetch } = useProjectRolePermissions();
const handleTypeChange = (event: React.FormEvent<HTMLInputElement>) => {
setType(event.currentTarget.value);
};
const isDisabled = () => {
return type === env.type;
};
const handleCancel = () => {
setEditEnvironment(false);
resetFields();
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const updatedEnv = {
sortOrder: env.sortOrder,
const editPayload = () => {
return {
type,
sortOrder: environment.sortOrder,
};
};
const formatApiCode = () => {
return `curl --location --request PUT '${
uiConfig.unleashUrl
}/api/admin/environments/update/${id}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${JSON.stringify(editPayload(), undefined, 2)}'`;
};
const handleSubmit = async (e: Event) => {
e.preventDefault();
try {
await updateEnvironment(env.name, updatedEnv);
await updateEnvironment(id, editPayload());
refetch();
history.push('/environments');
setToastData({
type: 'success',
show: true,
text: 'Successfully updated environment.',
title: 'Successfully updated environment.',
});
resetFields();
refetch();
} catch (e) {
setToastData({
show: true,
type: 'error',
text: e.toString(),
});
} finally {
setEditEnvironment(false);
} catch (e: any) {
setToastApiError(e.toString());
}
};
const resetFields = () => {
setType(env.type);
const handleCancel = () => {
history.goBack();
};
const formId = 'edit-environment-form';
return (
<Dialogue
open={editEnvironment}
<FormTemplate
title="Edit environment"
onClose={handleCancel}
onClick={handleSubmit}
primaryButtonText="Save"
secondaryButtonText="Cancel"
disabledPrimaryButton={isDisabled()}
formId={formId}
description="Environments allow you to manage your
product lifecycle from local development
through production. Your projects and
feature toggles are accessible in all your
environments, but they can take different
configurations per environment. This means
that you can enable a feature toggle in a
development or test environment without
enabling the feature toggle in the
production environment."
documentationLink="https://docs.getunleash.io/user_guide/environments"
formatApiCode={formatApiCode}
>
<div className={styles.body} ref={ref}>
<h3 className={styles.formHeader} data-loading>
Environment Id
</h3>
<h3 className={styles.subheader} data-loading>
<CloudCircle className={styles.icon} /> {env.name}
</h3>
<form id={formId}>
<EnvironmentTypeSelector
onChange={handleTypeChange}
value={type}
/>
</form>
</div>
</Dialogue>
<EnvironmentForm
handleSubmit={handleSubmit}
handleCancel={handleCancel}
name={name}
type={type}
setName={setName}
setType={setType}
mode="Edit"
errors={errors}
clearErrors={clearErrors}
>
<PermissionButton
onClick={handleSubmit}
permission={ADMIN}
type="submit"
>
Edit environment
</PermissionButton>
</EnvironmentForm>
</FormTemplate>
);
};

View File

@ -0,0 +1,47 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
container: {
maxWidth: '440px',
},
form: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
input: { width: '100%', marginBottom: '1rem' },
label: {
minWidth: '30px',
[theme.breakpoints.down(600)]: {
minWidth: 'auto',
},
},
buttonContainer: {
marginTop: 'auto',
display: 'flex',
justifyContent: 'flex-end',
},
cancelButton: {
marginRight: '1.5rem',
},
inputDescription: {
marginBottom: '0.5rem',
},
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',
},
}));

View File

@ -0,0 +1,74 @@
import { Button } from '@material-ui/core';
import { useStyles } from './EnvironmentForm.styles';
import React from 'react';
import Input from '../../common/Input/Input';
import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector';
import { trim } from '../../common/util';
interface IEnvironmentForm {
name: string;
type: string;
setName: React.Dispatch<React.SetStateAction<string>>;
setType: React.Dispatch<React.SetStateAction<string>>;
validateEnvironmentName?: (e: any) => void;
handleSubmit: (e: any) => void;
handleCancel: () => void;
errors: { [key: string]: string };
mode: string;
clearErrors: () => void;
}
const EnvironmentForm: React.FC<IEnvironmentForm> = ({
children,
handleSubmit,
handleCancel,
name,
type,
setName,
setType,
validateEnvironmentName,
errors,
mode,
clearErrors,
}) => {
const styles = useStyles();
return (
<form onSubmit={handleSubmit} className={styles.form}>
<h3 className={styles.formHeader}>Environment information</h3>
<div className={styles.container}>
<p className={styles.inputDescription}>
What is your environment name? (Can't be changed later)
</p>
<Input
className={styles.input}
label="Environment name"
value={name}
onChange={e => setName(trim(e.target.value))}
error={Boolean(errors.name)}
errorText={errors.name}
onFocus={() => clearErrors()}
onBlur={validateEnvironmentName}
disabled={mode === 'Edit'}
/>
<p className={styles.inputDescription}>
What type of environment do you want to create?
</p>
<EnvironmentTypeSelector
onChange={e => setType(e.currentTarget.value)}
value={type}
/>
</div>
<div className={styles.buttonContainer}>
<Button onClick={handleCancel} className={styles.cancelButton}>
Cancel
</Button>
{children}
</div>
</form>
);
};
export default EnvironmentForm;

View File

@ -6,6 +6,7 @@ export const useStyles = makeStyles(theme => ({
},
formHeader: {
fontWeight: 'bold',
//@ts-ignore
fontSize: theme.fontSizes.bodySize,
marginTop: '1.5rem',
marginBottom: '0.5rem',

View File

@ -18,10 +18,6 @@ const EnvironmentTypeSelector = ({
const styles = useStyles();
return (
<FormControl component="fieldset">
<h3 className={styles.formHeader} data-loading>
Environment Type
</h3>
<RadioGroup
data-loading
value={value}

View File

@ -1,12 +1,13 @@
import { CloudCircle } from '@material-ui/icons';
import StringTruncator from '../../../../common/StringTruncator/StringTruncator';
import { ICreateEnvironmentSuccessProps } from '../CreateEnvironmentSuccess';
import { useStyles } from './CreateEnvironmentSuccessCard.styles';
import StringTruncator from '../../../common/StringTruncator/StringTruncator';
import { useStyles } from './EnvironmentCard.styles';
const CreateEnvironmentSuccessCard = ({
name,
type,
}: ICreateEnvironmentSuccessProps) => {
interface IEnvironmentProps {
name: string;
type: string;
}
const EnvironmentCard = ({ name, type }: IEnvironmentProps) => {
const styles = useStyles();
return (
<div className={styles.container}>
@ -30,4 +31,4 @@ const CreateEnvironmentSuccessCard = ({
);
};
export default CreateEnvironmentSuccessCard;
export default EnvironmentCard;

View File

@ -3,7 +3,7 @@ import React from 'react';
import { IEnvironment } from '../../../../interfaces/environments';
import Dialogue from '../../../common/Dialogue';
import Input from '../../../common/Input/Input';
import CreateEnvironmentSuccessCard from '../../CreateEnvironment/CreateEnvironmentSuccess/CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard';
import EnvironmentCard from '../EnvironmentCard/EnvironmentCard';
import { useStyles } from './EnvironmentDeleteConfirm.styles';
interface IEnviromentDeleteConfirmProps {
@ -52,7 +52,7 @@ const EnvironmentDeleteConfirm = ({
strategies that are active in this environment across all
feature toggles.
</Alert>
<CreateEnvironmentSuccessCard name={env?.name} type={env?.type} />
<EnvironmentCard name={env?.name} type={env?.type} />
<p className={styles.deleteParagraph}>
In order to delete this environment, please enter the id of the

View File

@ -17,7 +17,6 @@ import useToast from '../../../hooks/useToast';
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
import EnvironmentListItem from './EnvironmentListItem/EnvironmentListItem';
import { mutate } from 'swr';
import EditEnvironment from '../EditEnvironment/EditEnvironment';
import EnvironmentToggleConfirm from './EnvironmentToggleConfirm/EnvironmentToggleConfirm';
import useProjectRolePermissions from '../../../hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
@ -31,7 +30,6 @@ const EnvironmentList = () => {
protected: false,
};
const { environments, refetch } = useEnvironments();
const [editEnvironment, setEditEnvironment] = useState(false);
const { refetch: refetchProjectRolePermissions } =
useProjectRolePermissions();
@ -151,7 +149,6 @@ const EnvironmentList = () => {
<EnvironmentListItem
key={env.name}
env={env}
setEditEnvironment={setEditEnvironment}
setDeldialogue={setDeldialogue}
setSelectedEnv={setSelectedEnv}
setToggleDialog={setToggleDialog}
@ -195,13 +192,6 @@ const EnvironmentList = () => {
confirmName={confirmName}
setConfirmName={setConfirmName}
/>
<EditEnvironment
env={selectedEnv}
setEditEnvironment={setEditEnvironment}
editEnvironment={editEnvironment}
setToastData={setToastData}
/>
<EnvironmentToggleConfirm
env={selectedEnv}
open={toggleDialog}

View File

@ -25,12 +25,12 @@ import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
import { XYCoord } from 'dnd-core';
import DisabledIndicator from '../../../common/DisabledIndicator/DisabledIndicator';
import StringTruncator from '../../../common/StringTruncator/StringTruncator';
import { useHistory } from 'react-router-dom';
interface IEnvironmentListItemProps {
env: IEnvironment;
setSelectedEnv: React.Dispatch<React.SetStateAction<IEnvironment>>;
setDeldialogue: React.Dispatch<React.SetStateAction<boolean>>;
setEditEnvironment: React.Dispatch<React.SetStateAction<boolean>>;
setToggleDialog: React.Dispatch<React.SetStateAction<boolean>>;
index: number;
moveListItem: (dragIndex: number, hoverIndex: number) => IEnvironment[];
@ -51,8 +51,8 @@ const EnvironmentListItem = ({
moveListItem,
moveListItemApi,
setToggleDialog,
setEditEnvironment,
}: IEnvironmentListItemProps) => {
const history = useHistory();
const ref = useRef<HTMLDivElement>(null);
const ACCEPT_TYPE = 'LIST_ITEM';
const [{ isDragging }, drag] = useDrag({
@ -178,8 +178,7 @@ const EnvironmentListItem = ({
aria-label="update"
disabled={env.protected}
onClick={() => {
setSelectedEnv(env);
setEditEnvironment(prev => !prev);
history.push(`/environments/${env.name}`);
}}
>
<Edit />

View File

@ -4,7 +4,7 @@ import React from 'react';
import { IEnvironment } from '../../../../interfaces/environments';
import ConditionallyRender from '../../../common/ConditionallyRender';
import Dialogue from '../../../common/Dialogue';
import CreateEnvironmentSuccessCard from '../../CreateEnvironment/CreateEnvironmentSuccess/CreateEnvironmentSuccessCard/CreateEnvironmentSuccessCard';
import EnvironmentCard from '../EnvironmentCard/EnvironmentCard';
interface IEnvironmentToggleConfirmProps {
env: IEnvironment;
@ -52,10 +52,7 @@ const EnvironmentToggleConfirm = ({
}
/>
<CreateEnvironmentSuccessCard
name={env?.name}
type={env?.type}
/>
<EnvironmentCard name={env?.name} type={env?.type} />
</Dialogue>
);
};

View File

@ -0,0 +1,69 @@
import { useEffect, useState } from 'react';
import useEnvironmentApi from '../../../hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
const useEnvironmentForm = (
initialName = '',
initialType = 'development'
) => {
const NAME_EXISTS_ERROR = 'Error: Environment';
const [name, setName] = useState(initialName);
const [type, setType] = useState(initialType);
const [errors, setErrors] = useState({});
useEffect(() => {
setName(initialName);
}, [initialName]);
useEffect(() => {
setType(initialType);
}, [initialType]);
const { validateEnvName } = useEnvironmentApi();
const getEnvPayload = () => {
return {
name,
type,
};
};
const validateEnvironmentName = async () => {
if (name.length === 0) {
setErrors(prev => ({
...prev,
name: 'Environment name can not be empty',
}));
return false;
}
try {
await validateEnvName(name);
} catch (e: any) {
if (e.toString().includes(NAME_EXISTS_ERROR)) {
setErrors(prev => ({
...prev,
name: 'Name already exists',
}));
}
return false;
}
return true;
};
const clearErrors = () => {
setErrors({});
};
return {
name,
setName,
type,
setType,
getEnvPayload,
validateEnvironmentName,
clearErrors,
errors,
};
};
export default useEnvironmentForm;

View File

@ -214,6 +214,14 @@ Array [
"title": "Environments",
"type": "protected",
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"path": "/environments/:id",
"title": "Edit",
"type": "protected",
},
Object {
"component": [Function],
"flag": "EEA",

View File

@ -37,13 +37,15 @@ import Project from '../project/Project/Project';
import RedirectFeatureViewPage from '../../page/features/redirect';
import RedirectArchive from '../feature/RedirectArchive/RedirectArchive';
import EnvironmentList from '../environments/EnvironmentList/EnvironmentList';
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
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';
import CreateApiToken from '../admin/api-token/CreateApiToken/CreateApiToken';
import CreateEnvironment from '../environments/CreateEnvironment/CreateEnvironment';
import EditEnvironment from '../environments/EditEnvironment/EditEnvironment';
export const routes = [
// Project
@ -255,6 +257,14 @@ export const routes = [
layout: 'main',
menu: {},
},
{
path: '/environments/:id',
title: 'Edit',
component: EditEnvironment,
type: 'protected',
layout: 'main',
menu: {},
},
{
path: '/environments',
title: 'Environments',

View File

@ -0,0 +1,10 @@
import { IEnvironment } from '../../../../interfaces/environments';
export const defaultEnvironment: IEnvironment = {
name: '',
type: '',
createdAt: '',
sortOrder: 0,
enabled: false,
protected: false
};

View File

@ -0,0 +1,49 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react';
import { formatApiPath } from '../../../../utils/format-path';
import { IEnvironment } from '../../../../interfaces/environments';
import handleErrorResponses from '../httpErrorResponseHandler';
import { defaultEnvironment } from './defaultEnvironment';
const useEnvironment = (
id: string,
options: SWRConfiguration = {}
) => {
const fetcher = async () => {
const path = formatApiPath(
`api/admin/environments/${id}`
);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Environment data'))
.then(res => res.json());
};
const FEATURE_CACHE_KEY = `api/admin/environments/${id}`;
const { data, error } = useSWR<IEnvironment>(FEATURE_CACHE_KEY, fetcher, {
...options,
});
const [loading, setLoading] = useState(!error && !data);
const refetch = () => {
mutate(FEATURE_CACHE_KEY);
};
useEffect(() => {
setLoading(!error && !data);
}, [data, error]);
return {
environment: data || defaultEnvironment,
error,
loading,
refetch,
FEATURE_CACHE_KEY,
};
};
export default useEnvironment;