Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
594 changes: 299 additions & 295 deletions app/api/__generated__/Api.ts

Large diffs are not rendered by default.

626 changes: 334 additions & 292 deletions app/api/__generated__/validate.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { navToLogin } from './nav-to-login'
type Params<F> = F extends (p: infer P) => any ? P : never
type Result<F> = F extends (p: any) => Promise<ApiResult<infer R>> ? R : never

export type ResultsPage<TItem> = { items: TItem[]; nextPage?: string }
export type ResultsPage<TItem> = { items: TItem[]; nextPage?: string | null }

type ApiClient = Record<string, (...args: any) => Promise<ApiResult<any>>>
/* eslint-enable @typescript-eslint/no-explicit-any */
Expand Down
4 changes: 2 additions & 2 deletions app/components/InstanceAutoRestartPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CloseButton, Popover, PopoverButton, PopoverPanel } from '@headlessui/r
import { formatDistanceToNow } from 'date-fns'
import { type ReactNode } from 'react'
import { Link } from 'react-router'
import { match } from 'ts-pattern'
import { match, P } from 'ts-pattern'

import {
AutoRestart12Icon,
Expand Down Expand Up @@ -69,7 +69,7 @@ export const InstanceAutoRestartPopover = ({ instance }: { instance: Instance })
</Badge>
))
.with('best_effort', () => <Badge>best effort</Badge>)
.with(undefined, () => <Badge color="neutral">Default</Badge>)
.with(P.nullish, () => <Badge color="neutral">Default</Badge>)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jerk!

Copy link
Collaborator Author

@david-crespo david-crespo May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed a test fails with a null policy without the fix, passes with the fix.

Screenshot 2025-05-06 at 5 56 36 PM

.exhaustive()}
<div className="transition-transform group-hover:translate-x-1">
<NextArrow12Icon />
Expand Down
2 changes: 1 addition & 1 deletion app/components/form/fields/useItemsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function customRouterFormToData(value: string): string | undefined {
}

