From 24dc84427e74cde625b9af753e33de4e49b7a625 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 12 Dec 2024 12:00:04 -0600 Subject: [PATCH] clean up gateway routes fetch logic by extracting shared hook --- .../vpcs/VpcPage/tabs/VpcGatewaysTab.tsx | 51 +++----- .../project/vpcs/internet-gateway-edit.tsx | 113 ++++++++++-------- 2 files changed, 75 insertions(+), 89 deletions(-) diff --git a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx index 3bc72c23d..64582fa6b 100644 --- a/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx +++ b/app/pages/project/vpcs/VpcPage/tabs/VpcGatewaysTab.tsx @@ -24,6 +24,8 @@ import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' +import { useGatewayRoutes } from '../../internet-gateway-edit' + const gatewayList = ({ project, vpc }: PP.Vpc) => getListQFn('internetGatewayList', { query: { project, vpc, limit: ALL_ISH } }) const routerList = ({ project, vpc }: PP.Vpc) => @@ -48,41 +50,20 @@ const InternetGatewayIpPoolCell = ({ gatewayId }: { gatewayId: string }) => { return } -// called by InternetGatewayAttachedRoutesCell to get the routes per router -// we need to have this in its own function because useQuery cannot be called inside a loop -const InternetGatewayRoutes = ({ - project, - vpc, - gateway, - router, -}: PP.VpcInternetGateway & { router: string }) => { - const { data: routes } = useQuery(routeList({ project, vpc, router }).optionsFn()) - if (!routes || routes.items.length < 1) return null - return routes.items - .filter((r) => r.target.type === 'internet_gateway' && r.target.value === gateway) - .map((route) => ( - +const GatewayRoutes = ({ project, vpc, gateway }: PP.VpcInternetGateway) => { + const matchingRoutes = useGatewayRoutes({ project, vpc, gateway }) + + if (!matchingRoutes?.length) return + + return matchingRoutes.map(([router, route]) => { + const to = pb.vpcRouterRouteEdit({ project, vpc, router, route: route.name }) + const key = `${router}-${route.name}` + return ( + {route.name} - )) -} - -const InternetGatewayAttachedRoutesCell = ({ - project, - vpc, - gateway, -}: PP.VpcInternetGateway) => { - const { data: routers } = useQuery(routerList({ project, vpc }).optionsFn()) - const matchingRoutes = routers?.items.map((router) => { - const props = { project, vpc, gateway, router: router.name } - return + ) }) - if (!matchingRoutes?.length) return - return
{matchingRoutes}
} const colHelper = createColumnHelper() @@ -152,11 +133,7 @@ export function VpcInternetGatewaysTab() { id: 'routes', header: 'Routes', cell: (info) => ( - + ), }), colHelper.accessor('timeCreated', Columns.timeCreated), diff --git a/app/pages/project/vpcs/internet-gateway-edit.tsx b/app/pages/project/vpcs/internet-gateway-edit.tsx index 1f7369ae9..52549a4fb 100644 --- a/app/pages/project/vpcs/internet-gateway-edit.tsx +++ b/app/pages/project/vpcs/internet-gateway-edit.tsx @@ -6,13 +6,20 @@ * Copyright Oxide Computer Company */ -import { useQuery } from '@tanstack/react-query' +import { useQueries, useQuery } from '@tanstack/react-query' import { useForm } from 'react-hook-form' import { Link, useNavigate, type LoaderFunctionArgs } from 'react-router-dom' +import * as R from 'remeda' import { Gateway16Icon } from '@oxide/design-system/icons/react' -import { apiQueryClient, getListQFn, queryClient, usePrefetchedApiQuery } from '~/api' +import { + apiQueryClient, + getListQFn, + queryClient, + usePrefetchedApiQuery, + usePrefetchedQuery, +} from '~/api' import { SideModalForm } from '~/components/form/SideModalForm' import { getInternetGatewaySelector, useInternetGatewaySelector } from '~/hooks/use-params' import { DescriptionCell } from '~/table/cells/DescriptionCell' @@ -28,54 +35,55 @@ import { links } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' -const RouterRow = ({ - project, - vpc, - gateway, - router, -}: PP.VpcInternetGateway & { router: string }) => { - const matchingRoutes: JSX.Element[] = [] - const { data: routes } = useQuery(routeList({ project, vpc, router }).optionsFn()) - if (!routes || routes.items.length < 1) return - routes.items.forEach((route) => { - if (route.target.type === 'internet_gateway' && route.target.value === gateway) { - matchingRoutes.push( - - {router} - - - {route.name} - - - - ) - } +const RoutesEmpty = () => ( + + + No VPC router routes target this gateway. + + +) + +/** + * For a given gateway, return a list of [router name, RouterRoute] pairs + */ +export function useGatewayRoutes({ project, vpc, gateway }: PP.VpcInternetGateway) { + const { data: routers } = usePrefetchedQuery(routerList({ project, vpc }).optionsFn()) + const routerNames = routers.items.map((r) => r.name) + + const routesQueries = useQueries({ + queries: routerNames.map((router) => routeList({ project, vpc, router }).optionsFn()), }) - return matchingRoutes -} + const loadedRoutesLists = routesQueries.filter((q) => !!q.data).map((q) => q.data.items) + + // loading. should never happen because of prefetches + if (loadedRoutesLists.length < routers.items.length) return null -const RouterRows = ({ project, vpc, gateway }: PP.VpcInternetGateway) => { - const { data: routers } = useQuery(routerList({ project, vpc }).optionsFn()) - const matchingRoutes = routers?.items.flatMap((router) => - RouterRow({ project, vpc, gateway, router: router.name }) + return R.pipe( + R.zip(routerNames, loadedRoutesLists), + R.flatMap(([router, routes]) => routes.map((route) => [router, route] as const)), + R.filter(([_, r]) => r.target.type === 'internet_gateway' && r.target.value === gateway) ) - return matchingRoutes?.length ? ( - matchingRoutes - ) : ( - - - No VPC routes target this gateway. +} + +function RouteRows({ project, vpc, gateway }: PP.VpcInternetGateway) { + const matchingRoutes = useGatewayRoutes({ project, vpc, gateway }) + + if (!matchingRoutes) return null + if (matchingRoutes.length === 0) return + + return matchingRoutes.map(([router, route]) => ( + + {router} + + + {route.name} + - ) + )) } const gatewayIpPoolList = ({ project, vpc, gateway }: PP.VpcInternetGateway) => @@ -100,14 +108,13 @@ EditInternetGatewayForm.loader = async function ({ params }: LoaderFunctionArgs) }), queryClient.prefetchQuery(gatewayIpPoolList({ project, vpc, gateway }).optionsFn()), queryClient.prefetchQuery(gatewayIpAddressList({ project, vpc, gateway }).optionsFn()), - (await queryClient.fetchQuery(routerList({ project, vpc }).optionsFn())).items.map( - (router) => { + ...(await queryClient.fetchQuery(routerList({ project, vpc }).optionsFn())).items.map( + (router) => queryClient.prefetchQuery( routeList({ project, vpc, router: router.name }).optionsFn() ) - } ), - ]) + ] satisfies Promise[]) return null } @@ -235,11 +242,13 @@ export function EditInternetGatewayForm() { - Router - Route + + Router + Route + - +