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
17 changes: 3 additions & 14 deletions app/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import {
getApiQueryOptionsErrorsAllowed,
getListQueryOptionsFn,
getUseApiMutation,
getUsePrefetchedApiQuery,
wrapQueryClient,
} from './hooks'

export const api = new Api({
Expand Down Expand Up @@ -55,15 +53,14 @@ export const apiqErrorsAllowed = getApiQueryOptionsErrorsAllowed(api.methods)
* `useQueryTable`.
*/
export const getListQFn = getListQueryOptionsFn(api.methods)
export const useApiMutation = getUseApiMutation(api.methods)

/**
* Same as `useApiQuery`, except we use `invariant(data)` to ensure the data is
* Same as `useQuery`, except we use `invariant(data)` to ensure the data is
* already there in the cache at request time, which means it has been
* prefetched in a loader. Whenever this hook is used, there should be an e2e
* test loading the page to exercise the invariant in CI.
*/
export const usePrefetchedApiQuery = getUsePrefetchedApiQuery(api.methods)
export const useApiMutation = getUseApiMutation(api.methods)

export const usePrefetchedQuery = <TData>(options: UseQueryOptions<TData, ApiError>) =>
ensurePrefetched(useQuery(options), options.queryKey)

Expand Down Expand Up @@ -96,11 +93,3 @@ export const queryClient = new QueryClient({
},
},
})

// to be used in loaders, which are outside the component tree and therefore
// don't have access to context
export const apiQueryClient = wrapQueryClient(api.methods, queryClient)

// used to retrieve the typed query client in components. doesn't need to exist:
// we could import apiQueryClient directly everywhere, but the change is noisy
export const useApiQueryClient = () => apiQueryClient
65 changes: 0 additions & 65 deletions app/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import {
hashKey,
queryOptions,
useMutation,
useQuery,
type FetchQueryOptions,
type InvalidateQueryFilters,
type QueryClient,
type QueryKey,
type UseMutationOptions,
type UseQueryOptions,
Expand Down Expand Up @@ -89,15 +85,6 @@ type UseQueryOtherOptions<T> = Omit<
'queryKey' | 'queryFn' | 'initialData'
>

/**
* `queryKey` and `queryFn` are always constructed by our helper hooks, so we
* only allow the rest of the options.
*/
type FetchQueryOtherOptions<T> = Omit<
FetchQueryOptions<T, ApiError>,
'queryKey' | 'queryFn'
>

export const getApiQueryOptions =
<A extends ApiClient>(api: A) =>
<M extends string & keyof A>(
Expand Down Expand Up @@ -177,17 +164,6 @@ export const getListQueryOptionsFn =
}
}

export const getUsePrefetchedApiQuery =
<A extends ApiClient>(api: A) =>
<M extends string & keyof A>(
method: M,
params: Params<A[M]>,
options: UseQueryOtherOptions<Result<A[M]>> = {}
) => {
const qOptions = getApiQueryOptions(api)(method, params, options)
return ensurePrefetched(useQuery(qOptions), qOptions.queryKey)
}