/** Convert value from response body to form value */
export function customRouterDataToForm(value: string | undefined): string {
export function customRouterDataToForm(value: string | undefined | null): string {
return value || NO_ROUTER
}

Expand Down
3 changes: 0 additions & 3 deletions app/components/oxql-metrics/util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ const utilizationQueryResult1: OxqlQueryResult = {
{
values: {
type: 'double',
// there is a bug in the client generator that makes this not allow nulls,
// but we can in fact get them from the API for these values
// @ts-expect-error
values: [4991154550.953981, 5002306111.529594, 5005747970.58788, null],
},
metricType: 'gauge',
Expand Down
4 changes: 2 additions & 2 deletions app/forms/network-interface-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { useMemo } from 'react'
import { useForm } from 'react-hook-form'
import type { SetRequired } from 'type-fest'
import type { SetNonNullable, SetRequired } from 'type-fest'

import { useApiQuery, type ApiError, type InstanceNetworkInterfaceCreate } from '@oxide/api'

Expand All @@ -20,7 +20,7 @@ import { SideModalForm } from '~/components/form/SideModalForm'
import { useProjectSelector } from '~/hooks/use-params'
import { FormDivider } from '~/ui/lib/Divider'

const defaultValues: SetRequired<InstanceNetworkInterfaceCreate, 'ip'> = {
const defaultValues: SetRequired<SetNonNullable<InstanceNetworkInterfaceCreate>, 'ip'> = {
name: '',
description: '',
ip: '',
Expand Down
3 changes: 2 additions & 1 deletion app/forms/subnet-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import { useForm } from 'react-hook-form'
import { useNavigate } from 'react-router'
import type { SetNonNullable } from 'type-fest'

import { useApiMutation, useApiQueryClient, type VpcSubnetCreate } from '@oxide/api'

Expand All @@ -27,7 +28,7 @@ import { addToast } from '~/stores/toast'
import { FormDivider } from '~/ui/lib/Divider'
import { pb } from '~/util/path-builder'

const defaultValues: Required<VpcSubnetCreate> = {
const defaultValues: SetNonNullable<Required<VpcSubnetCreate>> = {
name: '',
description: '',
ipv4Block: '',
Expand Down
3 changes: 2 additions & 1 deletion app/forms/subnet-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import { useForm } from 'react-hook-form'
import { useNavigate, type LoaderFunctionArgs } from 'react-router'
import type { SetNonNullable } from 'type-fest'

import {
apiq,
Expand Down Expand Up @@ -61,7 +62,7 @@ export default function EditSubnetForm() {
},
})

const defaultValues: Required<VpcSubnetUpdate> = {
const defaultValues: SetNonNullable<Required<VpcSubnetUpdate>> = {
name: subnet.name,
description: subnet.description,
customRouter: customRouterDataToForm(subnet.customRouterId),
Expand Down
5 changes: 4 additions & 1 deletion app/forms/vpc-router-route-common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import type { UseFormReturn } from 'react-hook-form'
import type { SetNonNullable } from 'type-fest'

import {
usePrefetchedApiQuery,
Expand All @@ -26,7 +27,9 @@ import { Message } from '~/ui/lib/Message'
import { ALL_ISH } from '~/util/consts'
import { validateIp, validateIpNet } from '~/util/ip'

export type RouteFormValues = RouterRouteCreate | Required<RouterRouteUpdate>
export type RouteFormValues =
| RouterRouteCreate
| SetNonNullable<Required<RouterRouteUpdate>>

export const routeFormMessage = {
vpcSubnetNotModifiable:
Expand Down
3 changes: 2 additions & 1 deletion app/pages/system/silos/SiloQuotasTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { useState } from 'react'
import { useForm } from 'react-hook-form'
import type { SetNonNullable } from 'type-fest'

import {
apiQueryClient,
Expand Down Expand Up @@ -96,7 +97,7 @@ function EditQuotasForm({ onDismiss }: { onDismiss: () => void }) {
const quotas = utilization.allocated

// required because we need to rule out undefined because NumberField hates that
const defaultValues: Required<SiloQuotasUpdate> = {
const defaultValues: SetNonNullable<Required<SiloQuotasUpdate>> = {
cpus: quotas.cpus,
memory: bytesToGiB(quotas.memory),
storage: bytesToGiB(quotas.storage),
Expand Down
2 changes: 1 addition & 1 deletion app/table/cells/InstanceLinkCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { pb } from '~/util/path-builder'
import { EmptyCell, SkeletonCell } from './EmptyCell'
import { LinkCell } from './LinkCell'

export const InstanceLinkCell = ({ instanceId }: { instanceId?: string }) => {
export const InstanceLinkCell = ({ instanceId }: { instanceId?: string | null }) => {
const { project } = useProjectSelector()
const { data: instance } = useApiQuery(
'instanceView',
Expand Down
2 changes: 1 addition & 1 deletion app/table/cells/RouterLinkCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { pb } from '~/util/path-builder'
import { EmptyCell, SkeletonCell } from './EmptyCell'
import { LinkCell } from './LinkCell'

export const RouterLinkCell = ({ routerId }: { routerId?: string }) => {
export const RouterLinkCell = ({ routerId }: { routerId?: string | null }) => {
const { project, vpc } = useVpcSelector()
const { data: router, isError } = useApiQuery(
'vpcRouterView',
Expand Down
2 changes: 1 addition & 1 deletion app/ui/lib/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface PaginationProps {
pageSize: number
hasNext: boolean
hasPrev: boolean
nextPage: string | undefined
nextPage: string | undefined | null
onNext: (nextPage: string) => void
onPrev: () => void
className?: string
Expand Down
1 change: 1 addition & 0 deletions mock-api/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const failedInstance: Json<Instance> = {
hostname: 'oxide.com',
project_id: project.id,
run_state: 'failed',
auto_restart_policy: null,
auto_restart_cooldown_expiration: addMinutes(new Date(), 5).toISOString(), // 5 minutes from now
time_last_auto_restarted: addMinutes(new Date(), -55).toISOString(), // 55 minutes ago
}
Expand Down
4 changes: 2 additions & 2 deletions mock-api/msw/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ function ensureNoParentSelectors(
}
}

export const getIpFromPool = (poolName: string | undefined) => {
const pool = lookup.ipPool({ pool: poolName })
export const getIpFromPool = (poolName: string | undefined | null) => {
const pool = lookup.ipPool({ pool: poolName || undefined })
const ipPoolRange = db.ipPoolRanges.find((range) => range.ip_pool_id === pool.id)
if (!ipPoolRange) throw notFoundErr(`IP range for pool '${poolName}'`)

Expand Down
16 changes: 11 additions & 5 deletions mock-api/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,14 @@ export const handlers = makeHandlers({
id: uuid(),
project_id: project.id,
// TODO: use ip-num to actually get the next available IP in the pool
ip: [...Array(4)].map(() => Math.floor(Math.random() * 256)).join('.'),
ip:
body.ip ||
Array.from({ length: 4 })
.map(() => Math.floor(Math.random() * 256))
.join('.'),
ip_pool_id: pool.id,
...body,
description: body.description,
name: body.name,
...getTimestamps(),
}
db.floatingIps.push(newFloatingIp)
Expand Down Expand Up @@ -661,6 +666,7 @@ export const handlers = makeHandlers({
// https://github.com/oxidecomputer/omicron/blob/0c6ab099e/nexus/db-queries/src/db/datastore/instance.rs#L228-L239
instance.auto_restart_enabled = match(instance.auto_restart_policy)
.with(undefined, () => true)
.with(null, () => true)
.with('best_effort', () => true)
.with('never', () => false)
.exhaustive()
Expand Down Expand Up @@ -1518,9 +1524,9 @@ export const handlers = makeHandlers({
requireFleetCollab(cookies)
const quotas = lookup.siloQuotas(path)

if (body.cpus !== undefined) quotas.cpus = body.cpus
if (body.memory !== undefined) quotas.memory = body.memory
if (body.storage !== undefined) quotas.storage = body.storage
if (body.cpus != null) quotas.cpus = body.cpus
if (body.memory != null) quotas.memory = body.memory
if (body.storage != null) quotas.storage = body.storage

return quotas
},
Expand Down
12 changes: 7 additions & 5 deletions mock-api/msw/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import { genI64Data } from '../metrics'
import { db } from './db'

interface PaginateOptions {
limit?: number
pageToken?: string
limit?: number | null
pageToken?: string | null
}
export interface ResultsPage<I extends { id: string }> {
items: I[]
Expand All @@ -49,7 +49,9 @@ export const paginated = <P extends PaginateOptions, I extends { id: string }>(
params: P,
items: I[]
) => {
const { limit = 100, pageToken } = params || {}
const limit = params.limit || 100
const pageToken = params.pageToken

let startIndex = pageToken ? items.findIndex((i) => i.id === pageToken) : 0
startIndex = startIndex < 0 ? 0 : startIndex

Expand Down Expand Up @@ -410,10 +412,10 @@ export const ipInAnyRange = (ip: string, ranges: IpRange[]) =>

export function updateDesc(
resource: { description: string },
update: { description?: string }
update: { description?: string | null }
) {
// Can't be `if (update.description)` because you could never set it to ''
if (update.description !== undefined) {
if (update.description != null) {
resource.description = update.description
}
}
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"devDependencies": {
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@mswjs/http-middleware": "^0.10.3",
"@oxide/openapi-gen-ts": "~0.6.2",
"@oxide/openapi-gen-ts": "~0.7.0",
"@playwright/test": "^1.52.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
Expand Down
Loading