From acf3f2ea59184324a48d6f8f2b030c2309a906d9 Mon Sep 17 00:00:00 2001 From: cpathipa <119517080+cpathipa@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:17:06 -0600 Subject: [PATCH] feat: [M3-7292] - Stepper component - Details content (#9849) * feat: [M3-7311] - Create Load Balancer flow - Mange state. * Code cleanup * Added changeset: Create Load Balancer flow - Mange state. * Update pr-9848-upcoming-features-1698675578462.md * feat: [M3-7292] - Stepper component - Details content * Added changeset: Stepper component - Details content * Update update-database.spec.ts * Update packages/manager/.changeset/pr-9848-upcoming-features-1698675578462.md Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> * Fix typos * Update LoadBalancerConfiguration.test.tsx * use useFormikContext instead of custom state management hook * PR feedback * Code cleanup * Update loadbalancers.schema.ts * Update packages/validation/src/loadbalancers.schema.ts Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> * Update packages/validation/src/loadbalancers.schema.ts Co-authored-by: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> * Update loadbalancers.schema.ts * schema dependency issue. * Resolve type issues * Fix Formik issues * update tests * Delete pr-9848-upcoming-features-1698675578462.md * PR - feedback use CreateLoadbalancerPayload * PR - feedback error handling * PR - Feedback use full type * PR - feedback * Code cleanup, PR feedback * Code cleanup * Create global test helper to wrap with formik and theme - renderWithThemeAndFormik * code cleanup * Update packages/manager/src/utilities/testHelpers.tsx Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> * Update packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.tsx Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> * PR - feedback * Added latest copy * Update LoadBalancerConfiguration.test.tsx * Add learn more link * Update packages/manager/src/features/LoadBalancers/LoadBalancerCreate/ConfigurationDetails.tsx Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> * Update packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/constants.tsx Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> --------- Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> Co-authored-by: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> --- packages/api-v4/src/aglb/types.ts | 4 +- ...pr-9849-upcoming-features-1698676302215.md | 5 + packages/manager/src/factories/aglb.ts | 5 + .../ConfigurationDetails.tsx | 112 ++++++++++++++++++ .../LoadBalancerActionPanel.tsx | 1 + .../LoadBalancerConfiguration.test.tsx | 50 ++++++-- .../LoadBalancerConfiguration.tsx | 54 +++++---- .../LoadBalancerConfigurations.tsx | 25 ++++ .../LoadBalancerCreate/LoadBalancerCreate.tsx | 26 ++-- .../LoadBalancerLabel.test.tsx | 45 ++++--- .../LoadBalancerCreate/LoadBalancerLabel.tsx | 3 +- .../Configurations/ConfigurationForm.tsx | 7 +- .../Configurations/constants.tsx | 8 ++ .../ServiceTargets/constants.tsx | 1 + .../src/features/LoadBalancers/constants.ts | 3 + .../manager/src/utilities/testHelpers.tsx | 16 +++ .../validation/src/loadbalancers.schema.ts | 33 +++--- 17 files changed, 304 insertions(+), 94 deletions(-) create mode 100644 packages/manager/.changeset/pr-9849-upcoming-features-1698676302215.md create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerCreate/ConfigurationDetails.tsx create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfigurations.tsx diff --git a/packages/api-v4/src/aglb/types.ts b/packages/api-v4/src/aglb/types.ts index aa68e3f8b8a..14a541d0f1c 100644 --- a/packages/api-v4/src/aglb/types.ts +++ b/packages/api-v4/src/aglb/types.ts @@ -31,7 +31,7 @@ export interface UpdateLoadbalancerPayload { configuration_ids?: number[]; } -type Protocol = 'tcp' | 'http' | 'https'; +export type Protocol = 'tcp' | 'http' | 'https'; type RouteProtocol = 'tcp' | 'http'; @@ -46,6 +46,7 @@ export type MatchField = 'path_prefix' | 'query' | 'host' | 'header' | 'method'; export interface RoutePayload { label: string; + protocol: Protocol; rules: RuleCreatePayload[]; } @@ -145,6 +146,7 @@ export interface RouteServiceTargetPayload { export interface ServiceTargetPayload { label: string; protocol: Protocol; + percentage: number; endpoints: Endpoint[]; certificate_id: number | null; load_balancing_policy: Policy; diff --git a/packages/manager/.changeset/pr-9849-upcoming-features-1698676302215.md b/packages/manager/.changeset/pr-9849-upcoming-features-1698676302215.md new file mode 100644 index 00000000000..491c983f73c --- /dev/null +++ b/packages/manager/.changeset/pr-9849-upcoming-features-1698676302215.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Stepper component - Details content ([#9849](https://github.com/linode/manager/pull/9849)) diff --git a/packages/manager/src/factories/aglb.ts b/packages/manager/src/factories/aglb.ts index 8bd8b4d1d9f..ed864bf7372 100644 --- a/packages/manager/src/factories/aglb.ts +++ b/packages/manager/src/factories/aglb.ts @@ -12,6 +12,7 @@ import { UpdateLoadbalancerPayload, } from '@linode/api-v4/lib/aglb/types'; import * as Factory from 'factory.ts'; + import { pickRandom } from 'src/utilities/random'; export const mockCertificate = ` @@ -119,6 +120,7 @@ export const createLoadbalancerWithAllChildrenFactory = Factory.Sync.makeFactory routes: [ { label: 'my-route', + protocol: 'tcp', rules: [ { match_condition: { @@ -149,6 +151,7 @@ export const createLoadbalancerWithAllChildrenFactory = Factory.Sync.makeFactory }, label: 'my-service-target', load_balancing_policy: 'round_robin', + percentage: 0, protocol: 'https', }, ], @@ -279,6 +282,7 @@ export const serviceTargetFactory = Factory.Sync.makeFactory({ id: Factory.each((i) => i), label: Factory.each((i) => `service-target-${i}`), load_balancing_policy: 'round_robin', + percentage: 0, protocol: 'https', }); @@ -303,6 +307,7 @@ export const createServiceTargetFactory = Factory.Sync.makeFactory { + const { + errors, + handleBlur, + handleChange, + setFieldValue, + touched, + values, + } = useFormikContext(); + + return ( + + Details + ({ marginRight: theme.spacing(1) })}> + The port the load balancer listens on, and the protocol for routing + incoming traffic to the targets. + + + + + setFieldValue(`${name}.${index}.protocol`, value) + } + textFieldProps={{ + labelTooltipText: CONFIGURATION_COPY.Protocol, + }} + value={protocolOptions.find( + (option) => option.value === values[name]?.[index]?.protocol + )} + disableClearable + label="Protocol" + options={protocolOptions} + /> + + + + + TLS Certificates + + + + ({ + marginLeft: '0 !important', + marginRight: `${theme.spacing(1 / 2)} !important`, + })} + /> + + After the load balancer is created, and if the protocol is HTTPS, + upload TLS termination certificates. + Learn more. + + + + + + + ); +}; diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerActionPanel.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerActionPanel.tsx index 8a6c43caa6a..8639e6f8f83 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerActionPanel.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerActionPanel.tsx @@ -19,6 +19,7 @@ export const LoadBalancerActionPanel = () => { buttonType="primary" onClick={submitForm} sx={{ marginLeft: 'auto' }} + type="submit" > Review Load Balancer diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.test.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.test.tsx index a7f01c1eed9..8a481f085ca 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.test.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.test.tsx @@ -2,16 +2,37 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { renderWithTheme } from 'src/utilities/testHelpers'; +import { renderWithThemeAndFormik } from 'src/utilities/testHelpers'; import { LoadBalancerConfiguration } from './LoadBalancerConfiguration'; +// Define your initial values based on your form structure +const initialValues = { + configurations: [{ label: '', port: 80, protocol: 'https' }], + label: '', +}; + describe('LoadBalancerConfiguration', () => { test('Should render Details content', () => { - renderWithTheme(); - expect( - screen.getByText('TODO: AGLB - Implement Details step content.') - ).toBeInTheDocument(); + renderWithThemeAndFormik( + , + { initialValues, onSubmit: vi.fn() } + ); + + const ConfigurationInputLabel = screen.getByPlaceholderText( + 'Enter Configuration Label' + ); + const ConfigurationPort = screen.getByPlaceholderText('Enter Port'); + + userEvent.type(ConfigurationInputLabel, 'Test Label'); + // Clear the input field before typing + userEvent.clear(ConfigurationPort); + userEvent.type(ConfigurationPort, '90'); + + expect(ConfigurationInputLabel).toHaveValue('Test Label'); + expect(ConfigurationPort).toHaveValue(90); + + expect(screen.getByText('Protocol')).toBeInTheDocument(); expect( screen.queryByText( 'TODO: AGLB - Implement Service Targets Configuration.' @@ -24,7 +45,10 @@ describe('LoadBalancerConfiguration', () => { expect(screen.queryByText('Previous: Details')).toBeNull(); }); test('Should navigate to Service Targets content', () => { - renderWithTheme(); + renderWithThemeAndFormik( + , + { initialValues, onSubmit: vi.fn() } + ); userEvent.click(screen.getByTestId('service-targets')); expect( screen.getByText('TODO: AGLB - Implement Service Targets Configuration.') @@ -40,7 +64,10 @@ describe('LoadBalancerConfiguration', () => { expect(screen.queryByText('Previous: Service Targets')).toBeNull(); }); test('Should navigate to Routes content', () => { - renderWithTheme(); + renderWithThemeAndFormik( + , + { initialValues, onSubmit: vi.fn() } + ); userEvent.click(screen.getByTestId('service-targets')); userEvent.click(screen.getByTestId('routes')); expect( @@ -57,11 +84,12 @@ describe('LoadBalancerConfiguration', () => { expect(screen.getByText('Previous: Service Targets')).toBeInTheDocument(); }); test('Should be able to go previous step', () => { - renderWithTheme(); + renderWithThemeAndFormik( + , + { initialValues, onSubmit: vi.fn() } + ); userEvent.click(screen.getByTestId('service-targets')); userEvent.click(screen.getByText('Previous: Details')); - expect( - screen.getByText('TODO: AGLB - Implement Details step content.') - ).toBeInTheDocument(); + expect(screen.getByText('Protocol')).toBeInTheDocument(); }); }); diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.tsx index 3d6d3cccd9d..c342fdb8662 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfiguration.tsx @@ -1,41 +1,53 @@ import Stack from '@mui/material/Stack'; +import { useFormikContext } from 'formik'; import * as React from 'react'; import { Paper } from 'src/components/Paper'; import { Typography } from 'src/components/Typography'; import { VerticalLinearStepper } from 'src/components/VerticalLinearStepper/VerticalLinearStepper'; -export const configurationSteps = [ - { - content:
TODO: AGLB - Implement Details step content.
, - handler: () => null, - label: 'Details', - }, - { - content:
TODO: AGLB - Implement Service Targets Configuration.
, - handler: () => null, - label: 'Service Targets', - }, - { - content:
TODO: AGLB - Implement Routes Configuration.
, - handler: () => null, - label: 'Routes', - }, -]; +import { ConfigurationDetails } from './ConfigurationDetails'; + +import type { CreateLoadbalancerPayload } from '@linode/api-v4'; + +interface Props { + index: number; + name: string; +} + +export const LoadBalancerConfiguration = ({ index, name }: Props) => { + const configurationSteps = [ + { + content: , + handler: () => null, + label: 'Details', + }, + { + content:
TODO: AGLB - Implement Service Targets Configuration.
, + handler: () => null, + label: 'Service Targets', + }, + { + content:
TODO: AGLB - Implement Routes Configuration.
, + handler: () => null, + label: 'Routes', + }, + ]; + + const { values } = useFormikContext(); -export const LoadBalancerConfiguration = () => { return ( ({ marginBottom: theme.spacing(2) })} variant="h2" > - Configuration -{' '} + Configuration -{values[name]?.[index]?.label} - A Configuration listens on a port and uses Route Rules to forward - request to Service Target Endpoints + The load balancer configuration for processing incoming requests, the + service targets it directs requests to and routing rules. diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfigurations.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfigurations.tsx new file mode 100644 index 00000000000..522b33b6e2b --- /dev/null +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerConfigurations.tsx @@ -0,0 +1,25 @@ +import { FieldArray, useFormikContext } from 'formik'; +import * as React from 'react'; + +import { LoadBalancerConfiguration } from './LoadBalancerConfiguration'; + +import type { CreateLoadbalancerPayload } from '@linode/api-v4'; + +export const LoadBalancerConfigurations = () => { + const { values } = useFormikContext(); + return ( + + {({ insert, push, remove }) => ( +
+ {values.configurations?.map((configuration, index) => ( + + ))} +
+ )} +
+ ); +}; diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx index 35dfd1a9182..31f872bc648 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx @@ -1,6 +1,6 @@ import { CreateLoadBalancerSchema } from '@linode/validation'; import Stack from '@mui/material/Stack'; -import { Form, Formik } from 'formik'; +import { Formik, Form as FormikForm } from 'formik'; import * as React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle/DocumentTitle'; @@ -8,18 +8,25 @@ import { LandingHeader } from 'src/components/LandingHeader'; import { AGLB_FEEDBACK_FORM_URL } from 'src/features/LoadBalancers/constants'; import { LoadBalancerActionPanel } from './LoadBalancerActionPanel'; -import { LoadBalancerConfiguration } from './LoadBalancerConfiguration'; +import { LoadBalancerConfigurations } from './LoadBalancerConfigurations'; import { LoadBalancerLabel } from './LoadBalancerLabel'; import { LoadBalancerRegions } from './LoadBalancerRegions'; import type { CreateLoadbalancerPayload } from '@linode/api-v4'; -const initialValues = { +const initialValues: CreateLoadbalancerPayload = { + configurations: [ + { certificates: [], label: '', port: 443, protocol: 'https' }, + ], label: '', regions: [], }; export const LoadBalancerCreate = () => { + const handleSubmit = (values: CreateLoadbalancerPayload) => { + // console.log('Submitted values:', values); + }; + return ( <> @@ -36,22 +43,19 @@ export const LoadBalancerCreate = () => { betaFeedbackLink={AGLB_FEEDBACK_FORM_URL} title="Create" /> - - onSubmit={(values, actions) => { - // TODO: AGLB - Implement form submit - // console.log('Values ', values); - }} + -
+ - + - +
); diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.test.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.test.tsx index 1f21fccc1c8..7be2ff1fe73 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.test.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.test.tsx @@ -1,8 +1,7 @@ import { fireEvent } from '@testing-library/react'; -import { Formik } from 'formik'; import React from 'react'; -import { renderWithTheme } from 'src/utilities/testHelpers'; +import { renderWithThemeAndFormik } from 'src/utilities/testHelpers'; import { LoadBalancerLabel } from './LoadBalancerLabel'; @@ -11,29 +10,20 @@ const loadBalancerTestId = 'textfield-input'; import type { CreateLoadbalancerPayload } from '@linode/api-v4'; -type MockFormikContext = { - initialErrors?: {}; - initialTouched?: {}; - initialValues: CreateLoadbalancerPayload; -}; - -const initialValues = { +const initialValues: CreateLoadbalancerPayload = { label: loadBalancerLabelValue, regions: [], }; -const renderWithFormikWrapper = (mockFormikContext: MockFormikContext) => - renderWithTheme( - - - - ); - describe('LoadBalancerLabel', () => { it('should render the component with a label and no error', () => { - const { getByTestId, queryByText } = renderWithFormikWrapper({ - initialValues, - }); + const { getByTestId, queryByText } = renderWithThemeAndFormik( + , + { + initialValues, + onSubmit: vi.fn(), + } + ); const labelInput = getByTestId(loadBalancerTestId); const errorNotice = queryByText('Error Text'); @@ -45,11 +35,15 @@ describe('LoadBalancerLabel', () => { }); it('should render the component with an error message', () => { - const { getByTestId, getByText } = renderWithFormikWrapper({ - initialErrors: { label: 'This is an error' }, - initialTouched: { label: true }, - initialValues, - }); + const { getByTestId, getByText } = renderWithThemeAndFormik( + , + { + initialErrors: { label: 'This is an error' }, + initialTouched: { label: true }, + initialValues, + onSubmit: vi.fn(), + } + ); const labelInput = getByTestId(loadBalancerTestId); const errorNotice = getByText('This is an error'); @@ -59,8 +53,9 @@ describe('LoadBalancerLabel', () => { }); it('should update formik values on input change', () => { - const { getByTestId } = renderWithFormikWrapper({ + const { getByTestId } = renderWithThemeAndFormik(, { initialValues, + onSubmit: vi.fn(), }); const labelInput = getByTestId(loadBalancerTestId); diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.tsx index f385010e44d..6e3122b26a8 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerLabel.tsx @@ -9,6 +9,7 @@ import type { CreateLoadbalancerPayload } from '@linode/api-v4'; export const LoadBalancerLabel = () => { const { errors, + handleBlur, handleChange, touched, values, @@ -23,12 +24,12 @@ export const LoadBalancerLabel = () => { data-qa-label-header > { validationSchema, }); - const protocolOptions = [ - { label: 'HTTPS', value: 'https' }, - { label: 'HTTP', value: 'http' }, - { label: 'TCP', value: 'tcp' }, - ]; - const handleRemoveCert = (index: number) => { formik.values.certificates.splice(index, 1); formik.setFieldValue('certificates', formik.values.certificates); diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/constants.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/constants.tsx index a58c2216932..da980e603ef 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/constants.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/constants.tsx @@ -27,6 +27,12 @@ export const initialValues = { route_ids: [], }; +export const protocolOptions = [ + { label: 'HTTPS', value: 'https' }, + { label: 'HTTP', value: 'http' }, + { label: 'TCP', value: 'tcp' }, +]; + export const CONFIGURATION_COPY = { Certificates: 'TLS termination certificates create an encrypted link between your clients and Global Load Balancer, and terminate incoming traffic on the load balancer. Once the load balancing policy is applied, traffic is forwarded to your service targets over encrypted TLS connections. Responses from your service targets to your clients are also encrypted.', @@ -38,4 +44,6 @@ export const CONFIGURATION_COPY = { Available Protocols for information. ), + configuration: + 'If a label is not entered, a default value based on protocol or port number is assigned.', }; diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/constants.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/constants.tsx index 945a475e594..e8e0b5d6590 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/constants.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/ServiceTargets/constants.tsx @@ -57,6 +57,7 @@ export const initialValues: ServiceTargetPayload = { }, label: '', load_balancing_policy: 'round_robin', + percentage: 10, protocol: 'https', }; diff --git a/packages/manager/src/features/LoadBalancers/constants.ts b/packages/manager/src/features/LoadBalancers/constants.ts index 086526acd93..f5fa62d1ad4 100644 --- a/packages/manager/src/features/LoadBalancers/constants.ts +++ b/packages/manager/src/features/LoadBalancers/constants.ts @@ -8,6 +8,9 @@ export const AGLB_FEEDBACK_FORM_URL = export const AGLB_DOCS_URL = 'https://deploy-preview-14--roaring-gelato-12dc9e.netlify.app'; +export const AGLB_DOCS_TLS_CERTIFICATE = + 'https://deploy-preview-14--roaring-gelato-12dc9e.netlify.app/docs/products/networking/global-loadbalancer/guides/certificates/'; + export const AGLB_DOCS = { Developers: `${AGLB_DOCS_URL}/docs/products/networking/global-loadbalancer/developers`, GettingStarted: `${AGLB_DOCS_URL}/docs/products/networking/global-loadbalancer/get-started`, diff --git a/packages/manager/src/utilities/testHelpers.tsx b/packages/manager/src/utilities/testHelpers.tsx index 88216873e89..05aba12a843 100644 --- a/packages/manager/src/utilities/testHelpers.tsx +++ b/packages/manager/src/utilities/testHelpers.tsx @@ -6,6 +6,7 @@ import { } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import mediaQuery from 'css-mediaquery'; +import { Formik, FormikConfig, FormikValues } from 'formik'; import { Provider as LDProvider } from 'launchdarkly-react-client-sdk/lib/context'; import { SnackbarProvider } from 'notistack'; import { mergeDeepRight } from 'ramda'; @@ -152,6 +153,21 @@ export const renderWithTheme = (ui: any, options: Options = {}) => { return render(wrapWithTheme(ui, options)); }; +/** + * Renders the given UI component within both the Formik and renderWithTheme. + * + * @param {React.ReactElement} ui - The React component that you want to render. This component + * typically will be a part of or a whole form. + * @param {FormikConfig} configObj - Formik configuration object which includes all the necessary + * configurations for the Formik context such as initialValues, + * validationSchema, and onSubmit. + */ + +export const renderWithThemeAndFormik = ( + ui: React.ReactElement, + configObj: FormikConfig +) => renderWithTheme({ui}); + declare global { // export would be better, but i m aligning with how the namespace is declared by vi-axe // eslint-disable-next-line @typescript-eslint/no-namespace diff --git a/packages/validation/src/loadbalancers.schema.ts b/packages/validation/src/loadbalancers.schema.ts index 0e155aa2faf..af4f3d80127 100644 --- a/packages/validation/src/loadbalancers.schema.ts +++ b/packages/validation/src/loadbalancers.schema.ts @@ -236,44 +236,41 @@ const CreateLoadBalancerRuleSchema = object({ export const ConfigurationSchema = object({ label: string().required(LABEL_REQUIRED), - port: number().required('Port is required.').min(1).max(65535), + port: number() + .min(1, 'Port must be greater than 0.') + .max(65535, 'Port must be less than or equal to 65535.') + .typeError('Port must be a number.') + .required('Port is required.'), protocol: string().oneOf(['tcp', 'http', 'https']).required(), certificates: array().when('protocol', { is: (val: string) => val === 'https', then: (o) => o.of(CertificateEntrySchema).required(), otherwise: (o) => o.notRequired(), }), - routes: string().when('protocol', { + routes: array().when('protocol', { is: 'tcp', - then: array() - .of( + then: (o) => + o.of( object({ label: string().required(), protocol: string().oneOf(['tcp']).required(), rules: array().of(CreateLoadBalancerRuleSchema).required(), }) - ) - .required(), - otherwise: array() - .of( - object().shape({ + ), + otherwise: (o) => + o.of( + object({ label: string().required(), protocol: string().oneOf(['http']).required(), rules: array().of(CreateLoadBalancerRuleSchema).required(), }) - ) - .required(), + ), }), }); export const CreateLoadBalancerSchema = object({ - label: string() - .matches( - /^[a-zA-Z0-9.\-_]+$/, - 'Label may only contain letters, numbers, periods, dashes, and underscores.' - ) - .required(LABEL_REQUIRED), - tags: array().of(string()), // TODO: AGLB - Should confirm on this with API team. Assuming this will be out of scope for Beta. + label: string().min(1, 'Label must not be empty.').required(LABEL_REQUIRED), + // tags: array().of(string()), // TODO: AGLB - Should confirm on this with API team. Assuming this will be out of scope for Beta. regions: array().of(string()).required(), configurations: array().of(ConfigurationSchema), });