mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-22 01:16:07 +02:00
feat($env): additional environments - API integration (#8424)
Make API calls from "order environments" dialog, improve validation
This commit is contained in:
parent
01b2a15b8a
commit
1fa918e4f7
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -4,6 +4,10 @@ import { useUiFlag } from 'hooks/useUiFlag';
|
|||||||
import { PurchasableFeature } from './PurchasableFeature/PurchasableFeature';
|
import { PurchasableFeature } from './PurchasableFeature/PurchasableFeature';
|
||||||
import { OrderEnvironmentsDialog } from './OrderEnvironmentsDialog/OrderEnvironmentsDialog';
|
import { OrderEnvironmentsDialog } from './OrderEnvironmentsDialog/OrderEnvironmentsDialog';
|
||||||
import { OrderEnvironmentsConfirmation } from './OrderEnvironmentsConfirmation/OrderEnvironmentsConfirmation';
|
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 = {};
|
type OrderEnvironmentsProps = {};
|
||||||
|
|
||||||
@ -17,18 +21,40 @@ export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
|
|||||||
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
|
const isPurchaseAdditionalEnvironmentsEnabled = useUiFlag(
|
||||||
'purchaseAdditionalEnvironments',
|
'purchaseAdditionalEnvironments',
|
||||||
);
|
);
|
||||||
|
const errors = useFormErrors();
|
||||||
|
const { orderEnvironments } = useOrderEnvironmentApi();
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
if (!isPro() || !isPurchaseAdditionalEnvironmentsEnabled) {
|
if (!isPro() || !isPurchaseAdditionalEnvironmentsEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = (environments: string[]) => {
|
const onSubmit = async (environments: string[]) => {
|
||||||
setPurchaseDialogOpen(false);
|
let hasErrors = false;
|
||||||
// TODO: API call
|
environments.forEach((environment, index) => {
|
||||||
setConfirmationState({
|
const field = `environment-${index}`;
|
||||||
isOpen: true,
|
if (environment.trim() === '') {
|
||||||
environmentsCount: environments.length,
|
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 (
|
return (
|
||||||
@ -42,6 +68,7 @@ export const OrderEnvironments: FC<OrderEnvironmentsProps> = () => {
|
|||||||
open={purchaseDialogOpen}
|
open={purchaseDialogOpen}
|
||||||
onClose={() => setPurchaseDialogOpen(false)}
|
onClose={() => setPurchaseDialogOpen(false)}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<OrderEnvironmentsConfirmation
|
<OrderEnvironmentsConfirmation
|
||||||
open={confirmationState.isOpen}
|
open={confirmationState.isOpen}
|
||||||
|
@ -11,12 +11,14 @@ import FormTemplate from 'component/common/FormTemplate/FormTemplate';
|
|||||||
import { OrderEnvironmentsDialogPricing } from './OrderEnvironmentsDialogPricing/OrderEnvironmentsDialogPricing';
|
import { OrderEnvironmentsDialogPricing } from './OrderEnvironmentsDialogPricing/OrderEnvironmentsDialogPricing';
|
||||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||||
import Input from 'component/common/Input/Input';
|
import Input from 'component/common/Input/Input';
|
||||||
|
import type { IFormErrors } from 'hooks/useFormErrors';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
|
||||||
type OrderEnvironmentsDialogProps = {
|
type OrderEnvironmentsDialogProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (environments: string[]) => void;
|
onSubmit: (environments: string[]) => void;
|
||||||
|
errors?: IFormErrors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledDialog = styled(Dialog)(({ theme }) => ({
|
const StyledDialog = styled(Dialog)(({ theme }) => ({
|
||||||
@ -75,11 +77,14 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
|
|||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
errors,
|
||||||
}) => {
|
}) => {
|
||||||
const { trackEvent } = usePlausibleTracker();
|
const { trackEvent } = usePlausibleTracker();
|
||||||
const [selectedOption, setSelectedOption] = useState(OPTIONS[0]);
|
const [selectedOption, setSelectedOption] = useState(OPTIONS[0]);
|
||||||
const [costCheckboxChecked, setCostCheckboxChecked] = useState(false);
|
const [costCheckboxChecked, setCostCheckboxChecked] = useState(false);
|
||||||
const [environmentNames, setEnvironmentNames] = useState<string[]>([]);
|
const [environmentNames, setEnvironmentNames] = useState<string[]>(['']);
|
||||||
|
|
||||||
|
console.log({ environmentNames });
|
||||||
|
|
||||||
const trackEnvironmentSelect = () => {
|
const trackEnvironmentSelect = () => {
|
||||||
trackEvent('order-environments', {
|
trackEvent('order-environments', {
|
||||||
@ -143,7 +148,10 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
|
|||||||
const value = Number.parseInt(option, 10);
|
const value = Number.parseInt(option, 10);
|
||||||
setSelectedOption(value);
|
setSelectedOption(value);
|
||||||
setEnvironmentNames((names) =>
|
setEnvironmentNames((names) =>
|
||||||
names.slice(0, value),
|
[...names, ...Array(value).fill('')].slice(
|
||||||
|
0,
|
||||||
|
value,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
trackEnvironmentSelect();
|
trackEnvironmentSelect();
|
||||||
}}
|
}}
|
||||||
@ -154,20 +162,28 @@ export const OrderEnvironmentsDialog: FC<OrderEnvironmentsDialogProps> = ({
|
|||||||
How would you like the environment
|
How would you like the environment
|
||||||
{selectedOption > 1 ? 's' : ''} to be named?
|
{selectedOption > 1 ? 's' : ''} to be named?
|
||||||
</Typography>
|
</Typography>
|
||||||
{[...Array(selectedOption)].map((_, i) => (
|
{[...Array(selectedOption)].map((_, i) => {
|
||||||
<Input
|
const error = errors?.getFormError(
|
||||||
key={i}
|
`environment-${i}`,
|
||||||
label={`Environment ${i + 1} name`}
|
);
|
||||||
value={environmentNames[i]}
|
|
||||||
onChange={(event) => {
|
return (
|
||||||
setEnvironmentNames((names) => {
|
<Input
|
||||||
const newValues = [...names];
|
key={i}
|
||||||
newValues[i] = event.target.value;
|
label={`Environment ${i + 1} name`}
|
||||||
return newValues;
|
value={environmentNames[i]}
|
||||||
});
|
onChange={(event) => {
|
||||||
}}
|
setEnvironmentNames((names) => {
|
||||||
/>
|
const newValues = [...names];
|
||||||
))}
|
newValues[i] = event.target.value;
|
||||||
|
return newValues;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
error={Boolean(error)}
|
||||||
|
errorText={error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</StyledEnvironmentNameInputs>
|
</StyledEnvironmentNameInputs>
|
||||||
<Box>
|
<Box>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
@ -862,6 +862,7 @@ export * from './oidcSettingsSchemaOneOfFour';
|
|||||||
export * from './oidcSettingsSchemaOneOfFourDefaultRootRole';
|
export * from './oidcSettingsSchemaOneOfFourDefaultRootRole';
|
||||||
export * from './oidcSettingsSchemaOneOfFourIdTokenSigningAlgorithm';
|
export * from './oidcSettingsSchemaOneOfFourIdTokenSigningAlgorithm';
|
||||||
export * from './oidcSettingsSchemaOneOfIdTokenSigningAlgorithm';
|
export * from './oidcSettingsSchemaOneOfIdTokenSigningAlgorithm';
|
||||||
|
export * from './orderEnvironmentsSchema';
|
||||||
export * from './outdatedSdksSchema';
|
export * from './outdatedSdksSchema';
|
||||||
export * from './outdatedSdksSchemaSdksItem';
|
export * from './outdatedSdksSchemaSdksItem';
|
||||||
export * from './overrideSchema';
|
export * from './overrideSchema';
|
||||||
|
13
frontend/src/openapi/models/orderEnvironmentsSchema.ts
Normal file
13
frontend/src/openapi/models/orderEnvironmentsSchema.ts
Normal file
@ -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[];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user