const prefetchError = (key?: QueryKey) =>
`Expected query to be prefetched.
Key: ${key ? hashKey(key) : '<unknown>'}
Expand Down Expand Up @@ -257,47 +233,6 @@ export const getUseApiMutation =
...options,
})

export const wrapQueryClient = <A extends ApiClient>(api: A, queryClient: QueryClient) => ({
/**
* Note that we only take a single argument, `method`, rather than allowing
* the full query key `[query, params]` to be specified. This is to avoid
* accidentally overspecifying and therefore failing to match the desired
* query. The params argument can be added back in if we ever have a use case
* for it.
*
* Passing no arguments will invalidate all queries.
*/
invalidateQueries: <M extends keyof A>(method?: M, filters?: InvalidateQueryFilters) =>
queryClient.invalidateQueries(method ? { queryKey: [method], ...filters } : undefined),
setQueryData: <M extends keyof A>(method: M, params: Params<A[M]>, data: Result<A[M]>) =>
queryClient.setQueryData([method, params], data),
setQueryDataErrorsAllowed: <M extends keyof A>(
method: M,
params: Params<A[M]>,
data: ErrorsAllowed<Result<A[M]>, ApiError>
) => queryClient.setQueryData([method, params, ERRORS_ALLOWED], data),
fetchQuery: <M extends string & keyof A>(
method: M,
params: Params<A[M]>,
options: FetchQueryOtherOptions<Result<A[M]>> = {}
) =>
queryClient.fetchQuery({
queryKey: [method, params],
queryFn: () => api[method](params).then(handleResult(method)),
...options,
}),
prefetchQuery: <M extends string & keyof A>(
method: M,
params: Params<A[M]>,
options: FetchQueryOtherOptions<Result<A[M]>> = {}
) =>
queryClient.prefetchQuery({
queryKey: [method, params],
queryFn: () => api[method](params).then(handleResult(method)),
...options,
}),
})

/*
1. what's up with [method, params]?

Expand Down
10 changes: 5 additions & 5 deletions app/api/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useMemo } from 'react'
import * as R from 'remeda'

import type { FleetRole, IdentityType, ProjectRole, SiloRole } from './__generated__/Api'
import { usePrefetchedApiQuery } from './client'
import { apiq, usePrefetchedQuery } from './client'

/**
* Union of all the specific roles, which are all the same, which makes making
Expand Down Expand Up @@ -98,8 +98,8 @@ export function useUserRows(
): UserAccessRow[] {
// HACK: because the policy has no names, we are fetching ~all the users,
// putting them in a dictionary, and adding the names to the rows
const { data: users } = usePrefetchedApiQuery('userList', {})
const { data: groups } = usePrefetchedApiQuery('groupList', {})
const { data: users } = usePrefetchedQuery(apiq('userList', {}))
const { data: groups } = usePrefetchedQuery(apiq('groupList', {}))
return useMemo(() => {
const userItems = users?.items || []
const groupItems = groups?.items || []
Expand Down Expand Up @@ -137,8 +137,8 @@ export type Actor = {
* the given policy.
*/
export function useActorsNotInPolicy(policy: Policy): Actor[] {
const { data: users } = usePrefetchedApiQuery('userList', {})
const { data: groups } = usePrefetchedApiQuery('groupList', {})
const { data: users } = usePrefetchedQuery(apiq('userList', {}))
const { data: groups } = usePrefetchedQuery(apiq('groupList', {}))
return useMemo(() => {
// IDs are UUIDs, so no need to include identity type in set value to disambiguate
const actorsInPolicy = new Set(policy?.roleAssignments.map((ra) => ra.identityId) || [])
Expand Down
11 changes: 5 additions & 6 deletions app/components/AttachEphemeralIpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { useMemo } from 'react'
import { useForm } from 'react-hook-form'

import { useApiMutation, useApiQueryClient, usePrefetchedApiQuery } from '~/api'
import { apiq, queryClient, useApiMutation, usePrefetchedQuery } from '~/api'
import { ListboxField } from '~/components/form/fields/ListboxField'
import { HL } from '~/components/HL'
import { useInstanceSelector } from '~/hooks/use-params'
Expand All @@ -20,18 +20,17 @@ import { ALL_ISH } from '~/util/consts'
import { toIpPoolItem } from './form/fields/ip-pool-item'

export const AttachEphemeralIpModal = ({ onDismiss }: { onDismiss: () => void }) => {
const queryClient = useApiQueryClient()
const { project, instance } = useInstanceSelector()
const { data: siloPools } = usePrefetchedApiQuery('projectIpPoolList', {
query: { limit: ALL_ISH },
})
const { data: siloPools } = usePrefetchedQuery(
apiq('projectIpPoolList', { query: { limit: ALL_ISH } })
)
const defaultPool = useMemo(
() => siloPools?.items.find((pool) => pool.isDefault),
[siloPools]
)
const instanceEphemeralIpAttach = useApiMutation('instanceEphemeralIpAttach', {
onSuccess(ephemeralIp) {
queryClient.invalidateQueries('instanceExternalIpList')
queryClient.invalidateEndpoint('instanceExternalIpList')
addToast(<>IP <HL>{ephemeralIp.ip}</HL> attached</>) // prettier-ignore
onDismiss()
},
Expand Down
7 changes: 3 additions & 4 deletions app/components/AttachFloatingIpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { useForm } from 'react-hook-form'

import {
apiqErrorsAllowed,
queryClient,
useApiMutation,
useApiQueryClient,
type FloatingIp,
type Instance,
} from '~/api'
Expand Down Expand Up @@ -67,11 +67,10 @@ export const AttachFloatingIpModal = ({
instance: Instance
onDismiss: () => void
}) => {
const queryClient = useApiQueryClient()
const floatingIpAttach = useApiMutation('floatingIpAttach', {
onSuccess(floatingIp) {
queryClient.invalidateQueries('floatingIpList')
queryClient.invalidateQueries('instanceExternalIpList')
queryClient.invalidateEndpoint('floatingIpList')
queryClient.invalidateEndpoint('instanceExternalIpList')
addToast(<>IP <HL>{floatingIp.name}</HL> attached</>) // prettier-ignore
onDismiss()
},
Expand Down
4 changes: 2 additions & 2 deletions app/components/form/fields/SshKeysField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { useState } from 'react'
import { useController, type Control } from 'react-hook-form'

import { usePrefetchedApiQuery } from '@oxide/api'
import { apiq, usePrefetchedQuery } from '@oxide/api'
import { Key16Icon } from '@oxide/design-system/icons/react'

import type { InstanceCreateInput } from '~/forms/instance-create'
Expand Down Expand Up @@ -55,7 +55,7 @@ export function SshKeysField({
control: Control<InstanceCreateInput>
isSubmitting: boolean
}) {
const keys = usePrefetchedApiQuery('currentUserSshKeyList', {}).data?.items || []
const keys = usePrefetchedQuery(apiq('currentUserSshKeyList', {})).data?.items || []
const [showAddSshKey, setShowAddSshKey] = useState(false)

const {
Expand Down
6 changes: 2 additions & 4 deletions app/forms/disk-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { useController, useForm, type Control } from 'react-hook-form'

import {
apiq,
queryClient,
useApiMutation,
useApiQueryClient,
type BlockSize,
type Disk,
type DiskCreate,
Expand Down Expand Up @@ -70,11 +70,9 @@ export function CreateDiskSideModalForm({
onDismiss,
unavailableDiskNames = [],
}: CreateSideModalFormProps) {
const queryClient = useApiQueryClient()

const createDisk = useApiMutation('diskCreate', {
onSuccess(data) {
queryClient.invalidateQueries('diskList')
queryClient.invalidateEndpoint('diskList')
addToast(<>Disk <HL>{data.name}</HL> created</>) // prettier-ignore
onSuccess?.(data)
onDismiss()
Expand Down
9 changes: 5 additions & 4 deletions app/forms/firewall-rules-common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { useController, useForm, type Control } from 'react-hook-form'
import { Badge } from '@oxide/design-system/ui'

import {
usePrefetchedApiQuery,
apiq,
usePrefetchedQuery,
type ApiError,
type Instance,
type Vpc,
Expand Down Expand Up @@ -109,13 +110,13 @@ const TargetAndHostFilterSubform = ({
// prefetchedApiQueries below are prefetched in firewall-rules-create and -edit
const {
data: { items: instances },
} = usePrefetchedApiQuery('instanceList', { query: { project, limit: ALL_ISH } })
} = usePrefetchedQuery(apiq('instanceList', { query: { project, limit: ALL_ISH } }))
const {
data: { items: vpcs },
} = usePrefetchedApiQuery('vpcList', { query: { project, limit: ALL_ISH } })
} = usePrefetchedQuery(apiq('vpcList', { query: { project, limit: ALL_ISH } }))
const {
data: { items: vpcSubnets },
} = usePrefetchedApiQuery('vpcSubnetList', { query: { project, vpc, limit: ALL_ISH } })
} = usePrefetchedQuery(apiq('vpcSubnetList', { query: { project, vpc, limit: ALL_ISH } }))

const subform = useForm({ defaultValues: targetAndHostDefaultValues })
const field = useController({ name: `${sectionType}s`, control }).field
Expand Down
23 changes: 11 additions & 12 deletions app/forms/firewall-rules-create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { useForm } from 'react-hook-form'
import { useNavigate, useParams, type LoaderFunctionArgs } from 'react-router'

import {
apiQueryClient,
apiq,
firewallRuleGetToPut,
queryClient,
useApiMutation,
useApiQueryClient,
usePrefetchedApiQuery,
usePrefetchedQuery,
type VpcFirewallRule,
} from '@oxide/api'

Expand Down Expand Up @@ -61,34 +61,33 @@ const ruleToValues = (rule: VpcFirewallRule): FirewallRuleValues => ({
export async function clientLoader({ params }: LoaderFunctionArgs) {
const { project, vpc } = getVpcSelector(params)
await Promise.all([
apiQueryClient.prefetchQuery('vpcFirewallRulesView', { query: { project, vpc } }),
apiQueryClient.prefetchQuery('instanceList', { query: { project, limit: ALL_ISH } }),
apiQueryClient.prefetchQuery('vpcList', { query: { project, limit: ALL_ISH } }),
apiQueryClient.prefetchQuery('vpcSubnetList', {
query: { project, vpc, limit: ALL_ISH },
}),
queryClient.prefetchQuery(apiq('vpcFirewallRulesView', { query: { project, vpc } })),
queryClient.prefetchQuery(apiq('instanceList', { query: { project, limit: ALL_ISH } })),
queryClient.prefetchQuery(apiq('vpcList', { query: { project, limit: ALL_ISH } })),
queryClient.prefetchQuery(
apiq('vpcSubnetList', { query: { project, vpc, limit: ALL_ISH } })
),
])

return null
}

export default function CreateFirewallRuleForm() {
const vpcSelector = useVpcSelector()
const queryClient = useApiQueryClient()

const navigate = useNavigate()
const onDismiss = () => navigate(pb.vpcFirewallRules(vpcSelector))

const updateRules = useApiMutation('vpcFirewallRulesUpdate', {
onSuccess(updatedRules) {
const newRule = updatedRules.rules[updatedRules.rules.length - 1]
queryClient.invalidateQueries('vpcFirewallRulesView')
queryClient.invalidateEndpoint('vpcFirewallRulesView')
addToast(<>Firewall rule <HL>{newRule.name}</HL> created</>) // prettier-ignore
navigate(pb.vpcFirewallRules(vpcSelector))
},
})

const { data } = usePrefetchedApiQuery('vpcFirewallRulesView', { query: vpcSelector })
const { data } = usePrefetchedQuery(apiq('vpcFirewallRulesView', { query: vpcSelector }))
const existingRules = data.rules

// The :rule path param is optional. If it is present, we are creating a
Expand Down
Loading
Loading