Skip to content

Commit

Permalink
Add environment types environment order (#8447)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tymek authored Oct 15, 2024
1 parent 4167d77 commit f5a2a18
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 44 deletions.
17 changes: 11 additions & 6 deletions frontend/src/component/common/GeneralSelect/GeneralSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function GeneralSelect<T extends string = string>({
classes,
fullWidth,
visuallyHideLabel,
labelId,
...rest
}: IGeneralSelectProps<T>) {
const onSelectChange = (event: SelectChangeEvent) => {
Expand All @@ -65,12 +66,15 @@ function GeneralSelect<T extends string = string>({
classes={classes}
fullWidth={fullWidth}
>
<InputLabel
sx={visuallyHideLabel ? visuallyHidden : null}
htmlFor={id}
>
{label}
</InputLabel>
{label ? (
<InputLabel
sx={visuallyHideLabel ? visuallyHidden : null}
htmlFor={id}
id={labelId}
>
{label}
</InputLabel>
) : null}
<Select
name={name}
disabled={disabled}
Expand All @@ -87,6 +91,7 @@ function GeneralSelect<T extends string = string>({
},
}}
IconComponent={KeyboardArrowDownOutlined}
labelId={labelId}
{...rest}
>
{options.map((option) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useFormErrors } from 'hooks/useFormErrors';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useOrderEnvironmentApi } from 'hooks/api/actions/useOrderEnvironmentsApi/useOrderEnvironmentsApi';
import type { OrderEnvironmentsSchema } from 'openapi';

type OrderEnvironmentsProps = {};

Expand All @@ -29,11 +30,14 @@ export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
return null;
}

const onSubmit = async (environments: string[]) => {
const onSubmit = async (
environments: OrderEnvironmentsSchema['environments'],
) => {
let hasErrors = false;
environments.forEach((environment, index) => {
const field = `environment-${index}`;
if (environment.trim() === '') {
const environmentName = environment.name.trim();
if (environmentName === '') {
errors.setFormError(field, 'Environment name is required');
hasErrors = true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ describe('OrderEnvironmentsDialog Component', () => {
screen.getAllByLabelText(/environment \d+ name/i);
expect(environmentInputs).toHaveLength(1);

const selectButton = screen.getByRole('combobox');
const selectButton = screen.getByRole('combobox', {
name: /select the number of additional environments/i,
});
fireEvent.mouseDown(selectButton);

const option2 = screen.getByRole('option', { name: '2 environments' });
Expand Down Expand Up @@ -75,7 +77,9 @@ describe('OrderEnvironmentsDialog Component', () => {
const onSubmitMock = vi.fn();
renderComponent({ onSubmit: onSubmitMock });

const selectButton = screen.getByRole('combobox');
const selectButton = screen.getByRole('combobox', {
name: /select the number of additional environments/i,
});
fireEvent.mouseDown(selectButton);

const option2 = screen.getByRole('option', { name: '2 environments' });
Expand All @@ -97,7 +101,10 @@ describe('OrderEnvironmentsDialog Component', () => {
fireEvent.click(submitButton);

expect(onSubmitMock).toHaveBeenCalledTimes(1);
expect(onSubmitMock).toHaveBeenCalledWith(['Dev', 'Staging']);
expect(onSubmitMock).toHaveBeenCalledWith([
{ name: 'Dev', type: 'development' },
{ name: 'Staging', type: 'development' },
]);
});

test('should call onClose when "Cancel" button is clicked', () => {
Expand All @@ -114,7 +121,9 @@ describe('OrderEnvironmentsDialog Component', () => {
const onSubmitMock = vi.fn();
renderComponent({ onSubmit: onSubmitMock });

const selectButton = screen.getByRole('combobox');
const selectButton = screen.getByRole('combobox', {
name: /select the number of additional environments/i,
});
fireEvent.mouseDown(selectButton);

const option3 = screen.getByRole('option', { name: '3 environments' });
Expand Down Expand Up @@ -146,6 +155,58 @@ describe('OrderEnvironmentsDialog Component', () => {
fireEvent.click(submitButton);

expect(onSubmitMock).toHaveBeenCalledTimes(1);
expect(onSubmitMock).toHaveBeenCalledWith(['Dev', 'Staging']);
expect(onSubmitMock).toHaveBeenCalledWith([
{ name: 'Dev', type: 'development' },
{ name: 'Prod', type: 'development' },
]);
});

test('should allow for changing environment types', () => {
const onSubmitMock = vi.fn();
renderComponent({ onSubmit: onSubmitMock });

const selectButton = screen.getByRole('combobox', {
name: /select the number of additional environments/i,
});
fireEvent.mouseDown(selectButton);
const option3 = screen.getByRole('option', { name: '2 environments' });
fireEvent.click(option3);

const checkbox = screen.getByRole('checkbox', {
name: /i understand adding environments leads to extra costs/i,
});
fireEvent.click(checkbox);

const environmentInputs =
screen.getAllByLabelText(/environment \d+ name/i);
fireEvent.change(environmentInputs[0], { target: { value: 'Test' } });
fireEvent.change(environmentInputs[1], {
target: { value: 'Staging' },
});

const typeSelects = screen.getAllByRole('combobox', {
name: /type of environment/i,
});

fireEvent.mouseDown(typeSelects[0]);
const optionTesting = screen.getByRole('option', {
name: /testing/i,
});
fireEvent.click(optionTesting);

fireEvent.mouseDown(typeSelects[1]);
const optionProduction = screen.getByRole('option', {
name: /pre\-production/i,
});
fireEvent.click(optionProduction);

const submitButton = screen.getByRole('button', { name: /order/i });
fireEvent.click(submitButton);

expect(onSubmitMock).toHaveBeenCalledTimes(1);
expect(onSubmitMock).toHaveBeenCalledWith([
{ name: 'Test', type: 'testing' },
{ name: 'Staging', type: 'pre-production' },
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import {
Dialog,
styled,
Typography,
TextField,
} from '@mui/material';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { OrderEnvironmentsDialogPricing } from './OrderEnvironmentsDialogPricing/OrderEnvironmentsDialogPricing';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import Input from 'component/common/Input/Input';
import type { IFormErrors } from 'hooks/useFormErrors';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import type { OrderEnvironmentsSchemaEnvironmentsItem } from 'openapi';

type OrderEnvironmentsDialogProps = {
open: boolean;
onClose: () => void;
onSubmit: (environments: string[]) => void;
onSubmit: (environments: OrderEnvironmentsSchemaEnvironmentsItem[]) => void;
errors?: IFormErrors;
};

Expand Down Expand Up @@ -50,6 +51,16 @@ const StyledGeneralSelect = styled(GeneralSelect)(({ theme }) => ({
margin: theme.spacing(1, 0),
}));

const StyledTypeSelect = styled(GeneralSelect)(({ theme }) => ({
minWidth: '166px',
}));

const StyledEnvironmentInputs = styled(Box)(({ theme }) => ({
display: 'flex',
gap: theme.spacing(2),
marginBottom: theme.spacing(2),
}));

const StyledFields = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
Expand All @@ -72,6 +83,12 @@ const StyledCheckbox = styled(Checkbox)(({ theme }) => ({

const PRICE = 10;
const OPTIONS = [1, 2, 3];
const ENVIRONMENT_TYPES = [
'development',
'testing',
'pre-production',
'production',
];

export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
open,
Expand All @@ -82,7 +99,9 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
const { trackEvent } = usePlausibleTracker();
const [selectedOption, setSelectedOption] = useState(OPTIONS[0]);
const [costCheckboxChecked, setCostCheckboxChecked] = useState(false);
const [environmentNames, setEnvironmentNames] = useState<string[]>(['']);
const [environments, setEnvironments] = useState<
{ name: string; type: string }[]
>([{ name: '', type: ENVIRONMENT_TYPES[0] }]);

const trackEnvironmentSelect = () => {
trackEvent('order-environments', {
Expand Down Expand Up @@ -110,7 +129,7 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
<Button
variant='contained'
disabled={!costCheckboxChecked}
onClick={() => onSubmit(environmentNames)}
onClick={() => onSubmit(environments)}
>
Order
</Button>
Expand All @@ -129,14 +148,11 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
</Typography>
<StyledFields>
<Box>
<Typography
component='label'
htmlFor='numberOfEnvironments'
>
<Typography component='label' id='numberOfEnvironments'>
Select the number of additional environments
</Typography>
<StyledGeneralSelect
id='numberOfEnvironments'
labelId='numberOfEnvironments'
value={`${selectedOption}`}
options={OPTIONS.map((option) => ({
key: `${option}`,
Expand All @@ -145,11 +161,14 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
onChange={(option) => {
const value = Number.parseInt(option, 10);
setSelectedOption(value);
setEnvironmentNames((names) =>
[...names, ...Array(value).fill('')].slice(
0,
value,
),
setEnvironments((envs) =>
[
...envs,
...Array(value).fill({
name: '',
type: ENVIRONMENT_TYPES[0],
}),
].slice(0, value),
);
trackEnvironmentSelect();
}}
Expand All @@ -164,22 +183,45 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
const error = errors?.getFormError(
`environment-${i}`,
);

return (
<Input
key={i}
label={`Environment ${i + 1} name`}
value={environmentNames[i]}
onChange={(event) => {
setEnvironmentNames((names) => {
const newValues = [...names];
newValues[i] = event.target.value;
return newValues;
});
}}
error={Boolean(error)}
errorText={error}
/>
<StyledEnvironmentInputs key={i}>
<StyledTypeSelect
label='Type of environment'
labelId={`environmentType${i}`}
value={
environments[i]?.type ||
ENVIRONMENT_TYPES[0]
}
options={ENVIRONMENT_TYPES.map(
(type) => ({
key: type,
label: type,
}),
)}
onChange={(type) => {
const newEnvironments = [
...environments,
];
newEnvironments[i].type = type;
setEnvironments(newEnvironments);
}}
/>
<TextField
size='small'
label={`Environment ${i + 1} Name`}
value={environments[i]?.name || ''}
onChange={(e) => {
const newEnvironments = [
...environments,
];
newEnvironments[i].name =
e.target.value;
setEnvironments(newEnvironments);
}}
error={!!error}
helperText={error}
/>
</StyledEnvironmentInputs>
);
})}
</StyledEnvironmentNameInputs>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/openapi/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ export * from './oidcSettingsSchemaOneOfFourDefaultRootRole';
export * from './oidcSettingsSchemaOneOfFourIdTokenSigningAlgorithm';
export * from './oidcSettingsSchemaOneOfIdTokenSigningAlgorithm';
export * from './orderEnvironmentsSchema';
export * from './orderEnvironmentsSchemaEnvironmentsItem';
export * from './outdatedSdksSchema';
export * from './outdatedSdksSchemaSdksItem';
export * from './overrideSchema';
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/openapi/models/orderEnvironmentsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* Do not edit manually.
* See `gen:api` script in package.json
*/
import type { OrderEnvironmentsSchemaEnvironmentsItem } from './orderEnvironmentsSchemaEnvironmentsItem';

/**
* A request for hosted customers to order new environments in Unleash.
*/
export interface OrderEnvironmentsSchema {
/** An array of environment names to be ordered. */
environments: string[];
/** An array of environments to be ordered, each with a name and type. */
environments: OrderEnvironmentsSchemaEnvironmentsItem[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

export type OrderEnvironmentsSchemaEnvironmentsItem = {
/** The name of the environment. */
name: string;
/** The type of the environment. */
type: string;
};

0 comments on commit f5a2a18

Please sign in to comment.