From 2152a89cefd0d7acf75b70f0edab79221e0e3181 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 3 Mar 2025 11:56:58 -0600 Subject: [PATCH 1/2] Back out "chore: convert last few routes (#2719)" This backs out commit a3566bd27df8a371a11acf1d0d1eacb7f00ba79f. --- app/pages/project/vpcs/VpcPage.tsx | 4 +- app/pages/project/vpcs/VpcRoutersTab.tsx | 7 +- app/pages/project/vpcs/VpcsPage.tsx | 7 +- app/pages/{ => system}/SiloImageEdit.tsx | 0 app/pages/{ => system}/SiloImagesPage.tsx | 7 +- app/pages/system/inventory/DisksTab.tsx | 7 +- app/pages/system/inventory/InventoryPage.tsx | 7 +- app/pages/system/inventory/SledsTab.tsx | 7 +- .../inventory/sled/SledInstancesTab.tsx | 7 +- app/pages/system/inventory/sled/SledPage.tsx | 11 +-- app/pages/system/networking/IpPoolPage.tsx | 8 +- app/pages/system/networking/IpPoolsPage.tsx | 7 +- app/routes.tsx | 89 ++++++++----------- test/e2e/breadcrumbs.e2e.ts | 2 +- test/e2e/inventory.e2e.ts | 1 - vite.config.ts | 2 +- 16 files changed, 70 insertions(+), 103 deletions(-) rename app/pages/{ => system}/SiloImageEdit.tsx (100%) rename app/pages/{ => system}/SiloImagesPage.tsx (98%) diff --git a/app/pages/project/vpcs/VpcPage.tsx b/app/pages/project/vpcs/VpcPage.tsx index 6518c6f04e..9f9c489f54 100644 --- a/app/pages/project/vpcs/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage.tsx @@ -27,12 +27,12 @@ import { VpcDocsPopover } from './VpcsPage' const vpcView = ({ project, vpc }: PP.Vpc) => apiq('vpcView', { path: { vpc }, query: { project } }) -export async function clientLoader({ params }: LoaderFunctionArgs) { +VpcPage.loader = async ({ params }: LoaderFunctionArgs) => { await queryClient.prefetchQuery(vpcView(getVpcSelector(params))) return null } -export default function VpcPage() { +export function VpcPage() { const navigate = useNavigate() const vpcSelector = useVpcSelector() const { project, vpc: vpcName } = vpcSelector diff --git a/app/pages/project/vpcs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcRoutersTab.tsx index daadeabe4b..62288118af 100644 --- a/app/pages/project/vpcs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcRoutersTab.tsx @@ -35,15 +35,14 @@ const colHelper = createColumnHelper() const vpcRouterList = (query: PP.Vpc) => getListQFn('vpcRouterList', { query }) -export async function clientLoader({ params }: LoaderFunctionArgs) { +export async function loader({ params }: LoaderFunctionArgs) { const { project, vpc } = getVpcSelector(params) await queryClient.prefetchQuery(vpcRouterList({ project, vpc }).optionsFn()) return null } -export const handle = { crumb: 'Routers' } - -export default function VpcRoutersTab() { +Component.displayName = 'VpcRoutersTab' +export function Component() { const vpcSelector = useVpcSelector() const navigate = useNavigate() const { project, vpc } = vpcSelector diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index 659d5a02a7..72406192f2 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -15,7 +15,6 @@ import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/r import { DocsPopover } from '~/components/DocsPopover' import { HL } from '~/components/HL' -import { makeCrumb } from '~/hooks/use-crumbs' import { getProjectSelector, useProjectSelector } from '~/hooks/use-params' import { useQuickActions } from '~/hooks/use-quick-actions' import { confirmDelete } from '~/stores/confirm-delete' @@ -66,15 +65,13 @@ const colHelper = createColumnHelper() // just as in the vpcList call for the quick actions menu, include limit to make // sure it matches the call in the QueryTable -export async function clientLoader({ params }: LoaderFunctionArgs) { +VpcsPage.loader = async ({ params }: LoaderFunctionArgs) => { const { project } = getProjectSelector(params) await queryClient.prefetchQuery(vpcList(project).optionsFn()) return null } -export const handle = makeCrumb('VPCs', (p) => pb.vpcs(getProjectSelector(p))) - -export default function VpcsPage() { +export function VpcsPage() { const { project } = useProjectSelector() const navigate = useNavigate() diff --git a/app/pages/SiloImageEdit.tsx b/app/pages/system/SiloImageEdit.tsx similarity index 100% rename from app/pages/SiloImageEdit.tsx rename to app/pages/system/SiloImageEdit.tsx diff --git a/app/pages/SiloImagesPage.tsx b/app/pages/system/SiloImagesPage.tsx similarity index 98% rename from app/pages/SiloImagesPage.tsx rename to app/pages/system/SiloImagesPage.tsx index 82f0ca89dd..977131244a 100644 --- a/app/pages/SiloImagesPage.tsx +++ b/app/pages/system/SiloImagesPage.tsx @@ -51,13 +51,11 @@ const EmptyState = () => ( const imageList = getListQFn('imageList', {}) -export async function clientLoader() { +export async function loader() { await queryClient.prefetchQuery(imageList.optionsFn()) return null } -export const handle = { crumb: 'Images' } - const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('name', { @@ -68,7 +66,8 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -export default function SiloImagesPage() { +Component.displayName = 'SiloImagesPage' +export function Component() { const [showModal, setShowModal] = useState(false) const [demoteImage, setDemoteImage] = useState(null) diff --git a/app/pages/system/inventory/DisksTab.tsx b/app/pages/system/inventory/DisksTab.tsx index 02025e3ecc..36a442688b 100644 --- a/app/pages/system/inventory/DisksTab.tsx +++ b/app/pages/system/inventory/DisksTab.tsx @@ -40,13 +40,11 @@ const EmptyState = () => ( const diskList = getListQFn('physicalDiskList', {}) -export async function clientLoader() { +export async function loader() { await queryClient.prefetchQuery(diskList.optionsFn()) return null } -export const handle = { crumb: 'Disks' } - const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('id', {}), @@ -71,7 +69,8 @@ const staticCols = [ }), ] -export default function DisksTab() { +Component.displayName = 'DisksTab' +export function Component() { const emptyState = const { table } = useQueryTable({ query: diskList, columns: staticCols, emptyState }) return table diff --git a/app/pages/system/inventory/InventoryPage.tsx b/app/pages/system/inventory/InventoryPage.tsx index 215c444836..8e1a10df9b 100644 --- a/app/pages/system/inventory/InventoryPage.tsx +++ b/app/pages/system/inventory/InventoryPage.tsx @@ -11,21 +11,18 @@ import { Servers16Icon, Servers24Icon } from '@oxide/design-system/icons/react' import { DocsPopover } from '~/components/DocsPopover' import { RouteTabs, Tab } from '~/components/RouteTabs' -import { makeCrumb } from '~/hooks/use-crumbs' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' const rackList = getListQFn('rackList', {}) -export async function clientLoader() { +InventoryPage.loader = async () => { await queryClient.prefetchQuery(rackList.optionsFn()) return null } -export const handle = makeCrumb('Inventory', pb.sledInventory()) - -export default function InventoryPage() { +export function InventoryPage() { const { data: racks } = usePrefetchedQuery(rackList.optionsFn()) const rack = racks?.items[0] diff --git a/app/pages/system/inventory/SledsTab.tsx b/app/pages/system/inventory/SledsTab.tsx index 68b94f344c..4051a05052 100644 --- a/app/pages/system/inventory/SledsTab.tsx +++ b/app/pages/system/inventory/SledsTab.tsx @@ -30,13 +30,11 @@ const STATE_BADGE_COLORS: Record = { const sledList = getListQFn('sledList', {}) -export async function clientLoader() { +export async function loader() { await queryClient.prefetchQuery(sledList.optionsFn()) return null } -export const handle = { crumb: 'Sleds' } - const colHelper = createColumnHelper() const staticCols = [ colHelper.accessor('id', { @@ -90,7 +88,8 @@ const staticCols = [ }), ] -export default function SledsTab() { +Component.displayName = 'SledsTab' +export function Component() { const emptyState = } title="No sleds found" /> const { table } = useQueryTable({ query: sledList, columns: staticCols, emptyState }) return table diff --git a/app/pages/system/inventory/sled/SledInstancesTab.tsx b/app/pages/system/inventory/sled/SledInstancesTab.tsx index 6440767e6e..59c80b724d 100644 --- a/app/pages/system/inventory/sled/SledInstancesTab.tsx +++ b/app/pages/system/inventory/sled/SledInstancesTab.tsx @@ -33,14 +33,12 @@ const EmptyState = () => { ) } -export async function clientLoader({ params }: LoaderFunctionArgs) { +export async function loader({ params }: LoaderFunctionArgs) { const { sledId } = requireSledParams(params) await queryClient.prefetchQuery(sledInstanceList(sledId).optionsFn()) return null } -export const handle = { crumb: 'Instances' } - // passing in empty function because we still want the copy ID button const makeActions = (): MenuAction[] => [] @@ -71,7 +69,8 @@ const staticCols = [ colHelper.accessor('timeCreated', Columns.timeCreated), ] -export default function SledInstancesTab() { +Component.displayName = 'SledInstancesTab' +export function Component() { const { sledId } = useSledParams() const columns = useColsWithActions(staticCols, makeActions) const { table } = useQueryTable({ diff --git a/app/pages/system/inventory/sled/SledPage.tsx b/app/pages/system/inventory/sled/SledPage.tsx index b150a158ce..4119b67e3c 100644 --- a/app/pages/system/inventory/sled/SledPage.tsx +++ b/app/pages/system/inventory/sled/SledPage.tsx @@ -12,26 +12,21 @@ import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api' import { Servers24Icon } from '@oxide/design-system/icons/react' import { RouteTabs, Tab } from '~/components/RouteTabs' -import { makeCrumb } from '~/hooks/use-crumbs' import { requireSledParams, useSledParams } from '~/hooks/use-params' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { PropertiesTable } from '~/ui/lib/PropertiesTable' -import { truncate } from '~/ui/lib/Truncate' import { pb } from '~/util/path-builder' -export async function clientLoader({ params }: LoaderFunctionArgs) { +export async function loader({ params }: LoaderFunctionArgs) { const { sledId } = requireSledParams(params) await apiQueryClient.prefetchQuery('sledView', { path: { sledId }, }) return null } -export const handle = makeCrumb( - (p) => truncate(p.sledId!, 12, 'middle'), - (p) => pb.sled({ sledId: p.sledId! }) -) -export default function SledPage() { +Component.displayName = 'SledPage' +export function Component() { const { sledId } = useSledParams() const { data: sled } = usePrefetchedApiQuery('sledView', { path: { sledId } }) diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index cea1eab308..dd05e1855f 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -33,7 +33,6 @@ import { ComboboxField } from '~/components/form/fields/ComboboxField' import { HL } from '~/components/HL' import { MoreActionsMenu } from '~/components/MoreActionsMenu' import { QueryParamTabs } from '~/components/QueryParamTabs' -import { makeCrumb } from '~/hooks/use-crumbs' import { getIpPoolSelector, useIpPoolSelector } from '~/hooks/use-params' import { confirmAction } from '~/stores/confirm-action' import { confirmDelete } from '~/stores/confirm-delete' @@ -60,7 +59,7 @@ const ipPoolView = (pool: string) => apiq('ipPoolView', { path: { pool } }) const ipPoolSiloList = (pool: string) => getListQFn('ipPoolSiloList', { path: { pool } }) const ipPoolRangeList = (pool: string) => getListQFn('ipPoolRangeList', { path: { pool } }) -export async function clientLoader({ params }: LoaderFunctionArgs) { +export async function loader({ params }: LoaderFunctionArgs) { const { pool } = getIpPoolSelector(params) await Promise.all([ queryClient.prefetchQuery(ipPoolView(pool)), @@ -80,9 +79,8 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { return null } -export const handle = makeCrumb((p) => p.pool!) - -export default function IpPoolpage() { +Component.displayName = 'IpPoolPage' +export function Component() { const poolSelector = useIpPoolSelector() const { data: pool } = usePrefetchedQuery(ipPoolView(poolSelector.pool)) const { data: ranges } = usePrefetchedQuery( diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index 17c83e6cd7..f112eb3e49 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -69,14 +69,13 @@ const staticColumns = [ const ipPoolList = () => getListQFn('ipPoolList', {}) -export async function clientLoader() { +export async function loader() { await queryClient.prefetchQuery(ipPoolList().optionsFn()) return null } -export const handle = { crumb: 'IP Pools' } - -export default function IpPoolsPage() { +Component.displayName = 'IpPoolsPage' +export function Component() { const navigate = useNavigate() const { mutateAsync: deletePool } = useApiMutation('ipPoolDelete', { diff --git a/app/routes.tsx b/app/routes.tsx index c69d1e7d9d..9425f9e7e7 100644 --- a/app/routes.tsx +++ b/app/routes.tsx @@ -16,7 +16,19 @@ import { import { NotFound } from './components/ErrorPage' import { makeCrumb, type Crumb } from './hooks/use-crumbs' -import { getInstanceSelector, getVpcSelector } from './hooks/use-params' +import { getInstanceSelector, getProjectSelector, getVpcSelector } from './hooks/use-params' +import * as VpcRoutersTab from './pages/project/vpcs//VpcRoutersTab' +import { VpcPage } from './pages/project/vpcs/VpcPage' +import { VpcsPage } from './pages/project/vpcs/VpcsPage' +import * as DisksTab from './pages/system/inventory/DisksTab' +import { InventoryPage } from './pages/system/inventory/InventoryPage' +import * as SledInstances from './pages/system/inventory/sled/SledInstancesTab' +import * as SledPage from './pages/system/inventory/sled/SledPage' +import * as SledsTab from './pages/system/inventory/SledsTab' +import * as IpPool from './pages/system/networking/IpPoolPage' +import * as IpPools from './pages/system/networking/IpPoolsPage' +import * as SiloImages from './pages/system/SiloImagesPage' +import { truncate } from './ui/lib/Truncate' import { pb } from './util/path-builder' // hack because RR doesn't export the redirect type @@ -117,58 +129,37 @@ export const routes = createRoutesFromElements( /> import('./pages/system/inventory/InventoryPage.tsx').then(convert)} + element={} + loader={InventoryPage.loader} + handle={makeCrumb('Inventory', pb.sledInventory())} > - - import('./pages/system/inventory/SledsTab') - .then(convert) - .then(({ loader }) => ({ - loader, - Component: () => , - })) - } - /> - import('./pages/system/inventory/SledsTab').then(convert)} - /> - import('./pages/system/inventory/DisksTab').then(convert)} - /> + } loader={SledsTab.loader} /> + + {/* a crumb for the sled ID looks ridiculous, unfortunately */} import('./pages/system/inventory/sled/SledPage').then(convert)} + {...SledPage} + handle={makeCrumb( + (p) => truncate(p.sledId!, 12, 'middle'), + (p) => pb.sled({ sledId: p.sledId! }) + )} > - import('./pages/system/inventory/sled/SledInstancesTab') - .then(convert) - .then(({ loader }) => ({ - loader, - Component: () => , - })) - } - /> - - import('./pages/system/inventory/sled/SledInstancesTab').then(convert) - } + element={} + loader={SledInstances.loader} /> + } /> - import('./pages/system/networking/IpPoolsPage').then(convert)}> + - import('./pages/system/networking/IpPoolPage').then(convert)} - > + p.pool!)}> import('./forms/ip-pool-edit').then(convert)} /> } /> import('./layouts/SiloLayout').then(convert)}> - import('./pages/SiloImagesPage.tsx').then(convert)} - > + import('./pages/SiloImageEdit.tsx').then(convert)} + lazy={() => import('./pages/system/SiloImageEdit').then(convert)} /> - import('./pages/project/vpcs/VpcsPage').then(convert)}> + pb.vpcs(getProjectSelector(p)))} + element={} + > pb.vpc(getVpcSelector(p)) )} > - import('./pages/project/vpcs/VpcPage').then(convert)}> + } loader={VpcPage.loader}> import('./forms/subnet-edit').then(convert)} /> - import('./pages/project/vpcs/VpcRoutersTab').then(convert)} - > + getCrumbs(page), { timeout: 10000 }).toEqual(crumbs) + await expect.poll(() => getCrumbs(page)).toEqual(crumbs) } const projectCrumbs: Pair[] = [ diff --git a/test/e2e/inventory.e2e.ts b/test/e2e/inventory.e2e.ts index b77f9e65d3..387fa3f82b 100644 --- a/test/e2e/inventory.e2e.ts +++ b/test/e2e/inventory.e2e.ts @@ -55,7 +55,6 @@ test('Sled inventory page', async ({ page }) => { await sledsTable.getByRole('link').first().click() await expectVisible(page, ['role=heading[name*="Sled"]']) - await expect(page.getByText('serialBRM02222869')).toBeVisible() const instancesTab = page.getByRole('tab', { name: 'Instances' }) await expect(instancesTab).toBeVisible() diff --git a/vite.config.ts b/vite.config.ts index 6a1607f4a7..87af9e0a99 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -110,7 +110,7 @@ export default defineConfig(({ mode }) => ({ // but some end up being like 300 bytes. It feels silly to have several // hundred of those, so we set a minimum size to end up with fewer. // https://rollupjs.org/configuration-options/#output-experimentalminchunksize - experimentalMinChunkSize: 30 * KiB, + experimentalMinChunkSize: 5 * KiB, }, }, // prevent inlining assets as `data:`, which is not permitted by our Content-Security-Policy From 63c4fdfe29bd108b729add8048416c55cc581c83 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 3 Mar 2025 12:07:47 -0600 Subject: [PATCH 2/2] retain-on-failure even in CI --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 2fd345a3f2..68ed1372f1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,7 @@ export default { timeout: 60 * 1000, // 1 minute fullyParallel: true, use: { - trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure', + trace: 'retain-on-failure', baseURL: 'http://localhost:4009', }, projects: [