mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
feat: create and edit environment required approvals (#9621)
This commit is contained in:
parent
07a4106f48
commit
1bd328f4e1
@ -27,6 +27,8 @@ const CreateEnvironment = () => {
|
|||||||
setName,
|
setName,
|
||||||
type,
|
type,
|
||||||
setType,
|
setType,
|
||||||
|
requiredApprovals,
|
||||||
|
setRequiredApprovals,
|
||||||
getEnvPayload,
|
getEnvPayload,
|
||||||
validateEnvironmentName,
|
validateEnvironmentName,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
@ -91,6 +93,8 @@ const CreateEnvironment = () => {
|
|||||||
type={type}
|
type={type}
|
||||||
setName={setName}
|
setName={setName}
|
||||||
setType={setType}
|
setType={setType}
|
||||||
|
requiredApprovals={requiredApprovals}
|
||||||
|
setRequiredApprovals={setRequiredApprovals}
|
||||||
mode='Create'
|
mode='Create'
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
Limit={
|
Limit={
|
||||||
|
@ -12,6 +12,7 @@ import useEnvironmentForm from '../hooks/useEnvironmentForm';
|
|||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { GO_BACK } from 'constants/navigate';
|
import { GO_BACK } from 'constants/navigate';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
const EditEnvironment = () => {
|
const EditEnvironment = () => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
@ -21,14 +22,30 @@ const EditEnvironment = () => {
|
|||||||
const { updateEnvironment } = useEnvironmentApi();
|
const { updateEnvironment } = useEnvironmentApi();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { name, type, setName, setType, errors, clearErrors } =
|
const {
|
||||||
useEnvironmentForm(environment.name, environment.type);
|
name,
|
||||||
|
type,
|
||||||
|
setName,
|
||||||
|
setType,
|
||||||
|
requiredApprovals,
|
||||||
|
setRequiredApprovals,
|
||||||
|
errors,
|
||||||
|
clearErrors,
|
||||||
|
} = useEnvironmentForm(
|
||||||
|
environment.name,
|
||||||
|
environment.type,
|
||||||
|
environment.requiredApprovals,
|
||||||
|
);
|
||||||
const { refetch } = usePermissions();
|
const { refetch } = usePermissions();
|
||||||
|
const globalChangeRequestConfigEnabled = useUiFlag(
|
||||||
|
'globalChangeRequestConfig',
|
||||||
|
);
|
||||||
|
|
||||||
const editPayload = () => {
|
const editPayload = () => {
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
sortOrder: environment.sortOrder,
|
sortOrder: environment.sortOrder,
|
||||||
|
...(globalChangeRequestConfigEnabled ? { requiredApprovals } : {}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,6 +101,8 @@ const EditEnvironment = () => {
|
|||||||
type={type}
|
type={type}
|
||||||
setName={setName}
|
setName={setName}
|
||||||
setType={setType}
|
setType={setType}
|
||||||
|
requiredApprovals={requiredApprovals}
|
||||||
|
setRequiredApprovals={setRequiredApprovals}
|
||||||
mode='Edit'
|
mode='Edit'
|
||||||
errors={errors}
|
errors={errors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
styled,
|
||||||
|
} from '@mui/material';
|
||||||
|
import KeyboardArrowDownOutlined from '@mui/icons-material/KeyboardArrowDownOutlined';
|
||||||
|
import GeneralSelect from '../../common/GeneralSelect/GeneralSelect';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
interface IEnvironmentChangeRequestProps {
|
||||||
|
onChange: (approvals: number | null) => void;
|
||||||
|
value: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledRadioGroup = styled(RadioGroup)({
|
||||||
|
flexDirection: 'row',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledRadioButtonGroup = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledRequiredApprovals = styled('p')(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
marginBottom: theme.spacing(0.5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const useApprovalOptions = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const approvalOptions = Array.from(Array(10).keys())
|
||||||
|
.map((key) => String(key + 1))
|
||||||
|
.map((key) => {
|
||||||
|
const labelText = key === '1' ? 'approval' : 'approvals';
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label: `${key} ${labelText}`,
|
||||||
|
sx: { fontSize: theme.fontSizes.smallBody },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return approvalOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeRequestSelector = ({
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
}: IEnvironmentChangeRequestProps) => {
|
||||||
|
const approvalOptions = useApprovalOptions();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl component='fieldset'>
|
||||||
|
<StyledRadioGroup
|
||||||
|
data-loading
|
||||||
|
value={value ? 'yes' : 'no'}
|
||||||
|
onChange={(event) => {
|
||||||
|
if (event.target.value === 'yes') {
|
||||||
|
onChange(1);
|
||||||
|
} else {
|
||||||
|
onChange(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledRadioButtonGroup>
|
||||||
|
<FormControlLabel
|
||||||
|
value='no'
|
||||||
|
label='No'
|
||||||
|
control={<Radio />}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value='yes'
|
||||||
|
label='Yes'
|
||||||
|
control={<Radio />}
|
||||||
|
/>
|
||||||
|
</StyledRadioButtonGroup>
|
||||||
|
</StyledRadioGroup>
|
||||||
|
{value ? (
|
||||||
|
<>
|
||||||
|
<StyledRequiredApprovals>
|
||||||
|
Required approvals
|
||||||
|
</StyledRequiredApprovals>
|
||||||
|
<GeneralSelect
|
||||||
|
label='Set required approvals for the current environment'
|
||||||
|
visuallyHideLabel
|
||||||
|
sx={{ width: '160px' }}
|
||||||
|
options={approvalOptions}
|
||||||
|
value={value ? String(value) : undefined}
|
||||||
|
onChange={(approvals) => onChange(Number(approvals))}
|
||||||
|
IconComponent={KeyboardArrowDownOutlined}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
@ -1,14 +1,19 @@
|
|||||||
import { Box, Button, styled } from '@mui/material';
|
import { Box, Button, styled } from '@mui/material';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
import EnvironmentTypeSelector from './EnvironmentTypeSelector/EnvironmentTypeSelector';
|
import { EnvironmentTypeSelector } from './EnvironmentTypeSelector';
|
||||||
|
import { ChangeRequestSelector } from './ChangeRequestSelector';
|
||||||
import { trim } from 'component/common/util';
|
import { trim } from 'component/common/util';
|
||||||
|
import { useUiFlag } from '../../../hooks/useUiFlag';
|
||||||
|
|
||||||
interface IEnvironmentForm {
|
interface IEnvironmentForm {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
requiredApprovals: number | null;
|
||||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setType: React.Dispatch<React.SetStateAction<string>>;
|
setType: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
setRequiredApprovals: React.Dispatch<React.SetStateAction<number | null>>;
|
||||||
|
|
||||||
validateEnvironmentName?: (e: any) => void;
|
validateEnvironmentName?: (e: any) => void;
|
||||||
handleSubmit: (e: any) => void;
|
handleSubmit: (e: any) => void;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
@ -67,14 +72,19 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
|||||||
handleCancel,
|
handleCancel,
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
requiredApprovals,
|
||||||
setName,
|
setName,
|
||||||
setType,
|
setType,
|
||||||
|
setRequiredApprovals,
|
||||||
validateEnvironmentName,
|
validateEnvironmentName,
|
||||||
errors,
|
errors,
|
||||||
mode,
|
mode,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
Limit,
|
Limit,
|
||||||
}) => {
|
}) => {
|
||||||
|
const globalChangeRequestConfigEnabled = useUiFlag(
|
||||||
|
'globalChangeRequestConfig',
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<StyledForm onSubmit={handleSubmit}>
|
<StyledForm onSubmit={handleSubmit}>
|
||||||
<StyledFormHeader>Environment information</StyledFormHeader>
|
<StyledFormHeader>Environment information</StyledFormHeader>
|
||||||
@ -102,6 +112,19 @@ const EnvironmentForm: React.FC<IEnvironmentForm> = ({
|
|||||||
onChange={(e) => setType(e.currentTarget.value)}
|
onChange={(e) => setType(e.currentTarget.value)}
|
||||||
value={type}
|
value={type}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{globalChangeRequestConfigEnabled ? (
|
||||||
|
<>
|
||||||
|
<StyledInputDescription sx={{ mt: 2 }}>
|
||||||
|
Would you like to pre-define change requests for
|
||||||
|
this environment?
|
||||||
|
</StyledInputDescription>
|
||||||
|
<ChangeRequestSelector
|
||||||
|
onChange={setRequiredApprovals}
|
||||||
|
value={requiredApprovals}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
|
|
||||||
<LimitContainer>{Limit}</LimitContainer>
|
<LimitContainer>{Limit}</LimitContainer>
|
||||||
|
@ -21,7 +21,7 @@ const StyledRadioButtonGroup = styled('div')({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
});
|
});
|
||||||
|
|
||||||
const EnvironmentTypeSelector = ({
|
export const EnvironmentTypeSelector = ({
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
}: IEnvironmentTypeSelectorProps) => {
|
}: IEnvironmentTypeSelectorProps) => {
|
||||||
@ -56,5 +56,3 @@ const EnvironmentTypeSelector = ({
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EnvironmentTypeSelector;
|
|
@ -21,7 +21,7 @@ import type {
|
|||||||
} from 'interfaces/environments';
|
} from 'interfaces/environments';
|
||||||
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import EnvironmentTypeSelector from 'component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector';
|
import { EnvironmentTypeSelector } from 'component/environments/EnvironmentForm/EnvironmentTypeSelector';
|
||||||
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { EnvironmentProjectSelect } from './EnvironmentProjectSelect/EnvironmentProjectSelect';
|
import { EnvironmentProjectSelect } from './EnvironmentProjectSelect/EnvironmentProjectSelect';
|
||||||
import { SelectProjectInput } from 'component/admin/apiToken/ApiTokenForm/ProjectSelector/SelectProjectInput/SelectProjectInput';
|
import { SelectProjectInput } from 'component/admin/apiToken/ApiTokenForm/ProjectSelector/SelectProjectInput/SelectProjectInput';
|
||||||
|
@ -2,9 +2,16 @@ import { useEffect, useState } from 'react';
|
|||||||
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
|
||||||
const useEnvironmentForm = (initialName = '', initialType = 'development') => {
|
const useEnvironmentForm = (
|
||||||
|
initialName = '',
|
||||||
|
initialType = 'development',
|
||||||
|
initialRequiredApprovals: number | null = null,
|
||||||
|
) => {
|
||||||
const [name, setName] = useState(initialName);
|
const [name, setName] = useState(initialName);
|
||||||
const [type, setType] = useState(initialType);
|
const [type, setType] = useState(initialType);
|
||||||
|
const [requiredApprovals, setRequiredApprovals] = useState(
|
||||||
|
initialRequiredApprovals,
|
||||||
|
);
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -15,12 +22,17 @@ const useEnvironmentForm = (initialName = '', initialType = 'development') => {
|
|||||||
setType(initialType);
|
setType(initialType);
|
||||||
}, [initialType]);
|
}, [initialType]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRequiredApprovals(initialRequiredApprovals);
|
||||||
|
}, [initialRequiredApprovals]);
|
||||||
|
|
||||||
const { validateEnvName } = useEnvironmentApi();
|
const { validateEnvName } = useEnvironmentApi();
|
||||||
|
|
||||||
const getEnvPayload = () => {
|
const getEnvPayload = () => {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
...(requiredApprovals ? { requiredApprovals } : {}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,6 +63,8 @@ const useEnvironmentForm = (initialName = '', initialType = 'development') => {
|
|||||||
setName,
|
setName,
|
||||||
type,
|
type,
|
||||||
setType,
|
setType,
|
||||||
|
requiredApprovals,
|
||||||
|
setRequiredApprovals,
|
||||||
getEnvPayload,
|
getEnvPayload,
|
||||||
validateEnvironmentName,
|
validateEnvironmentName,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
|
@ -12,6 +12,7 @@ export interface IEnvironment {
|
|||||||
apiTokenCount?: number;
|
apiTokenCount?: number;
|
||||||
enabledToggleCount?: number;
|
enabledToggleCount?: number;
|
||||||
lastSeenAt: string;
|
lastSeenAt: string;
|
||||||
|
requiredApprovals?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectEnvironment extends IEnvironment {
|
export interface IProjectEnvironment extends IEnvironment {
|
||||||
|
@ -92,6 +92,7 @@ export type UiFlags = {
|
|||||||
edgeObservability?: boolean;
|
edgeObservability?: boolean;
|
||||||
adminNavUI?: boolean;
|
adminNavUI?: boolean;
|
||||||
tagTypeColor?: boolean;
|
tagTypeColor?: boolean;
|
||||||
|
globalChangeRequestConfig?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -21,7 +21,7 @@ interface IEnvironmentsTable {
|
|||||||
sort_order: number;
|
sort_order: number;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
protected: boolean;
|
protected: boolean;
|
||||||
required_approvals?: number;
|
required_approvals?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEnvironmentsWithCountsTable extends IEnvironmentsTable {
|
interface IEnvironmentsWithCountsTable extends IEnvironmentsTable {
|
||||||
|
@ -198,7 +198,7 @@ export interface IEnvironment {
|
|||||||
projectCount?: number;
|
projectCount?: number;
|
||||||
apiTokenCount?: number;
|
apiTokenCount?: number;
|
||||||
enabledToggleCount?: number;
|
enabledToggleCount?: number;
|
||||||
requiredApprovals?: number;
|
requiredApprovals?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectEnvironment extends IEnvironment {
|
export interface IProjectEnvironment extends IEnvironment {
|
||||||
@ -216,7 +216,7 @@ export interface IEnvironmentCreate {
|
|||||||
type: string;
|
type: string;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
requiredApprovals?: number;
|
requiredApprovals?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEnvironmentClone {
|
export interface IEnvironmentClone {
|
||||||
|
Loading…
Reference in New Issue
Block a user