Skip to content

Commit

Permalink
feat($env): additional environments - API integration (#8424)
Browse files Browse the repository at this point in the history
Make API calls from "order environments" dialog, improve validation
  • Loading branch information
Tymek authored Oct 11, 2024
1 parent 01b2a15 commit 1fa918e
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { OrderEnvironments } from './OrderEnvironments';
import { testServerRoute, testServerSetup } from 'utils/testServer';

const server = testServerSetup();

const setupServerRoutes = (changeRequestsEnabled = true) => {
testServerRoute(server, '/api/admin/ui-config', {
environment: 'Pro',
flags: {
purchaseAdditionalEnvironments: true,
},
});
};

describe('OrderEnvironmentsDialog Component', () => {
test('should show error if environment name is empty', async () => {
setupServerRoutes();
render(<OrderEnvironments />);

await waitFor(async () => {
const openDialogButton = await screen.queryByRole('button', {
name: /view pricing/i,
});
expect(openDialogButton).toBeInTheDocument();
fireEvent.click(openDialogButton!);
});

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

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

expect(
screen.getByText(/environment name is required/i),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { useUiFlag } from 'hooks/useUiFlag';
import { PurchasableFeature } from './PurchasableFeature/PurchasableFeature';
import { OrderEnvironmentsDialog } from './OrderEnvironmentsDialog/OrderEnvironmentsDialog';
import { OrderEnvironmentsConfirmation } from './OrderEnvironmentsConfirmation/OrderEnvironmentsConfirmation';
import { useFormErrors } from 'hooks/useFormErrors';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useOrderEnvironmentApi } from 'hooks/api/actions/useOrderEnvironmentsApi/useOrderEnvironmentsApi';

type OrderEnvironmentsProps = {};

Expand All @@ -17,18 +21,40 @@ export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
'purchaseAdditionalEnvironments',
);
const errors = useFormErrors();
const { orderEnvironments } = useOrderEnvironmentApi();
const { setToastData, setToastApiError } = useToast();

if (!isPro() || !isPurchaseAdditionalEnvironmentsEnabled) {
return null;
}

const onSubmit = (environments: string[]) => {
setPurchaseDialogOpen(false);
// TODO: API call
setConfirmationState({
isOpen: true,
environmentsCount: environments.length,
const onSubmit = async (environments: string[]) => {
let hasErrors = false;
environments.forEach((environment, index) => {
const field = `environment-${index}`;
if (environment.trim() === '') {
errors.setFormError(field, 'Environment name is required');
hasErrors = true;
} else {
errors.removeFormError(field);
}
});

if (hasErrors) {
return;
} else {
try {
await orderEnvironments({ environments });
setPurchaseDialogOpen(false);
setConfirmationState({
isOpen: true,
environmentsCount: environments.length,
});
} catch (error) {
setToastApiError(formatUnknownError(error));
}
}
};

return (
Expand All @@ -42,6 +68,7 @@ export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
open={purchaseDialogOpen}
onClose={() => setPurchaseDialogOpen(false)}
onSubmit={onSubmit}
errors={errors}
/>
<OrderEnvironmentsConfirmation
open={confirmationState.isOpen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ 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';

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

const StyledDialog = styled(Dialog)(({ theme }) => ({
Expand Down Expand Up @@ -75,11 +77,14 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
open,
onClose,
onSubmit,
errors,
}) => {
const { trackEvent } = usePlausibleTracker();
const [selectedOption, setSelectedOption] = useState(OPTIONS[0]);
const [costCheckboxChecked, setCostCheckboxChecked] = useState(false);
const [environmentNames, setEnvironmentNames] = useState<string[]>([]);
const [environmentNames, setEnvironmentNames] = useState<string[]>(['']);

console.log({ environmentNames });

const trackEnvironmentSelect = () => {
trackEvent('order-environments', {
Expand Down Expand Up @@ -143,7 +148,10 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
const value = Number.parseInt(option, 10);
setSelectedOption(value);
setEnvironmentNames((names) =>
names.slice(0, value),
[...names, ...Array(value).fill('')].slice(
0,
value,
),
);
trackEnvironmentSelect();
}}
Expand All @@ -154,20 +162,28 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
How would you like the environment
{selectedOption > 1 ? 's' : ''} to be named?
</Typography>
{[...Array(selectedOption)].map((_, i) => (
<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;
});
}}
/>
))}
{[...Array(selectedOption)].map((_, i) => {
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}
/>
);
})}
</StyledEnvironmentNameInputs>
<Box>
<StyledCheckbox
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import useAPI from '../useApi/useApi';
import type { OrderEnvironmentsSchema } from 'openapi';

export const useOrderEnvironmentApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({
propagateErrors: true,
});

const orderEnvironments = async (payload: OrderEnvironmentsSchema) => {
const req = createRequest('api/admin/order-environments', {
method: 'POST',
body: JSON.stringify(payload),
});

const res = await makeRequest(req.caller, req.id);
return res.json();
};

return {
orderEnvironments,
errors,
loading,
};
};
1 change: 1 addition & 0 deletions frontend/src/openapi/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,7 @@ export * from './oidcSettingsSchemaOneOfFour';
export * from './oidcSettingsSchemaOneOfFourDefaultRootRole';
export * from './oidcSettingsSchemaOneOfFourIdTokenSigningAlgorithm';
export * from './oidcSettingsSchemaOneOfIdTokenSigningAlgorithm';
export * from './orderEnvironmentsSchema';
export * from './outdatedSdksSchema';
export * from './outdatedSdksSchemaSdksItem';
export * from './overrideSchema';
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/openapi/models/orderEnvironmentsSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

/**
* A request for hosted customers to order new environments in Unleash.
*/
export interface OrderEnvironmentsSchema {
/** An array of environment names to be ordered. */
environments: string[];
}

0 comments on commit 1fa918e

Please sign in to comment.