Skip to content

Commit

Permalink
chore: Migrate useSelfHostedSeatsAndLicense to TS Query V5 (#3577)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholas-codecov authored Dec 30, 2024
1 parent ab1389b commit ccf9ea6
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
QueryClientProvider as QueryClientProviderV5,
QueryClient as QueryClientV5,
useQuery as useQueryV5,
} from '@tanstack/react-queryV5'
import { renderHook, waitFor } from '@testing-library/react'
import { graphql, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { type MockInstance } from 'vitest'

import { useSelfHostedSeatsAndLicense } from './useSelfHostedSeatsAndLicense'
import { SelfHostedSeatsAndLicenseQueryOpts } from './SelfHostedSeatsAndLicenseQueryOpts'

const mockSelfHostedLicense = {
config: {
Expand All @@ -16,12 +20,14 @@ const mockSelfHostedLicense = {

const mockUnsuccessfulParseError = {}

const queryClient = new QueryClient({
const queryClientV5 = new QueryClientV5({
defaultOptions: { queries: { retry: false } },
})

const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
<QueryClientProviderV5 client={queryClientV5}>
{children}
</QueryClientProviderV5>
)

const server = setupServer()
Expand All @@ -30,7 +36,7 @@ beforeAll(() => {
})

afterEach(() => {
queryClient.clear()
queryClientV5.clear()
server.resetHandlers()
})

Expand Down Expand Up @@ -61,7 +67,10 @@ describe('useSelfHostedSeatsAndLicense', () => {
it('returns the license details', async () => {
setup({})
const { result } = renderHook(
() => useSelfHostedSeatsAndLicense({ provider: 'gh' }),
() =>
useQueryV5(
SelfHostedSeatsAndLicenseQueryOpts({ provider: 'gh' })
),
{ wrapper }
)

Expand Down Expand Up @@ -91,7 +100,8 @@ describe('useSelfHostedSeatsAndLicense', () => {
it('throws a 404', async () => {
setup({ isUnsuccessfulParseError: true })
const { result } = renderHook(
() => useSelfHostedSeatsAndLicense({ provider: 'gh' }),
() =>
useQueryV5(SelfHostedSeatsAndLicenseQueryOpts({ provider: 'gh' })),
{ wrapper }
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useQuery } from '@tanstack/react-query'
import { queryOptions as queryOptionsV5 } from '@tanstack/react-queryV5'
import { z } from 'zod'

import Api from 'shared/api'
import { rejectNetworkError } from 'shared/api/helpers'

export const SelfHostedSeatsAndLicenseSchema = z
.object({
Expand All @@ -19,32 +20,24 @@ export const SelfHostedSeatsAndLicenseSchema = z
})
.nullable()

export interface UseSelfHostedSeatsAndLicenseArgs {
provider: string
opts?: {
enabled: boolean
}
}

const query = `
query SelfHostedSeatsAndLicense {
config {
seatsUsed
seatsLimit
selfHostedLicense {
expirationDate
}
const query = `query SelfHostedSeatsAndLicense {
config {
seatsUsed
seatsLimit
selfHostedLicense {
expirationDate
}
}
`
}`

export interface SelfHostedSeatsAndLicenseQueryArgs {
provider: string
}

export const useSelfHostedSeatsAndLicense = ({
export const SelfHostedSeatsAndLicenseQueryOpts = ({
provider,
opts = {
enabled: true,
},
}: UseSelfHostedSeatsAndLicenseArgs) =>
useQuery({
}: SelfHostedSeatsAndLicenseQueryArgs) =>
queryOptionsV5({
queryKey: ['SelfHostedSeatsAndLicense', provider, query],
queryFn: ({ signal }) =>
Api.graphql({
Expand All @@ -55,13 +48,14 @@ export const useSelfHostedSeatsAndLicense = ({
const parsedRes = SelfHostedSeatsAndLicenseSchema.safeParse(res?.data)

if (!parsedRes.success) {
return Promise.reject({
return rejectNetworkError({
status: 404,
data: null,
data: {},
dev: `SelfHostedSeatsAndLicenseQueryOpts - 404 Failed to parse`,
error: parsedRes.error,
})
}

return parsedRes.data?.config ?? null
}),
...(!!opts && opts),
})
1 change: 0 additions & 1 deletion src/services/selfHosted/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './useSelfHostedCurrentUser'
export * from './useSelfHostedSeatsAndLicense'
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
QueryClientProvider as QueryClientProviderV5,
QueryClient as QueryClientV5,
} from '@tanstack/react-queryV5'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { graphql, HttpResponse } from 'msw'
Expand All @@ -15,45 +19,54 @@ vi.mock('config')
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, suspense: true } },
})
const server = setupServer()

const queryClientV5 = new QueryClientV5({
defaultOptions: { queries: { retry: false } },
})

const wrapper =
(initialEntries = ['/gh/test-org']): React.FC<React.PropsWithChildren> =>
({ children }) => (
<QueryClientProviderV5 client={queryClientV5}>
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={initialEntries}>
<Suspense fallback={<p>Loading</p>}>
<Route path="/:provider/:owner">{children}</Route>
</Suspense>
</MemoryRouter>
</QueryClientProvider>
</QueryClientProviderV5>
)

const server = setupServer()
beforeAll(() => {
server.listen()
})

afterEach(() => {
queryClient.clear()
queryClientV5.clear()
server.resetHandlers()
})

afterAll(() => {
server.close()
})

const wrapper =
(initialEntries = ['/gh/test-org']) =>
({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={initialEntries}>
<Suspense fallback={<p>Loading</p>}>
<Route path="/:provider/:owner">{children}</Route>
</Suspense>
</MemoryRouter>
</QueryClientProvider>
)
interface SetupArgs {
isUndefined?: boolean
seatsLimit?: number | null
seatsUsed?: number | null
expirationDate?: string | null
}

describe('SelfHostedLicenseExpiration', () => {
function setup({
isUndefined = false,
seatsLimit = 50,
seatsUsed = 10,
expirationDate = '2020-05-09T00:00:00',
}: {
isUndefined?: boolean
seatsLimit?: number | null
seatsUsed?: number | null
expirationDate?: string | null
}) {
}: SetupArgs) {
const user = userEvent.setup({ delay: null })

server.use(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5'
import { differenceInCalendarDays } from 'date-fns'
import { useState } from 'react'
import { useParams } from 'react-router-dom'

import config from 'config'

import { useSelfHostedSeatsAndLicense } from 'services/selfHosted/useSelfHostedSeatsAndLicense'
import { SelfHostedSeatsAndLicenseQueryOpts } from 'services/selfHosted/SelfHostedSeatsAndLicenseQueryOpts'
import { Provider } from 'shared/api/helpers'
import Banner from 'ui/Banner'
import Button from 'ui/Button'
Expand Down Expand Up @@ -56,31 +57,25 @@ interface URLParams {
const SelfHostedLicenseExpiration = () => {
const { provider } = useParams<URLParams>()
const [isModalOpen, setIsModalOpen] = useState(false)
const isSelfHosted = !!config.IS_SELF_HOSTED
const isDedicatedNamespace = !!config.IS_DEDICATED_NAMESPACE
const { data } = useSelfHostedSeatsAndLicense({
provider,
opts: { enabled: !!provider && isSelfHosted && isDedicatedNamespace },
})
const { data } = useSuspenseQueryV5(
SelfHostedSeatsAndLicenseQueryOpts({
provider,
})
)

const licenseExpirationDate = data?.selfHostedLicense?.expirationDate
const seatsUsed = data?.seatsUsed
const seatsLimit = data?.seatsLimit

if (
!isSelfHosted ||
!isDedicatedNamespace ||
!licenseExpirationDate ||
!seatsUsed ||
!seatsLimit
) {
if (!licenseExpirationDate || !seatsUsed || !seatsLimit) {
return null
}

const dateDiff = differenceInCalendarDays(
new Date(licenseExpirationDate),
new Date()
)

const isSeatsLimitReached = seatsUsed === seatsLimit
const isLicenseExpired = dateDiff < 0
const isLicenseExpiringWithin30Days = dateDiff < 31 && dateDiff >= 0
Expand Down Expand Up @@ -122,4 +117,18 @@ const SelfHostedLicenseExpiration = () => {
)
}

export default SelfHostedLicenseExpiration
// This wrapper is just so we don't make a request to the API if we're not on a
// self-hosted instance, this is because we're useSuspenseQuery is not
// toggle'ble through a `enabled` field like useQuery
function SelfHostedLicenseExpirationWrapper() {
const isSelfHosted = !!config.IS_SELF_HOSTED
const isDedicatedNamespace = !!config.IS_DEDICATED_NAMESPACE

if (!isSelfHosted || !isDedicatedNamespace) {
return null
}

return <SelfHostedLicenseExpiration />
}

export default SelfHostedLicenseExpirationWrapper

0 comments on commit ccf9ea6

Please sign in to comment.