diff --git a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
index 4b8912506a..f438cd063c 100644
--- a/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
+++ b/frontend/src/component/environments/CreateEnvironment/CreateEnvironment.tsx
@@ -27,6 +27,8 @@ const CreateEnvironment = () => {
setName,
type,
setType,
+ requiredApprovals,
+ setRequiredApprovals,
getEnvPayload,
validateEnvironmentName,
clearErrors,
@@ -91,6 +93,8 @@ const CreateEnvironment = () => {
type={type}
setName={setName}
setType={setType}
+ requiredApprovals={requiredApprovals}
+ setRequiredApprovals={setRequiredApprovals}
mode='Create'
clearErrors={clearErrors}
Limit={
diff --git a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
index 7ad42f9126..00bc1ba36b 100644
--- a/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
+++ b/frontend/src/component/environments/EditEnvironment/EditEnvironment.tsx
@@ -12,6 +12,7 @@ import useEnvironmentForm from '../hooks/useEnvironmentForm';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { GO_BACK } from 'constants/navigate';
+import { useUiFlag } from 'hooks/useUiFlag';
const EditEnvironment = () => {
const { uiConfig } = useUiConfig();
@@ -21,14 +22,30 @@ const EditEnvironment = () => {
const { updateEnvironment } = useEnvironmentApi();
const navigate = useNavigate();
- const { name, type, setName, setType, errors, clearErrors } =
- useEnvironmentForm(environment.name, environment.type);
+ const {
+ name,
+ type,
+ setName,
+ setType,
+ requiredApprovals,
+ setRequiredApprovals,
+ errors,
+ clearErrors,
+ } = useEnvironmentForm(
+ environment.name,
+ environment.type,
+ environment.requiredApprovals,
+ );
const { refetch } = usePermissions();
+ const globalChangeRequestConfigEnabled = useUiFlag(
+ 'globalChangeRequestConfig',
+ );
const editPayload = () => {
return {
type,
sortOrder: environment.sortOrder,
+ ...(globalChangeRequestConfigEnabled ? { requiredApprovals } : {}),
};
};
@@ -84,6 +101,8 @@ const EditEnvironment = () => {
type={type}
setName={setName}
setType={setType}
+ requiredApprovals={requiredApprovals}
+ setRequiredApprovals={setRequiredApprovals}
mode='Edit'
errors={errors}
clearErrors={clearErrors}
diff --git a/frontend/src/component/environments/EnvironmentForm/ChangeRequestSelector.tsx b/frontend/src/component/environments/EnvironmentForm/ChangeRequestSelector.tsx
new file mode 100644
index 0000000000..59aa76e330
--- /dev/null
+++ b/frontend/src/component/environments/EnvironmentForm/ChangeRequestSelector.tsx
@@ -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 (
+
+ {
+ if (event.target.value === 'yes') {
+ onChange(1);
+ } else {
+ onChange(null);
+ }
+ }}
+ >
+
+ }
+ />
+ }
+ />
+
+
+ {value ? (
+ <>
+
+ Required approvals
+
+ onChange(Number(approvals))}
+ IconComponent={KeyboardArrowDownOutlined}
+ fullWidth
+ />
+ >
+ ) : null}
+
+ );
+};
diff --git a/frontend/src/component/environments/EnvironmentForm/EnvironmentForm.tsx b/frontend/src/component/environments/EnvironmentForm/EnvironmentForm.tsx
index 95fa0b6fd2..fae592d9ff 100644
--- a/frontend/src/component/environments/EnvironmentForm/EnvironmentForm.tsx
+++ b/frontend/src/component/environments/EnvironmentForm/EnvironmentForm.tsx
@@ -1,14 +1,19 @@
import { Box, Button, styled } from '@mui/material';
import type React from 'react';
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 { useUiFlag } from '../../../hooks/useUiFlag';
interface IEnvironmentForm {
name: string;
type: string;
+ requiredApprovals: number | null;
setName: React.Dispatch>;
setType: React.Dispatch>;
+ setRequiredApprovals: React.Dispatch>;
+
validateEnvironmentName?: (e: any) => void;
handleSubmit: (e: any) => void;
handleCancel: () => void;
@@ -67,14 +72,19 @@ const EnvironmentForm: React.FC = ({
handleCancel,
name,
type,
+ requiredApprovals,
setName,
setType,
+ setRequiredApprovals,
validateEnvironmentName,
errors,
mode,
clearErrors,
Limit,
}) => {
+ const globalChangeRequestConfigEnabled = useUiFlag(
+ 'globalChangeRequestConfig',
+ );
return (
Environment information
@@ -102,6 +112,19 @@ const EnvironmentForm: React.FC = ({
onChange={(e) => setType(e.currentTarget.value)}
value={type}
/>
+
+ {globalChangeRequestConfigEnabled ? (
+ <>
+
+ Would you like to pre-define change requests for
+ this environment?
+
+
+ >
+ ) : null}
{Limit}
diff --git a/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx b/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector.tsx
similarity index 95%
rename from frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx
rename to frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector.tsx
index 60f73f2c8c..02d73b03b3 100644
--- a/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.tsx
+++ b/frontend/src/component/environments/EnvironmentForm/EnvironmentTypeSelector.tsx
@@ -21,7 +21,7 @@ const StyledRadioButtonGroup = styled('div')({
flexDirection: 'column',
});
-const EnvironmentTypeSelector = ({
+export const EnvironmentTypeSelector = ({
onChange,
value,
}: IEnvironmentTypeSelectorProps) => {
@@ -56,5 +56,3 @@ const EnvironmentTypeSelector = ({
);
};
-
-export default EnvironmentTypeSelector;
diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentCloneModal/EnvironmentCloneModal.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentCloneModal/EnvironmentCloneModal.tsx
index 6f7da59ace..ee3dfc90f7 100644
--- a/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentCloneModal/EnvironmentCloneModal.tsx
+++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentCloneModal/EnvironmentCloneModal.tsx
@@ -21,7 +21,7 @@ import type {
} from 'interfaces/environments';
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
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 { EnvironmentProjectSelect } from './EnvironmentProjectSelect/EnvironmentProjectSelect';
import { SelectProjectInput } from 'component/admin/apiToken/ApiTokenForm/ProjectSelector/SelectProjectInput/SelectProjectInput';
diff --git a/frontend/src/component/environments/hooks/useEnvironmentForm.ts b/frontend/src/component/environments/hooks/useEnvironmentForm.ts
index a8da3299df..790518705b 100644
--- a/frontend/src/component/environments/hooks/useEnvironmentForm.ts
+++ b/frontend/src/component/environments/hooks/useEnvironmentForm.ts
@@ -2,9 +2,16 @@ import { useEffect, useState } from 'react';
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
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 [type, setType] = useState(initialType);
+ const [requiredApprovals, setRequiredApprovals] = useState(
+ initialRequiredApprovals,
+ );
const [errors, setErrors] = useState({});
useEffect(() => {
@@ -15,12 +22,17 @@ const useEnvironmentForm = (initialName = '', initialType = 'development') => {
setType(initialType);
}, [initialType]);
+ useEffect(() => {
+ setRequiredApprovals(initialRequiredApprovals);
+ }, [initialRequiredApprovals]);
+
const { validateEnvName } = useEnvironmentApi();
const getEnvPayload = () => {
return {
name,
type,
+ ...(requiredApprovals ? { requiredApprovals } : {}),
};
};
@@ -51,6 +63,8 @@ const useEnvironmentForm = (initialName = '', initialType = 'development') => {
setName,
type,
setType,
+ requiredApprovals,
+ setRequiredApprovals,
getEnvPayload,
validateEnvironmentName,
clearErrors,
diff --git a/frontend/src/interfaces/environments.ts b/frontend/src/interfaces/environments.ts
index 37e535b481..b5f9fcd55f 100644
--- a/frontend/src/interfaces/environments.ts
+++ b/frontend/src/interfaces/environments.ts
@@ -12,6 +12,7 @@ export interface IEnvironment {
apiTokenCount?: number;
enabledToggleCount?: number;
lastSeenAt: string;
+ requiredApprovals?: number | null;
}
export interface IProjectEnvironment extends IEnvironment {
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index ac8c727705..4560c28f32 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -92,6 +92,7 @@ export type UiFlags = {
edgeObservability?: boolean;
adminNavUI?: boolean;
tagTypeColor?: boolean;
+ globalChangeRequestConfig?: boolean;
};
export interface IVersionInfo {
diff --git a/src/lib/features/project-environments/environment-store.ts b/src/lib/features/project-environments/environment-store.ts
index 4559fcec7d..28dc2b089e 100644
--- a/src/lib/features/project-environments/environment-store.ts
+++ b/src/lib/features/project-environments/environment-store.ts
@@ -21,7 +21,7 @@ interface IEnvironmentsTable {
sort_order: number;
enabled: boolean;
protected: boolean;
- required_approvals?: number;
+ required_approvals?: number | null;
}
interface IEnvironmentsWithCountsTable extends IEnvironmentsTable {
diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts
index 82207be1a6..625d37422b 100644
--- a/src/lib/types/model.ts
+++ b/src/lib/types/model.ts
@@ -198,7 +198,7 @@ export interface IEnvironment {
projectCount?: number;
apiTokenCount?: number;
enabledToggleCount?: number;
- requiredApprovals?: number;
+ requiredApprovals?: number | null;
}
export interface IProjectEnvironment extends IEnvironment {
@@ -216,7 +216,7 @@ export interface IEnvironmentCreate {
type: string;
sortOrder?: number;
enabled?: boolean;
- requiredApprovals?: number;
+ requiredApprovals?: number | null;
}
export interface IEnvironmentClone {