From d47fdc55acae27b4fdd1ebdd3bfccdc81ef8e525 Mon Sep 17 00:00:00 2001 From: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:38:42 -0400 Subject: [PATCH] feat: [M3-7351] - AGLB Configurations Add Route Drawer and other refinements (#9853) * fix up AGLB configurations * various fixes * add basic e2e test * Added changeset: AGLB Configurations Add Route Drawer and other refinements * improve mock data * clean up and comment * improve the mobile experence * clean up regions * fix create page crash * feedback @mjac0bs * Added changeset: AGLB Configurations Add Route Drawer and other refinements * Added changeset: Add `UpdateConfigurationPayload` * Added changeset: Add `UpdateConfigurationSchema` for AGLB * Delete packages/manager/.changeset/pr-9853-upcoming-features-1698871857708.md * add `id` to the routes table and add `id` to Load Balancer summary * other minor improvements * add certificate `id` * export clean up and deep link configs --------- Co-authored-by: Banks Nussman --- ...pr-9853-upcoming-features-1698871909714.md | 5 + packages/api-v4/src/aglb/configurations.ts | 11 +- packages/api-v4/src/aglb/types.ts | 8 + ...pr-9853-upcoming-features-1698705643963.md | 5 + .../load-balancer-configurations.spec.ts | 38 ++++ packages/manager/src/MainContent.tsx | 6 +- packages/manager/src/factories/aglb.ts | 4 +- .../EntryPointLanding/EntryPointLanding.tsx | 14 -- .../LoadBalancerCreate/LoadBalancerCreate.tsx | 4 +- .../LoadBalancerRegions.tsx | 24 +-- .../Certificates/Certificates.tsx | 9 + .../ApplyCertificatesDrawer.tsx | 4 +- .../Configurations/ConfigurationAccordion.tsx | 124 ++++++++---- .../LoadBalancerConfigurations.tsx | 6 + .../LoadBalancerDetail/LoadBalancerDetail.tsx | 35 ++-- .../LoadBalancerRegions.tsx | 27 +++ .../LoadBalancerDetail/LoadBalancerRoutes.tsx | 71 +++++++ .../LoadBalancerServiceTargets.tsx | 26 ++- .../LoadBalancerSettings.tsx | 3 +- .../LoadBalancerSummary.tsx | 4 + .../Routes/AddRouteDrawer.tsx | 179 ++++++++++++++++++ .../LoadBalancerDetail/Routes/RouteSelect.tsx | 87 +++++++++ .../LoadBalancerDetail/Routes/RoutesTable.tsx | 97 +++------- .../LoadBalancerDetail/Routes/utils.ts | 24 ++- .../LoadBalancerDetail/Settings/Region.tsx | 20 +- .../LoadBalancerLanding.tsx | 4 +- .../src/features/LoadBalancers/index.tsx | 25 ++- packages/manager/src/mocks/serverHandlers.ts | 9 +- .../src/queries/aglb/configurations.ts | 3 +- packages/manager/src/queries/aglb/routes.ts | 26 ++- ...pr-9853-upcoming-features-1698871976111.md | 5 + .../validation/src/loadbalancers.schema.ts | 18 +- 32 files changed, 701 insertions(+), 224 deletions(-) create mode 100644 packages/api-v4/.changeset/pr-9853-upcoming-features-1698871909714.md create mode 100644 packages/manager/.changeset/pr-9853-upcoming-features-1698705643963.md create mode 100644 packages/manager/cypress/e2e/core/loadBalancers/load-balancer-configurations.spec.ts delete mode 100644 packages/manager/src/features/LoadBalancers/EntryPoints/EntryPointLanding/EntryPointLanding.tsx create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerDetail/LoadBalancerRegions.tsx create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerDetail/LoadBalancerRoutes.tsx create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Routes/AddRouteDrawer.tsx create mode 100644 packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Routes/RouteSelect.tsx create mode 100644 packages/validation/.changeset/pr-9853-upcoming-features-1698871976111.md diff --git a/packages/api-v4/.changeset/pr-9853-upcoming-features-1698871909714.md b/packages/api-v4/.changeset/pr-9853-upcoming-features-1698871909714.md new file mode 100644 index 00000000000..41eec7f5620 --- /dev/null +++ b/packages/api-v4/.changeset/pr-9853-upcoming-features-1698871909714.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +Add `UpdateConfigurationPayload` ([#9853](https://github.com/linode/manager/pull/9853)) diff --git a/packages/api-v4/src/aglb/configurations.ts b/packages/api-v4/src/aglb/configurations.ts index dd647471946..e98a31e6967 100644 --- a/packages/api-v4/src/aglb/configurations.ts +++ b/packages/api-v4/src/aglb/configurations.ts @@ -7,7 +7,12 @@ import Request, { } from '../request'; import { Filter, Params, ResourcePage } from '../types'; import { BETA_API_ROOT } from '../constants'; -import type { Configuration, ConfigurationPayload } from './types'; +import type { + Configuration, + ConfigurationPayload, + UpdateConfigurationPayload, +} from './types'; +import { UpdateConfigurationSchema } from '@linode/validation'; /** * getLoadbalancerConfigurations @@ -75,7 +80,7 @@ export const createLoadbalancerConfiguration = ( export const updateLoadbalancerConfiguration = ( loadbalancerId: number, configurationId: number, - data: Partial + data: UpdateConfigurationPayload ) => Request( setURL( @@ -83,7 +88,7 @@ export const updateLoadbalancerConfiguration = ( loadbalancerId )}/configurations/${encodeURIComponent(configurationId)}` ), - setData(data), + setData(data, UpdateConfigurationSchema), setMethod('PUT') ); diff --git a/packages/api-v4/src/aglb/types.ts b/packages/api-v4/src/aglb/types.ts index 71899bbff72..525678e92f0 100644 --- a/packages/api-v4/src/aglb/types.ts +++ b/packages/api-v4/src/aglb/types.ts @@ -111,6 +111,14 @@ export interface Configuration { routes: { id: number; label: string }[]; } +export type UpdateConfigurationPayload = Partial<{ + label: string; + port: number; + protocol: Protocol; + certificates: CertificateConfig[]; + routes: number[]; +}>; + export interface CertificateConfig { hostname: string; id: number; diff --git a/packages/manager/.changeset/pr-9853-upcoming-features-1698705643963.md b/packages/manager/.changeset/pr-9853-upcoming-features-1698705643963.md new file mode 100644 index 00000000000..5d32aeb6ef1 --- /dev/null +++ b/packages/manager/.changeset/pr-9853-upcoming-features-1698705643963.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +AGLB Configurations Add Route Drawer and other refinements ([#9853](https://github.com/linode/manager/pull/9853)) diff --git a/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-configurations.spec.ts b/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-configurations.spec.ts new file mode 100644 index 00000000000..8fc87120c34 --- /dev/null +++ b/packages/manager/cypress/e2e/core/loadBalancers/load-balancer-configurations.spec.ts @@ -0,0 +1,38 @@ +import { + mockAppendFeatureFlags, + mockGetFeatureFlagClientstream, +} from 'support/intercepts/feature-flags'; +import { makeFeatureFlagData } from 'support/util/feature-flags'; +import { loadbalancerFactory, configurationFactory } from '@src/factories'; +import { + mockGetLoadBalancer, + mockGetLoadBalancerConfigurations, +} from 'support/intercepts/load-balancers'; + +describe('Akamai Global Load Balancer configurations page', () => { + it('renders configurations', () => { + const loadbalancer = loadbalancerFactory.build(); + const configurations = configurationFactory.buildList(5); + + mockAppendFeatureFlags({ + aglb: makeFeatureFlagData(true), + }).as('getFeatureFlags'); + mockGetFeatureFlagClientstream().as('getClientStream'); + mockGetLoadBalancer(loadbalancer).as('getLoadBalancer'); + mockGetLoadBalancerConfigurations(loadbalancer.id, configurations).as( + 'getConfigurations' + ); + + cy.visitWithLogin(`/loadbalancers/${loadbalancer.id}/configurations`); + cy.wait([ + '@getFeatureFlags', + '@getClientStream', + '@getLoadBalancer', + '@getConfigurations', + ]); + + for (const configuration of configurations) { + cy.findByText(configuration.label).should('be.visible'); + } + }); +}); diff --git a/packages/manager/src/MainContent.tsx b/packages/manager/src/MainContent.tsx index 56c6a6edcd3..dab1dd692da 100644 --- a/packages/manager/src/MainContent.tsx +++ b/packages/manager/src/MainContent.tsx @@ -134,7 +134,11 @@ const Images = React.lazy(() => import('src/features/Images')); const Kubernetes = React.lazy(() => import('src/features/Kubernetes')); const ObjectStorage = React.lazy(() => import('src/features/ObjectStorage')); const Profile = React.lazy(() => import('src/features/Profile/Profile')); -const LoadBalancers = React.lazy(() => import('src/features/LoadBalancers')); +const LoadBalancers = React.lazy(() => + import('src/features/LoadBalancers').then((module) => ({ + default: module.LoadBalancers, + })) +); const NodeBalancers = React.lazy( () => import('src/features/NodeBalancers/NodeBalancers') ); diff --git a/packages/manager/src/factories/aglb.ts b/packages/manager/src/factories/aglb.ts index eb438a7d04d..ba4022defb5 100644 --- a/packages/manager/src/factories/aglb.ts +++ b/packages/manager/src/factories/aglb.ts @@ -258,7 +258,7 @@ export const createRouteFactory = Factory.Sync.makeFactory({ // ************************* export const serviceTargetFactory = Factory.Sync.makeFactory({ - ca_certificate: 'my-cms-certificate', + ca_certificate: 'certificate-0', endpoints: [ { ip: '192.168.0.100', @@ -276,7 +276,7 @@ export const serviceTargetFactory = Factory.Sync.makeFactory({ unhealthy_threshold: 5, }, id: Factory.each((i) => i), - label: Factory.each((i) => `images-backend-aws-${i}`), + label: Factory.each((i) => `service-target-${i}`), load_balancing_policy: 'round_robin', }); diff --git a/packages/manager/src/features/LoadBalancers/EntryPoints/EntryPointLanding/EntryPointLanding.tsx b/packages/manager/src/features/LoadBalancers/EntryPoints/EntryPointLanding/EntryPointLanding.tsx deleted file mode 100644 index 70dbaac6a6a..00000000000 --- a/packages/manager/src/features/LoadBalancers/EntryPoints/EntryPointLanding/EntryPointLanding.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import * as React from 'react'; - -import { DocumentTitleSegment } from 'src/components/DocumentTitle/DocumentTitle'; - -const EntryPointLanding = () => { - return ( - <> - - TODO: AGLB M3-6811: Service Target Landing - - ); -}; - -export default EntryPointLanding; diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx index 0308f977e0a..02cfbae851f 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerCreate.tsx @@ -18,7 +18,7 @@ const initialValues = { regions: [], }; -const LoadBalancerCreate = () => { +export const LoadBalancerCreate = () => { return ( <> @@ -54,5 +54,3 @@ const LoadBalancerCreate = () => { ); }; - -export default LoadBalancerCreate; diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerRegions.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerRegions.tsx index e5d999d8ed4..d1722ceda96 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerRegions.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerCreate/LoadBalancerRegions.tsx @@ -2,18 +2,10 @@ import Stack from '@mui/material/Stack'; import * as React from 'react'; import { BetaChip } from 'src/components/BetaChip/BetaChip'; -import { Country } from 'src/components/EnhancedSelect/variants/RegionSelect/utils'; -import { Flag } from 'src/components/Flag'; import { Paper } from 'src/components/Paper'; import { Typography } from 'src/components/Typography'; -const loadBalancerRegions = [ - { country: 'us', id: 'us-iad', label: 'Washington, DC' }, - { country: 'us', id: 'us-lax', label: 'Los Angeles, CA' }, - { country: 'fr', id: 'fr-par', label: 'Paris, FR' }, - { country: 'jp', id: 'jp-osa', label: 'Osaka, JP' }, - { country: 'au', id: 'ap-southeast', label: 'Sydney, AU' }, -]; +import { LoadBalancerRegions as Regions } from '../LoadBalancerDetail/LoadBalancerRegions'; export const LoadBalancerRegions = () => { return ( @@ -30,19 +22,7 @@ export const LoadBalancerRegions = () => { incurred. - - {loadBalancerRegions.map((region) => ( - - } /> - {`${region.label} (${region.id})`} - - ))} - + ); diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Certificates/Certificates.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Certificates/Certificates.tsx index f68703a1812..52c7f08907c 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Certificates/Certificates.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Certificates/Certificates.tsx @@ -142,6 +142,14 @@ export const Certificates = () => { > Label + + ID + @@ -151,6 +159,7 @@ export const Certificates = () => { {data?.data.map((certificate) => ( {certificate.label} + {certificate.id} { onClose(); }, validateOnChange: false, - validationSchema: certificateConfigSchema, + validationSchema: CertificateConfigSchema, }); useEffect(() => { diff --git a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/ConfigurationAccordion.tsx b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/ConfigurationAccordion.tsx index b1f24d6a6fe..a3e3b2ae405 100644 --- a/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/ConfigurationAccordion.tsx +++ b/packages/manager/src/features/LoadBalancers/LoadBalancerDetail/Configurations/ConfigurationAccordion.tsx @@ -1,14 +1,13 @@ -import { Stack } from 'src/components/Stack'; import { useFormik } from 'formik'; import React, { useState } from 'react'; import { Accordion } from 'src/components/Accordion'; -import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; +import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Box } from 'src/components/Box'; import { Button } from 'src/components/Button/Button'; import { Divider } from 'src/components/Divider'; -import Select from 'src/components/EnhancedSelect/Select'; import { InputLabel } from 'src/components/InputLabel'; +import { Stack } from 'src/components/Stack'; import { StatusIcon } from 'src/components/StatusIcon/StatusIcon'; import { TextField } from 'src/components/TextField'; import { TooltipIcon } from 'src/components/TooltipIcon'; @@ -17,23 +16,45 @@ import { useLoadBalancerConfigurationMutation } from 'src/queries/aglb/configura import { getErrorMap } from 'src/utilities/errorUtils'; import { pluralize } from 'src/utilities/pluralize'; +import { AddRouteDrawer } from '../Routes/AddRouteDrawer'; import { RoutesTable } from '../Routes/RoutesTable'; import { ApplyCertificatesDrawer } from './ApplyCertificatesDrawer'; import { CertificateTable } from './CertificateTable'; import { DeleteConfigurationDialog } from './DeleteConfigurationDialog'; import type { Configuration } from '@linode/api-v4'; +import { useParams } from 'react-router-dom'; interface Props { configuration: Configuration; loadbalancerId: number; } +function getConfigurationPayloadFromConfiguration( + configuration: Configuration +) { + return { + certificates: configuration.certificates, + label: configuration.label, + port: configuration.port, + protocol: configuration.protocol, + routes: configuration.routes.map((r) => r.id), + }; +} + export const ConfigurationAccordion = (props: Props) => { const { configuration, loadbalancerId } = props; + const { configurationId } = useParams<{ configurationId: string }>(); const [isApplyCertDialogOpen, setIsApplyCertDialogOpen] = useState(false); + const [isAddRouteDrawerOpen, setIsAddRouteDrawerOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [routesTableQuery, setRoutesTableQuery] = useState(''); + + const routesTableFilter = routesTableQuery + ? { label: { '+contains': routesTableQuery } } + : {}; + const { error, isLoading, @@ -42,7 +63,7 @@ export const ConfigurationAccordion = (props: Props) => { const formik = useFormik({ enableReinitialize: true, - initialValues: configuration, + initialValues: getConfigurationPayloadFromConfiguration(configuration), onSubmit(values) { mutateAsync(values); }, @@ -87,7 +108,7 @@ export const ConfigurationAccordion = (props: Props) => { {pluralize('Route', 'Routes', configuration.routes.length)} - {/* @TODO Hook up endpoint status */} + {/* TODO: AGLB - Hook up endpoint status */} Endpoints: @@ -103,6 +124,7 @@ export const ConfigurationAccordion = (props: Props) => { } + defaultExpanded={configuration.id === Number(configurationId)} headingProps={{ sx: { width: '100%' } }} >
@@ -115,44 +137,34 @@ export const ConfigurationAccordion = (props: Props) => { /> -