diff --git a/src/layouts/Header/components/AdminLink/AdminLink.test.tsx b/src/layouts/Header/components/AdminLink/AdminLink.test.tsx index 89f65a1f7c..9709ec0fbe 100644 --- a/src/layouts/Header/components/AdminLink/AdminLink.test.tsx +++ b/src/layouts/Header/components/AdminLink/AdminLink.test.tsx @@ -1,11 +1,19 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen } from '@testing-library/react' +import { + QueryClientProvider as QueryClientProviderV5, + QueryClient as QueryClientV5, +} from '@tanstack/react-queryV5' +import { render, screen, waitFor } from '@testing-library/react' import { http, HttpResponse } from 'msw' import { setupServer } from 'msw/node' +import { Suspense } from 'react' import { MemoryRouter, Route } from 'react-router-dom' import AdminLink from './AdminLink' +const queryClientV5 = new QueryClientV5({ + defaultOptions: { queries: { retry: false } }, +}) + const wrapper: ({ initialEntries, }: { @@ -13,27 +21,23 @@ const wrapper: ({ }) => React.FC = ({ initialEntries = '/gh' }) => ({ children }) => ( - + - {children} + Loading}>{children} - + ) -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}) - const server = setupServer() beforeAll(() => { server.listen() }) -beforeEach(() => { +afterEach(() => { + queryClientV5.clear() server.resetHandlers() - queryClient.clear() }) afterAll(() => { @@ -69,7 +73,7 @@ describe('AdminLink', () => { }) describe('user is not an admin', () => { - it('renders nothing', () => { + it('renders nothing', async () => { setup({ activated: false, email: 'codecov@codecov.io', @@ -80,7 +84,7 @@ describe('AdminLink', () => { }) const { container } = render(, { wrapper: wrapper({}) }) - expect(container).toBeEmptyDOMElement() + await waitFor(() => expect(container).toBeEmptyDOMElement()) }) }) }) diff --git a/src/layouts/Header/components/AdminLink/AdminLink.tsx b/src/layouts/Header/components/AdminLink/AdminLink.tsx index 82398fbcf4..40f0e1ed7d 100644 --- a/src/layouts/Header/components/AdminLink/AdminLink.tsx +++ b/src/layouts/Header/components/AdminLink/AdminLink.tsx @@ -1,9 +1,19 @@ -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' +import { useParams } from 'react-router' + +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import A from 'ui/A' import Icon from 'ui/Icon' +interface URLParams { + provider: string +} + function AdminLink() { - const { data: user } = useSelfHostedCurrentUser() + const { provider } = useParams() + const { data: user } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) if (!user?.isAdmin) { return null diff --git a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/ActivationBanner.jsx b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/ActivationBanner.jsx index 06a73f7db1..478aac9c06 100644 --- a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/ActivationBanner.jsx +++ b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/ActivationBanner.jsx @@ -1,8 +1,7 @@ -import { useQueryClient } from '@tanstack/react-query' import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' import { useParams } from 'react-router' -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import { SelfHostedSeatsConfigQueryOpts } from 'services/selfHosted/SelfHostedSeatsConfigQueryOpts' import A from 'ui/A' import Banner from 'ui/Banner' @@ -40,11 +39,13 @@ function canChangeActivation({ seatConfig, currentUser }) { function ActivationBanner() { const { provider } = useParams() - const queryClient = useQueryClient() - const { data: currentUser } = useSelfHostedCurrentUser() + const { data: seatConfig } = useSuspenseQueryV5( SelfHostedSeatsConfigQueryOpts({ provider }) ) + const { data: currentUser } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) const { canChange, displaySeatMsg } = canChangeActivation({ seatConfig, @@ -52,7 +53,6 @@ function ActivationBanner() { }) const { mutate } = useSelfActivationMutation({ - queryClient, canChange, }) diff --git a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.js b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.js index 4e3e26e4a4..122e4fabba 100644 --- a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.js +++ b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.js @@ -1,15 +1,18 @@ -import { useMutation } from '@tanstack/react-query' -import { useQueryClient as useQueryClientV5 } from '@tanstack/react-queryV5' +import { + useMutation as useMutationV5, + useQueryClient as useQueryClientV5, +} from '@tanstack/react-queryV5' import { useParams } from 'react-router-dom' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import { SelfHostedSeatsConfigQueryOpts } from 'services/selfHosted/SelfHostedSeatsConfigQueryOpts' import Api from 'shared/api' -export const useSelfActivationMutation = ({ queryClient, canChange }) => { +export const useSelfActivationMutation = ({ canChange }) => { const { provider } = useParams() const queryClientV5 = useQueryClientV5() - return useMutation({ + return useMutationV5({ mutationFn: (activated) => { if (canChange) { return Api.patch({ @@ -20,34 +23,37 @@ export const useSelfActivationMutation = ({ queryClient, canChange }) => { } }, onMutate: async (activated) => { - await queryClient.cancelQueries(['SelfHostedCurrentUser']) + // Cancel any in-flight queries + await queryClientV5.cancelQueries({ + queryKey: SelfHostedCurrentUserQueryOpts({ provider }).queryKey, + }) await queryClientV5.cancelQueries({ queryKey: SelfHostedSeatsConfigQueryOpts({ provider }).queryKey, }) - const prevUser = queryClient.getQueryData(['SelfHostedCurrentUser']) + // Get the current data before the mutation const prevSeat = queryClientV5.getQueryData( SelfHostedSeatsConfigQueryOpts({ provider }).queryKey ) + const prevUser = queryClientV5.getQueryData( + SelfHostedCurrentUserQueryOpts({ provider }).queryKey + ) if (canChange) { - queryClient.setQueryData(['SelfHostedCurrentUser'], (user) => ({ - ...user, - activated, - })) + // Optimistically update the data in the query client + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider }).queryKey, + (user) => ({ ...user, activated }) + ) queryClientV5.setQueryData( SelfHostedSeatsConfigQueryOpts({ provider }).queryKey, (seats) => { - const seatsUsed = seats?.data?.config?.seatsUsed - return { - data: { - config: { - ...seats?.data?.config, - seatsUsed: activated ? seatsUsed + 1 : seatsUsed - 1, - }, - }, - } + const seatsUsed = activated + ? seats?.data?.config?.seatsUsed + 1 + : seats?.data?.config?.seatsUsed - 1 + + return { data: { config: { ...seats?.data?.config, seatsUsed } } } } ) } @@ -55,14 +61,21 @@ export const useSelfActivationMutation = ({ queryClient, canChange }) => { return { prevUser, prevSeat } }, onError: (_err, _activated, context) => { - queryClient.setQueryData(['SelfHostedCurrentUser'], context.prevUser) + // Rollback the data if the mutation fails + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider }).queryKey, + context.prevUser + ) queryClientV5.setQueryData( SelfHostedSeatsConfigQueryOpts({ provider }).queryKey, context.prevSeat ) }, onSettled: () => { - queryClient.invalidateQueries(['SelfHostedCurrentUser']) + // Invalidate the queries to ensure they are re-fetched + queryClientV5.invalidateQueries({ + queryKey: SelfHostedCurrentUserQueryOpts({ provider }).queryKey, + }) queryClientV5.invalidateQueries( SelfHostedSeatsConfigQueryOpts({ provider }).queryKey ) diff --git a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.test.jsx b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.test.jsx index eec7be64cf..835dfc1e7d 100644 --- a/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.test.jsx +++ b/src/pages/AccountSettings/tabs/Profile/ActivationBanner/useSelfActivationMutation.test.jsx @@ -1,4 +1,3 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryClientProvider as QueryClientProviderV5, QueryClient as QueryClientV5, @@ -9,15 +8,11 @@ import { setupServer } from 'msw/node' import { MemoryRouter, Route } from 'react-router-dom' import { act } from 'react-test-renderer' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import { SelfHostedSeatsConfigQueryOpts } from 'services/selfHosted/SelfHostedSeatsConfigQueryOpts' import { useSelfActivationMutation } from './useSelfActivationMutation' -const queryClient = new QueryClient({ - logger: { error: () => {} }, - defaultOptions: { retry: false }, -}) - const queryClientV5 = new QueryClientV5({ logger: { error: () => {} }, defaultOptions: { retry: false }, @@ -27,11 +22,9 @@ const wrapper = (initialEntries = '/gh') => ({ children }) => ( - - - {children} - - + + {children} + ) @@ -41,7 +34,6 @@ beforeAll(() => { }) afterEach(() => { - queryClient.clear() queryClientV5.clear() server.resetHandlers() }) @@ -59,9 +51,10 @@ describe('useSelfActivationMutation', () => { { data: { config: { seatsUsed: 0, seatsLimit: 10 } } } ) - queryClient.setQueryData(['SelfHostedCurrentUser'], { - activated: false, - }) + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey, + { activated: false } + ) const mockUser = { activated: false, @@ -83,7 +76,7 @@ describe('useSelfActivationMutation', () => { it('updates query data', async () => { const { result } = renderHook( - () => useSelfActivationMutation({ queryClient, canChange: true }), + () => useSelfActivationMutation({ canChange: true }), { wrapper: wrapper() } ) @@ -101,7 +94,9 @@ describe('useSelfActivationMutation', () => { await waitFor(() => expect( - queryClient.getQueryData(['SelfHostedCurrentUser']) + queryClientV5.getQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey + ) ).toStrictEqual({ activated: true }) ) }) @@ -114,9 +109,10 @@ describe('useSelfActivationMutation', () => { { data: { config: { seatsUsed: 1, seatsLimit: 10 } } } ) - queryClient.setQueryData(['SelfHostedCurrentUser'], { - activated: true, - }) + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey, + { activated: true } + ) const mockUser = { activated: true, @@ -137,7 +133,7 @@ describe('useSelfActivationMutation', () => { it('updates query data', async () => { const { result } = renderHook( - () => useSelfActivationMutation({ queryClient, canChange: true }), + () => useSelfActivationMutation({ canChange: true }), { wrapper: wrapper() } ) @@ -155,7 +151,9 @@ describe('useSelfActivationMutation', () => { await waitFor(() => expect( - queryClient.getQueryData(['SelfHostedCurrentUser']) + queryClientV5.getQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey + ) ).toStrictEqual({ activated: false }) ) }) @@ -168,9 +166,10 @@ describe('useSelfActivationMutation', () => { { data: { config: { seatsUsed: 10, seatsLimit: 10 } } } ) - queryClient.setQueryData(['SelfHostedCurrentUser'], { - activated: false, - }) + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey, + { activated: false } + ) const mockUser = { activated: false, @@ -192,7 +191,7 @@ describe('useSelfActivationMutation', () => { it('does not change the query data', async () => { const { result } = renderHook( - () => useSelfActivationMutation({ queryClient, canChange: false }), + () => useSelfActivationMutation({ canChange: false }), { wrapper: wrapper() } ) @@ -210,7 +209,9 @@ describe('useSelfActivationMutation', () => { await waitFor(() => expect( - queryClient.getQueryData(['SelfHostedCurrentUser']) + queryClientV5.getQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey + ) ).toStrictEqual({ activated: false }) ) }) @@ -224,9 +225,10 @@ describe('useSelfActivationMutation', () => { { data: { config: { seatsUsed: 1, seatsLimit: 10 } } } ) - queryClient.setQueryData(['SelfHostedCurrentUser'], { - activated: true, - }) + queryClientV5.setQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey, + { activated: true } + ) const mockUser = { activated: true, @@ -244,7 +246,7 @@ describe('useSelfActivationMutation', () => { it('reverts to old query data', async () => { const { result } = renderHook( - () => useSelfActivationMutation({ queryClient, canChange: true }), + () => useSelfActivationMutation({ canChange: true }), { wrapper: wrapper() } ) @@ -263,7 +265,9 @@ describe('useSelfActivationMutation', () => { await waitFor(() => result.current.isError) await waitFor(() => expect( - queryClient.getQueryData(['SelfHostedCurrentUser']) + queryClientV5.getQueryData( + SelfHostedCurrentUserQueryOpts({ provider: 'gh' }).queryKey + ) ).toStrictEqual({ activated: true }) ) }) diff --git a/src/pages/AccountSettings/tabs/Profile/Profile.jsx b/src/pages/AccountSettings/tabs/Profile/Profile.jsx index 8122f9ecd8..46ede595db 100644 --- a/src/pages/AccountSettings/tabs/Profile/Profile.jsx +++ b/src/pages/AccountSettings/tabs/Profile/Profile.jsx @@ -1,7 +1,8 @@ +import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' import PropTypes from 'prop-types' import { Redirect } from 'react-router-dom' -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import ActivationBanner from './ActivationBanner' import AdminBanner from './AdminBanner' @@ -10,7 +11,9 @@ import NameEmailCard from './NameEmailCard' function Profile({ provider, owner }) { const yamlTab = `/account/${provider}/${owner}/yaml/` - const { data: currentUser } = useSelfHostedCurrentUser() + const { data: currentUser } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) const isPersonalSettings = currentUser?.username?.toLowerCase() === owner?.toLowerCase() diff --git a/src/pages/AdminSettings/AdminSettings.jsx b/src/pages/AdminSettings/AdminSettings.jsx index f506424d80..8b007559f8 100644 --- a/src/pages/AdminSettings/AdminSettings.jsx +++ b/src/pages/AdminSettings/AdminSettings.jsx @@ -1,10 +1,11 @@ +import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' import { lazy, Suspense } from 'react' import { Redirect, Switch, useParams } from 'react-router-dom' import { SentryRoute } from 'sentry' import SidebarLayout from 'layouts/SidebarLayout' -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import LoadingLogo from 'ui/LoadingLogo' import Spinner from 'ui/Spinner' @@ -27,7 +28,9 @@ const SpinnerLoader = ( function AdminSettings() { const { provider } = useParams() - const { data, isLoading } = useSelfHostedCurrentUser() + const { data, isLoading } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) const redirectTo = `/admin/${provider}/access` diff --git a/src/pages/AdminSettings/AdminSettings.test.jsx b/src/pages/AdminSettings/AdminSettings.test.jsx index 698d0659de..302dde9766 100644 --- a/src/pages/AdminSettings/AdminSettings.test.jsx +++ b/src/pages/AdminSettings/AdminSettings.test.jsx @@ -1,7 +1,12 @@ 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 { graphql, http, HttpResponse } from 'msw' import { setupServer } from 'msw/node' +import { Suspense } from 'react' import { MemoryRouter, Route } from 'react-router-dom' import AdminSettings from './AdminSettings' @@ -21,23 +26,30 @@ const user = { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }) +const queryClientV5 = new QueryClientV5({ + defaultOptions: { queries: { retry: false } }, +}) let testLocation const wrapper = ({ initialEntries, path }) => ({ children }) => ( - - - {children} - { - testLocation = location - return null - }} - /> - - + + + + Loading}> + {children} + { + testLocation = location + return null + }} + /> + + + + ) const server = setupServer() @@ -45,9 +57,10 @@ beforeAll(() => { server.listen() }) -beforeEach(() => { - server.resetHandlers() +afterEach(() => { queryClient.clear() + queryClientV5.clear() + server.resetHandlers() }) afterAll(() => { diff --git a/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx b/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx index 289d6b2aa5..76c4a59f3f 100644 --- a/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx +++ b/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx @@ -13,11 +13,8 @@ import { vi } from 'vitest' import ActivationRequiredSelfHosted from './ActivationRequiredSelfHosted' const queryClient = new QueryClient({ - defaultOptions: { - queries: { retry: false, suspense: false }, - }, + defaultOptions: { queries: { retry: false, suspense: false } }, }) - const queryClientV5 = new QueryClientV5({ defaultOptions: { queries: { retry: false } }, }) @@ -35,7 +32,6 @@ const wrapper: React.FC = ({ children }) => ( ) const server = setupServer() - beforeAll(() => { server.listen({ onUnhandledRequest: 'warn' }) console.error = () => {} diff --git a/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx b/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx index 3cb07b7a67..8f0465d581 100644 --- a/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx +++ b/src/pages/RepoPage/ActivationAlert/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx @@ -2,7 +2,7 @@ import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' import { useParams } from 'react-router' import upsideDownUmbrella from 'layouts/shared/NetworkErrorBoundary/assets/error-upsidedown-umbrella.svg' -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import { SelfHostedSeatsConfigQueryOpts } from 'services/selfHosted/SelfHostedSeatsConfigQueryOpts' import A from 'ui/A' import Button from 'ui/Button' @@ -62,10 +62,12 @@ interface URLParams { function ActivationRequiredSelfHosted() { const { provider } = useParams() - const { data } = useSelfHostedCurrentUser() const { data: selfHostedSeats } = useSuspenseQueryV5( SelfHostedSeatsConfigQueryOpts({ provider }) ) + const { data } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) const hasSelfHostedSeats = selfHostedSeats?.seatsUsed && diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx index 93640024e0..4396d0a270 100644 --- a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx +++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.test.tsx @@ -17,7 +17,6 @@ const queryClient = new QueryClient({ const queryClientV5 = new QueryClientV5({ defaultOptions: { queries: { retry: false } }, }) - const wrapper: React.FC = ({ children }) => ( diff --git a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx index b9f4ee3a80..10b8b6a291 100644 --- a/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx +++ b/src/pages/RepoPage/CoverageOnboarding/ActivationBanner/ActivationRequiredSelfHosted/ActivationRequiredSelfHosted.tsx @@ -1,7 +1,7 @@ import { useSuspenseQuery as useSuspenseQueryV5 } from '@tanstack/react-queryV5' import { useParams } from 'react-router' -import { useSelfHostedCurrentUser } from 'services/selfHosted' +import { SelfHostedCurrentUserQueryOpts } from 'services/selfHosted/SelfHostedCurrentUserQueryOpts' import { SelfHostedSeatsConfigQueryOpts } from 'services/selfHosted/SelfHostedSeatsConfigQueryOpts' import A from 'ui/A' import Banner from 'ui/Banner' @@ -67,10 +67,12 @@ interface URLParams { function ActivationRequiredSelfHosted() { const { provider } = useParams() - const { data } = useSelfHostedCurrentUser() const { data: selfHostedSeats } = useSuspenseQueryV5( SelfHostedSeatsConfigQueryOpts({ provider }) ) + const { data } = useSuspenseQueryV5( + SelfHostedCurrentUserQueryOpts({ provider }) + ) const hasSelfHostedSeats = selfHostedSeats?.seatsUsed && diff --git a/src/services/repos/useReposTeam.test.tsx b/src/services/repos/ReposTeamQueryOpts.test.tsx similarity index 77% rename from src/services/repos/useReposTeam.test.tsx rename to src/services/repos/ReposTeamQueryOpts.test.tsx index b2e1e4d839..366b5aaa92 100644 --- a/src/services/repos/useReposTeam.test.tsx +++ b/src/services/repos/ReposTeamQueryOpts.test.tsx @@ -1,21 +1,25 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { + QueryClientProvider as QueryClientProviderV5, + QueryClient as QueryClientV5, + useInfiniteQuery as useInfiniteQueryV5, +} from '@tanstack/react-queryV5' import { renderHook, waitFor } from '@testing-library/react' import { graphql, HttpResponse } from 'msw' import { setupServer } from 'msw/node' import { MemoryRouter, Route } from 'react-router-dom' import { MockInstance } from 'vitest' -import { useReposTeam } from './useReposTeam' +import { ReposTeamQueryOpts } from './ReposTeamQueryOpts' -const queryClient = new QueryClient({ +const queryClientV5 = new QueryClientV5({ defaultOptions: { queries: { retry: false } }, }) const wrapper: React.FC = ({ children }) => ( - + {children} - + ) const repo1 = { @@ -88,8 +92,8 @@ beforeAll(() => { }) beforeEach(() => { + queryClientV5.clear() server.resetHandlers() - queryClient.clear() }) afterAll(() => { @@ -129,7 +133,14 @@ describe('useReposTeam', () => { it('returns repositories', async () => { setup() const { result } = renderHook( - () => useReposTeam({ activated: true, owner: 'codecov' }), + () => + useInfiniteQueryV5( + ReposTeamQueryOpts({ + provider: 'gh', + activated: true, + owner: 'codecov', + }) + ), { wrapper } ) @@ -145,7 +156,7 @@ describe('useReposTeam', () => { }, }, ], - pageParams: [undefined], + pageParams: [''], }) ) }) @@ -155,7 +166,15 @@ describe('useReposTeam', () => { it('returns repositories of the user', async () => { setup() const { result } = renderHook( - () => useReposTeam({ owner: 'codecov', activated: true, first: 2 }), + () => + useInfiniteQueryV5( + ReposTeamQueryOpts({ + provider: 'gh', + activated: true, + owner: 'codecov', + first: 2, + }) + ), { wrapper } ) @@ -178,13 +197,10 @@ describe('useReposTeam', () => { { repos: [repo3, repo4], isCurrentUserPartOfOrg: true, - pageInfo: { - hasNextPage: false, - endCursor: 'aa', - }, + pageInfo: { hasNextPage: false, endCursor: 'aa' }, }, ], - pageParams: [undefined, 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA='], + pageParams: ['', 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA='], }) ) }) @@ -204,13 +220,24 @@ describe('useReposTeam', () => { it('throws an error', async () => { setup({ invalidResponse: true }) const { result } = renderHook( - () => useReposTeam({ owner: 'codecov', activated: true, first: 2 }), + () => + useInfiniteQueryV5( + ReposTeamQueryOpts({ + provider: 'gh', + activated: true, + owner: 'codecov', + first: 2, + }) + ), { wrapper } ) await waitFor(() => expect(result.current.error).toEqual( - expect.objectContaining({ status: 404 }) + expect.objectContaining({ + status: 404, + dev: 'ReposTeamQueryOpts - 404 Failed to parse schema', + }) ) ) }) diff --git a/src/services/repos/useReposTeam.tsx b/src/services/repos/ReposTeamQueryOpts.tsx similarity index 75% rename from src/services/repos/useReposTeam.tsx rename to src/services/repos/ReposTeamQueryOpts.tsx index 5dcebb842c..a5008a348d 100644 --- a/src/services/repos/useReposTeam.tsx +++ b/src/services/repos/ReposTeamQueryOpts.tsx @@ -1,11 +1,16 @@ -import { useInfiniteQuery } from '@tanstack/react-query' -import { useParams } from 'react-router-dom' +import { infiniteQueryOptions as infiniteQueryOptionsV5 } from '@tanstack/react-queryV5' import { z } from 'zod' import Api from 'shared/api' +import { rejectNetworkError } from 'shared/api/helpers' import { mapEdges } from 'shared/utils/graphql' -import { orderingOptions } from './config' +import { + nonActiveOrderingOptions, + OrderingDirection, + orderingOptions, + TeamOrdering, +} from './orderingOptions' const RepositorySchema = z .object({ @@ -27,7 +32,7 @@ const RepositorySchema = z }) .nullable() -export type Repository = z.infer +type Repository = z.infer const RequestSchema = z.object({ owner: z @@ -92,7 +97,8 @@ const query = `query GetReposTeam( } }` -interface UseReposTeamArgs { +interface ReposTeamQueryArgs { + provider: string activated?: boolean term?: string owner: string @@ -104,16 +110,15 @@ interface UseReposTeamArgs { repoNames?: string[] } -export function useReposTeam({ +function ReposTeamQueryOpts({ + provider, activated, term, owner, sortItem = orderingOptions[0], first = 20, repoNames, - ...options -}: UseReposTeamArgs) { - const { provider } = useParams<{ provider: string }>() +}: ReposTeamQueryArgs) { const variables = { filters: { activated, term, repoNames }, ordering: sortItem?.ordering, @@ -121,7 +126,7 @@ export function useReposTeam({ first, } - return useInfiniteQuery({ + return infiniteQueryOptionsV5({ queryKey: ['GetReposTeam', provider, variables, owner], queryFn: ({ pageParam, signal }) => { return Api.graphql({ @@ -137,9 +142,11 @@ export function useReposTeam({ const parsedRes = RequestSchema.safeParse(res?.data) if (!parsedRes.success) { - return Promise.reject({ + return rejectNetworkError({ status: 404, - data: null, + data: {}, + dev: 'ReposTeamQueryOpts - 404 Failed to parse schema', + error: parsedRes.error, }) } @@ -152,8 +159,21 @@ export function useReposTeam({ } }) }, - getNextPageParam: (data) => - data?.pageInfo?.hasNextPage ? data.pageInfo.endCursor : undefined, - ...options, + initialPageParam: '', + getNextPageParam: (data) => { + if (data?.pageInfo?.hasNextPage) { + return data.pageInfo.endCursor + } + return null + }, }) } + +export { + type Repository, + orderingOptions, + nonActiveOrderingOptions, + OrderingDirection, + ReposTeamQueryOpts, + TeamOrdering, +} diff --git a/src/services/repos/index.ts b/src/services/repos/index.ts index d541d18530..ae1f8c951b 100644 --- a/src/services/repos/index.ts +++ b/src/services/repos/index.ts @@ -1,3 +1,2 @@ export * from './useRepos' -export * from './config' -export * from './useReposTeam' +export * from './orderingOptions' diff --git a/src/services/repos/config.ts b/src/services/repos/orderingOptions.ts similarity index 100% rename from src/services/repos/config.ts rename to src/services/repos/orderingOptions.ts diff --git a/src/services/repos/useRepos.tsx b/src/services/repos/useRepos.tsx index ec15bd3007..73ac854710 100644 --- a/src/services/repos/useRepos.tsx +++ b/src/services/repos/useRepos.tsx @@ -5,7 +5,7 @@ import { RepositoryConfigSchema } from 'services/repo/useRepoConfig' import Api from 'shared/api' import { mapEdges } from 'shared/utils/graphql' -import { orderingOptions } from './config' +import { orderingOptions } from './orderingOptions' const RepositorySchema = z .object({ diff --git a/src/services/selfHosted/useSelfHostedCurrentUser.test.tsx b/src/services/selfHosted/SelfHostedCurrentUserQueryOpts.test.tsx similarity index 64% rename from src/services/selfHosted/useSelfHostedCurrentUser.test.tsx rename to src/services/selfHosted/SelfHostedCurrentUserQueryOpts.test.tsx index 0cbb86c020..7b6864aaff 100644 --- a/src/services/selfHosted/useSelfHostedCurrentUser.test.tsx +++ b/src/services/selfHosted/SelfHostedCurrentUserQueryOpts.test.tsx @@ -1,11 +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 { http, HttpResponse } from 'msw' import { setupServer } from 'msw/node' import { PropsWithChildren } from 'react' -import { MemoryRouter, Route } from 'react-router-dom' -import { useSelfHostedCurrentUser } from './useSelfHostedCurrentUser' +import { SelfHostedCurrentUserQueryOpts } from './SelfHostedCurrentUserQueryOpts' const user = { activated: false, @@ -16,15 +19,13 @@ const user = { username: 'codecov', } -const queryClient = new QueryClient({ +const queryClientV5 = new QueryClientV5({ defaultOptions: { queries: { retry: false } }, }) const wrapper: React.FC = ({ children }) => ( - - - {children} - - + + {children} + ) const server = setupServer() @@ -32,9 +33,9 @@ beforeAll(() => { server.listen() }) -beforeEach(() => { +afterEach(() => { + queryClientV5.clear() server.resetHandlers() - queryClient.clear() }) afterAll(() => { @@ -54,9 +55,10 @@ describe('useSelfHostedCurrentUser', () => { describe('when data is loaded', () => { it('returns the user info', async () => { setup() - const { result } = renderHook(() => useSelfHostedCurrentUser(), { - wrapper, - }) + const { result } = renderHook( + () => useQueryV5(SelfHostedCurrentUserQueryOpts({ provider: 'gh' })), + { wrapper } + ) await waitFor(() => expect(result.current.data).toEqual(user)) }) diff --git a/src/services/selfHosted/useSelfHostedCurrentUser.ts b/src/services/selfHosted/SelfHostedCurrentUserQueryOpts.ts similarity index 62% rename from src/services/selfHosted/useSelfHostedCurrentUser.ts rename to src/services/selfHosted/SelfHostedCurrentUserQueryOpts.ts index e657fb39b3..ce2f748cf2 100644 --- a/src/services/selfHosted/useSelfHostedCurrentUser.ts +++ b/src/services/selfHosted/SelfHostedCurrentUserQueryOpts.ts @@ -1,9 +1,8 @@ -import { useQuery } from '@tanstack/react-query' -import { useParams } from 'react-router-dom' +import { queryOptions as queryOptionsV5 } from '@tanstack/react-queryV5' import { z } from 'zod' import Api from 'shared/api' -import { NetworkErrorObject, rejectNetworkError } from 'shared/api/helpers' +import { rejectNetworkError } from 'shared/api/helpers' const SelfHostedCurrentUserSchema = z .object({ @@ -16,14 +15,14 @@ const SelfHostedCurrentUserSchema = z }) .nullable() -interface URLParams { +interface SelfHostedCurrentUserQueryArgs { provider: string } -export const useSelfHostedCurrentUser = (options = {}) => { - const { provider } = useParams() - - return useQuery({ +export const SelfHostedCurrentUserQueryOpts = ({ + provider, +}: SelfHostedCurrentUserQueryArgs) => { + return queryOptionsV5({ queryKey: ['SelfHostedCurrentUser', provider], queryFn: ({ signal }) => Api.get({ provider, path: '/users/current', signal }).then((res) => { @@ -33,11 +32,11 @@ export const useSelfHostedCurrentUser = (options = {}) => { return rejectNetworkError({ status: 404, data: {}, - dev: 'useSelfHostedCurrentUser - 404 schema parsing failed', - } satisfies NetworkErrorObject) + dev: 'SelfHostedCurrentUserQueryOpts - 404 schema parsing failed', + error: parsedData.error, + }) } return parsedData.data }), - ...options, }) } diff --git a/src/services/selfHosted/index.ts b/src/services/selfHosted/index.ts deleted file mode 100644 index 66e4b4acaf..0000000000 --- a/src/services/selfHosted/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useSelfHostedCurrentUser' diff --git a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.jsx b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx similarity index 95% rename from src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.jsx rename to src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx index 4b0782311f..4d78b98db9 100644 --- a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.jsx +++ b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx @@ -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 { subDays } from 'date-fns' @@ -16,14 +20,34 @@ import { repoDisplayOptions } from '../ListRepo' const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }) -const server = setupServer() +const queryClientV5 = new QueryClientV5({ + defaultOptions: { queries: { retry: false } }, +}) + +const wrapper = + (repoDisplay: string): React.FC => + ({ children }) => ( + + + + + + {children} + + + + + + ) +const server = setupServer() beforeAll(() => { server.listen() }) afterEach(() => { queryClient.clear() + queryClientV5.clear() server.resetHandlers() }) @@ -31,22 +55,13 @@ afterAll(() => { server.close() }) -const wrapper = - (repoDisplay) => - ({ children }) => ( - - - - - {children} - - - - - ) +interface SetupArgs { + edges: object[] + isCurrentUserPartOfOrg?: boolean +} describe('ReposTableTeam', () => { - function setup({ edges = [], isCurrentUserPartOfOrg = true }) { + function setup({ edges = [], isCurrentUserPartOfOrg = true }: SetupArgs) { const mockApiVars = vi.fn() const fetchNextPage = vi.fn() const user = userEvent.setup() @@ -80,8 +95,13 @@ describe('ReposTableTeam', () => { } describe('rendering table', () => { + interface EdgeArgs { + coverageEnabled?: boolean + bundleAnalysisEnabled?: boolean + } + const edges = ( - { coverageEnabled, bundleAnalysisEnabled } = { + { coverageEnabled = true, bundleAnalysisEnabled = true }: EdgeArgs = { coverageEnabled: true, bundleAnalysisEnabled: true, } @@ -444,15 +464,8 @@ describe('ReposTableTeam', () => { }) describe('when rendered empty repos', () => { - beforeEach(() => { - setup({ - edges: [], - repoDisplayPassed: repoDisplayOptions.ALL.text, - privateAccess: true, - }) - }) - it('renders no repos detected', async () => { + setup({ edges: [] }) render(, { wrapper: wrapper(repoDisplayOptions.CONFIGURED.text), }) @@ -469,12 +482,8 @@ describe('ReposTableTeam', () => { }) describe('when rendered empty search', () => { - beforeEach(() => { - setup({ - edges: [], - }) - }) it('renders no results found', async () => { + setup({ edges: [] }) render(, { wrapper: wrapper(repoDisplayOptions.ALL.text), }) @@ -911,24 +920,14 @@ describe('ReposTableTeam', () => { describe('getSortingOption', () => { it('returns the correct sorting options for name column', () => { - const nameAsc = getSortingOption([ - { - id: 'name', - desc: false, - }, - ]) + const nameAsc = getSortingOption([{ id: 'name', desc: false }]) expect(nameAsc).toEqual({ ordering: TeamOrdering.NAME, direction: OrderingDirection.ASC, }) - const nameDesc = getSortingOption([ - { - id: 'name', - desc: true, - }, - ]) + const nameDesc = getSortingOption([{ id: 'name', desc: true }]) expect(nameDesc).toEqual({ ordering: TeamOrdering.NAME, @@ -938,10 +937,7 @@ describe('getSortingOption', () => { it('returns the correct sorting options for last updated column', () => { const lastUpdatedAsc = getSortingOption([ - { - id: 'latestCommitAt', - desc: false, - }, + { id: 'latestCommitAt', desc: false }, ]) expect(lastUpdatedAsc).toEqual({ @@ -950,10 +946,7 @@ describe('getSortingOption', () => { }) const lastUpdatedDesc = getSortingOption([ - { - id: 'latestCommitAt', - desc: true, - }, + { id: 'latestCommitAt', desc: true }, ]) expect(lastUpdatedDesc).toEqual({ diff --git a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.tsx b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.tsx index ee78d834bd..fd12686adf 100644 --- a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.tsx +++ b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.tsx @@ -1,3 +1,4 @@ +import { useInfiniteQuery as useInfiniteQueryV5 } from '@tanstack/react-queryV5' import type { SortingState } from '@tanstack/react-table' import { createColumnHelper, @@ -8,16 +9,16 @@ import { } from '@tanstack/react-table' import cs from 'classnames' import isEmpty from 'lodash/isEmpty' -import PropTypes from 'prop-types' import { useContext, useMemo, useState } from 'react' import { useParams } from 'react-router-dom' import { OrderingDirection, Repository, + ReposTeamQueryOpts, TeamOrdering, - useReposTeam, -} from 'services/repos' +} from 'services/repos/ReposTeamQueryOpts' +import { Provider } from 'shared/api/helpers' import { ActiveContext } from 'shared/context' import { formatTimeToNow } from 'shared/utils/dates' import Button from 'ui/Button' @@ -144,7 +145,12 @@ const getColumns = ({ ] } +interface URLParams { + provider: Provider +} + const ReposTableTeam = ({ searchValue }: ReposTableTeamProps) => { + const { provider } = useParams() const [sorting, setSorting] = useState([ { id: 'latestCommitAt', @@ -168,12 +174,15 @@ const ReposTableTeam = ({ searchValue }: ReposTableTeamProps) => { hasNextPage, isFetching, isFetchingNextPage, - } = useReposTeam({ - activated, - sortItem: getSortingOption(sorting), - term: searchValue, - owner, - }) + } = useInfiniteQueryV5( + ReposTeamQueryOpts({ + provider, + activated, + sortItem: getSortingOption(sorting), + term: searchValue, + owner, + }) + ) const isCurrentUserPartOfOrg = !!reposData?.pages?.[0]?.isCurrentUserPartOfOrg @@ -278,8 +287,4 @@ const ReposTableTeam = ({ searchValue }: ReposTableTeamProps) => { ) } -ReposTableTeam.propTypes = { - searchValue: PropTypes.string.isRequired, -} - export default ReposTableTeam diff --git a/src/shared/ListRepo/ReposTableTeam/index.js b/src/shared/ListRepo/ReposTableTeam/index.ts similarity index 100% rename from src/shared/ListRepo/ReposTableTeam/index.js rename to src/shared/ListRepo/ReposTableTeam/index.ts