diff --git a/src/pages/AdminSettings/AdminMembers/MemberList/MemberTable.tsx b/src/pages/AdminSettings/AdminMembers/MemberList/MemberTable.tsx index 78219968ba..4d903f1ca6 100644 --- a/src/pages/AdminSettings/AdminMembers/MemberList/MemberTable.tsx +++ b/src/pages/AdminSettings/AdminMembers/MemberList/MemberTable.tsx @@ -4,9 +4,9 @@ import { useQueryClient, } from '@tanstack/react-query' import { - useSuspenseQuery as useSuspenseQueryV5, useInfiniteQuery as useInfiniteQueryV5, useQueryClient as useQueryClientV5, + useSuspenseQuery as useSuspenseQueryV5, } from '@tanstack/react-queryV5' import { createColumnHelper, diff --git a/src/pages/AnalyticsPage/AnalyticsPage.jsx b/src/pages/AnalyticsPage/AnalyticsPage.jsx index def8f61a06..bd4118e6b4 100644 --- a/src/pages/AnalyticsPage/AnalyticsPage.jsx +++ b/src/pages/AnalyticsPage/AnalyticsPage.jsx @@ -2,7 +2,7 @@ import { useParams } from 'react-router-dom' import NotFound from 'pages/NotFound' import { useLocationParams } from 'services/navigation' -import { orderingOptions } from 'services/repos' +import { orderingOptions } from 'services/repos/orderingOptions' import { useOwner } from 'services/user' import ReposTable from 'shared/ListRepo/ReposTable' diff --git a/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.jsx b/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.jsx index 1deefe59d5..dd17c6a5bc 100644 --- a/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.jsx +++ b/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.jsx @@ -1,8 +1,9 @@ +import { useInfiniteQuery as useInfiniteQueryV5 } from '@tanstack/react-queryV5' import PropTypes from 'prop-types' import { useMemo, useRef, useState } from 'react' import { useParams } from 'react-router-dom' -import { useRepos } from 'services/repos' +import { ReposQueryOpts } from 'services/repos/ReposQueryOpts' import { TierNames, useTier } from 'services/tier' import A from 'ui/A' import DateRangePicker from 'ui/DateRangePicker' @@ -60,16 +61,17 @@ function RepoSelector({ isLoading, fetchNextPage, hasNextPage, - } = useRepos({ - provider, - owner, - sortItem, - activated: active, - term: search, - first: Infinity, - suspense: false, - isPublic: shouldDisplayPublicReposOnly, - }) + } = useInfiniteQueryV5( + ReposQueryOpts({ + provider, + owner, + sortItem, + activated: active, + term: search, + first: Infinity, + isPublic: shouldDisplayPublicReposOnly, + }) + ) const reposSelectData = useMemo(() => { const data = reposData?.pages?.map((page) => page?.repos).flat() diff --git a/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.test.jsx b/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.test.jsx index 05d9f8c78e..560a6d807b 100644 --- a/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.test.jsx +++ b/src/pages/AnalyticsPage/ChartSelectors/ChartSelectors.test.jsx @@ -1,7 +1,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { + QueryClientProvider as QueryClientProviderV5, + QueryClient as QueryClientV5, +} from '@tanstack/react-queryV5' import { act, render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { subDays } from 'date-fns' import { graphql, HttpResponse } from 'msw' import { setupServer } from 'msw/node' import { Suspense } from 'react' @@ -25,7 +28,6 @@ global.ResizeObserver = ResizeObserverMock const mocks = vi.hoisted(() => ({ useIntersection: vi.fn(), - useRepos: vi.fn(), })) vi.mock('react-use', async () => { @@ -36,46 +38,71 @@ vi.mock('react-use', async () => { } }) -vi.mock('services/repos', async () => { - const actual = await vi.importActual('services/repos') - return { - ...actual, - useRepos: mocks.useRepos, - } -}) - const repositories = [ { + name: 'Repo name 1', + active: true, + activated: true, + coverageAnalytics: { + lines: 99, + percentCovered: null, + }, private: false, + updatedAt: '2021-04-22T14:09:39.822872+00:00', author: { username: 'owner1', }, - name: 'Repo name 1', - latestCommitAt: subDays(new Date(), 3), - coverage: 43, - activated: true, + repositoryConfig: { + indicationRange: { + upperRange: 80, + lowerRange: 60, + }, + }, + latestCommitAt: null, + coverageEnabled: true, + bundleAnalysisEnabled: true, }, { + name: 'Repo name 3', + active: false, + activated: true, + coverageAnalytics: { + lines: 99, + percentCovered: null, + }, private: false, + updatedAt: '2021-04-22T14:09:39.826948+00:00', author: { username: 'owner2', }, - name: 'Repo name 3', - latestCommitAt: subDays(new Date(), 4), - coverage: 35, - activated: false, + repositoryConfig: { + indicationRange: { + upperRange: 80, + lowerRange: 60, + }, + }, + latestCommitAt: null, + coverageEnabled: true, + bundleAnalysisEnabled: true, }, ] -const queryClient = new QueryClient() +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) +const queryClientV5 = new QueryClientV5({ + defaultOptions: { queries: { retry: false } }, +}) const wrapper = ({ children }) => ( - - - - Suspense

}>{children}
-
-
-
+ + + + + Suspense

}>{children}
+
+
+
+
) const server = setupServer() @@ -86,6 +113,7 @@ beforeAll(() => { afterEach(() => { queryClient.clear() + queryClientV5.clear() server.resetHandlers() }) @@ -102,34 +130,42 @@ describe('ChartSelectors', () => { function setup({ hasNextPage = false, tierValue = TierNames.PRO }) { // https://github.com/testing-library/user-event/issues/1034 const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }) - const fetchNextPage = vi.fn() - - mocks.useRepos.mockReturnValue({ - data: { - pages: [ - { - repos: repositories, - pageInfo: { - hasNextPage: true, - endCursor: 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', - }, - }, - ], - }, - fetchNextPage, - hasNextPage, - }) + const searchTerm = vi.fn() server.use( graphql.query('OwnerTier', () => { return HttpResponse.json({ data: { owner: { plan: { tierName: tierValue } } }, }) + }), + graphql.query('ReposForOwner', ({ variables }) => { + if (variables?.filters?.term) { + searchTerm(variables.filters.term) + } + + if (variables?.after) { + fetchNextPage() + } + + return HttpResponse.json({ + data: { + owner: { + username: 'owner', + repositories: { + edges: [{ node: repositories[0] }, { node: repositories[1] }], + pageInfo: { + hasNextPage, + endCursor: 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', + }, + }, + }, + }, + }) }) ) - return { fetchNextPage, user } + return { fetchNextPage, user, searchTerm } } describe('renders component', () => { @@ -148,7 +184,7 @@ describe('ChartSelectors', () => { { wrapper } ) - const datePicker = screen.getByText('Pick a date') + const datePicker = await screen.findByText('Pick a date') expect(datePicker).toBeInTheDocument() }) @@ -208,13 +244,13 @@ describe('ChartSelectors', () => { { wrapper } ) - let datePicker = screen.getByText('Pick a date') + let datePicker = await screen.findByText('Pick a date') await act(async () => { await user.click(datePicker) }) - const gridCells = screen.getAllByRole('gridcell', { name: '31' }) - const date = within(gridCells[0]).getByText('31') + const gridCells = await screen.findAllByRole('gridcell', { name: '31' }) + const date = await within(gridCells[0]).findByText('31') await act(async () => { await user.click(date) }) @@ -252,13 +288,13 @@ describe('ChartSelectors', () => { { wrapper } ) - let datePicker = screen.getByText('Mar 31, 2022 - Mar 31, 2022') + let datePicker = await screen.findByText('Mar 31, 2022 - Mar 31, 2022') await act(async () => { await user.click(datePicker) }) - let gridCells = screen.getAllByRole('gridcell', { name: '31' }) - let date = within(gridCells[0]).getByText('31') + let gridCells = await screen.findAllByRole('gridcell', { name: '31' }) + let date = await within(gridCells[0]).findByText('31') await act(async () => { await user.click(date) }) @@ -282,13 +318,13 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) - const repo1 = screen.getByText('Repo name 1') + const repo1 = await screen.findByText('Repo name 1') expect(repo1).toBeInTheDocument() - const repo3 = screen.getByText('Repo name 3') + const repo3 = await screen.findByText('Repo name 3') expect(repo3).toBeInTheDocument() }) @@ -308,13 +344,13 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('1 Repo selected') + const multiselect = await screen.findByText('1 Repo selected') await user.click(multiselect) - const repo1 = screen.getByText('Repo name 3') + const repo1 = await screen.findByText('Repo name 3') await user.click(repo1) - const multiSelectUpdated = screen.getByText('2 Repos selected') + const multiSelectUpdated = await screen.findByText('2 Repos selected') expect(multiSelectUpdated).toBeInTheDocument() }) @@ -334,10 +370,10 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) - const repo1 = screen.getByText('Repo name 1') + const repo1 = await screen.findByText('Repo name 1') await user.click(repo1) await waitFor(() => @@ -364,15 +400,15 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) - const searchBox = screen.getByPlaceholderText('Search for Repos') + const searchBox = await screen.findByPlaceholderText('Search for Repos') expect(searchBox).toBeInTheDocument() }) it('updates the textbox value when typing', async () => { - const { user } = setup({}) + const { user, searchTerm } = setup({}) render( { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) - const searchBox = screen.getByPlaceholderText('Search for Repos') + const searchBox = await screen.findByPlaceholderText('Search for Repos') await user.type(searchBox, 'codecov') - const searchBoxUpdated = screen.getByPlaceholderText('Search for Repos') + const searchBoxUpdated = + await screen.findByPlaceholderText('Search for Repos') expect(searchBoxUpdated).toHaveAttribute('value', 'codecov') await waitFor(() => { - expect(mocks.useRepos).toHaveBeenCalledWith({ - activated: true, - first: Infinity, - owner: 'codecov', - provider: 'gh', - sortItem: { - direction: 'ASC', - ordering: 'NAME', - }, - suspense: false, - term: 'codecov', - isPublic: null, - }) + expect(searchTerm).toHaveBeenCalledWith('codecov') }) }) }) @@ -434,7 +459,7 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) await waitFor(() => expect(fetchNextPage).toHaveBeenCalled()) @@ -458,7 +483,7 @@ describe('ChartSelectors', () => { { wrapper } ) - const multiselect = screen.getByText('All Repos') + const multiselect = await screen.findByText('All Repos') await user.click(multiselect) expect(fetchNextPage).not.toHaveBeenCalled() @@ -484,7 +509,7 @@ describe('ChartSelectors', () => { { wrapper } ) - const clearFilters = screen.getByRole('button', { + const clearFilters = await screen.findByRole('button', { name: 'Clear filters', }) await user.click(clearFilters) @@ -521,21 +546,6 @@ describe('ChartSelectors', () => { const upgradeLink = await screen.findByRole('link', { name: 'Upgrade' }) expect(upgradeLink).toBeInTheDocument() expect(upgradeLink).toHaveAttribute('href', '/plan/gh/codecov/upgrade') - await waitFor(() => { - expect(mocks.useRepos).toHaveBeenCalledWith({ - activated: true, - first: Infinity, - owner: 'codecov', - provider: 'gh', - sortItem: { - direction: 'ASC', - ordering: 'NAME', - }, - suspense: false, - term: '', - isPublic: true, - }) - }) }) }) }) diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/CodeTreeTable/CodeTreeTable.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/CodeTreeTable/CodeTreeTable.tsx index 1c4d5b5828..88d03cf807 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/CodeTreeTable/CodeTreeTable.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/CodeTreeTable/CodeTreeTable.tsx @@ -8,7 +8,7 @@ import cs from 'classnames' import { useEffect } from 'react' import { useInView } from 'react-intersection-observer' -import { OrderingDirection } from 'services/repos' +import { OrderingDirection } from 'services/repos/orderingOptions' import { useTableDefaultSort } from 'shared/ContentsTable/useTableDefaultSort' import { Row } from 'shared/ContentsTable/utils' import Icon from 'ui/Icon' diff --git a/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/FileListTable/FileListTable.tsx b/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/FileListTable/FileListTable.tsx index 1d4b26ac61..b40d3b9394 100644 --- a/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/FileListTable/FileListTable.tsx +++ b/src/pages/RepoPage/CoverageTab/OverviewTab/subroute/FileExplorer/FileListTable/FileListTable.tsx @@ -8,7 +8,7 @@ import cs from 'classnames' import { useEffect } from 'react' import { useInView } from 'react-intersection-observer' -import { OrderingDirection } from 'services/repos' +import { OrderingDirection } from 'services/repos/orderingOptions' import { useTableDefaultSort } from 'shared/ContentsTable/useTableDefaultSort' import { Row } from 'shared/ContentsTable/utils' import Icon from 'ui/Icon' diff --git a/src/services/repos/useRepos.test.tsx b/src/services/repos/ReposQueryOpts.test.tsx similarity index 80% rename from src/services/repos/useRepos.test.tsx rename to src/services/repos/ReposQueryOpts.test.tsx index 9ff02393eb..0ec7a126c7 100644 --- a/src/services/repos/useRepos.test.tsx +++ b/src/services/repos/ReposQueryOpts.test.tsx @@ -1,23 +1,27 @@ -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 { useRepos } from './useRepos' +import { ReposQueryOpts } from './ReposQueryOpts' -const queryClient = new QueryClient({ +const queryClientV5 = new QueryClientV5({ defaultOptions: { queries: { retry: false } }, }) const wrapper = (initialEntries = '/gh'): React.FC => ({ children }) => ( - + {children} - + ) const repo1 = { @@ -74,7 +78,7 @@ beforeAll(() => { }) afterEach(() => { - queryClient.clear() + queryClientV5.clear() server.resetHandlers() }) @@ -82,7 +86,7 @@ afterAll(() => { server.close() }) -describe('useRepos', () => { +describe('ReposQueryOpts', () => { function setup({ invalidResponse = false } = {}) { server.use( graphql.query('ReposForOwner', (info) => { @@ -95,16 +99,8 @@ describe('useRepos', () => { username: 'codecov', repositories: { edges: info.variables.after - ? [ - { - node: repo2, - }, - ] - : [ - { - node: repo1, - }, - ], + ? [{ node: repo2 }] + : [{ node: repo1 }], pageInfo: { hasNextPage: info.variables.after ? false : true, endCursor: info.variables.after @@ -123,10 +119,9 @@ describe('useRepos', () => { it('returns repositories of the owner', async () => { setup() const { result } = renderHook( - () => useRepos({ provider: '', owner: 'codecov' }), - { - wrapper: wrapper(), - } + () => + useInfiniteQueryV5(ReposQueryOpts({ provider: '', owner: 'codecov' })), + { wrapper: wrapper() } ) await waitFor(() => { @@ -146,10 +141,8 @@ describe('useRepos', () => { it('returns next set of repositories', async () => { setup() const { result } = renderHook( - () => useRepos({ provider: '', owner: '' }), - { - wrapper: wrapper(), - } + () => useInfiniteQueryV5(ReposQueryOpts({ provider: '', owner: '' })), + { wrapper: wrapper() } ) await waitFor(() => result.current.isFetching) @@ -190,13 +183,17 @@ describe('useRepos', () => { it('throws an error', async () => { setup({ invalidResponse: true }) const { result } = renderHook( - () => useRepos({ provider: '', owner: 'owner1' }), + () => + useInfiniteQueryV5(ReposQueryOpts({ provider: '', owner: 'owner1' })), { wrapper: wrapper() } ) await waitFor(() => expect(result.current.error).toEqual( - expect.objectContaining({ status: 404 }) + expect.objectContaining({ + status: 404, + dev: 'ReposQueryOpts - 404 Failed to parse schema', + }) ) ) }) diff --git a/src/services/repos/useRepos.tsx b/src/services/repos/ReposQueryOpts.tsx similarity index 74% rename from src/services/repos/useRepos.tsx rename to src/services/repos/ReposQueryOpts.tsx index 73ac854710..0523e6cb86 100644 --- a/src/services/repos/useRepos.tsx +++ b/src/services/repos/ReposQueryOpts.tsx @@ -1,11 +1,17 @@ -import { useInfiniteQuery } from '@tanstack/react-query' +import { infiniteQueryOptions as infiniteQueryOptionsV5 } from '@tanstack/react-queryV5' import { z } from 'zod' import { RepositoryConfigSchema } from 'services/repo/useRepoConfig' import Api from 'shared/api' +import { rejectNetworkError } from 'shared/api/helpers' import { mapEdges } from 'shared/utils/graphql' -import { orderingOptions } from './orderingOptions' +import { + nonActiveOrderingOptions, + OrderingDirection, + orderingOptions, + TeamOrdering, +} from './orderingOptions' const RepositorySchema = z .object({ @@ -30,7 +36,7 @@ const RepositorySchema = z }) .nullable() -export type RepositoryResult = z.infer +type RepositoryResult = z.infer const RequestSchema = z.object({ owner: z @@ -101,7 +107,7 @@ const query = `query ReposForOwner( } }` -interface UseReposArgs { +interface ReposQueryArgs { provider: string owner: string activated?: boolean @@ -115,7 +121,7 @@ interface UseReposArgs { isPublic?: true | false | null } -export function useRepos({ +function ReposQueryOpts({ provider, owner, activated, @@ -124,8 +130,7 @@ export function useRepos({ first = 20, repoNames, isPublic = null, // by default, get both public and private repos - ...options -}: UseReposArgs) { +}: ReposQueryArgs) { const variables = { filters: { activated, term, repoNames, isPublic }, ordering: sortItem?.ordering, @@ -133,7 +138,7 @@ export function useRepos({ first, } - return useInfiniteQuery({ + return infiniteQueryOptionsV5({ queryKey: ['repos', provider, owner, variables], queryFn: ({ pageParam, signal }) => { return Api.graphql({ @@ -143,27 +148,37 @@ export function useRepos({ variables: { ...variables, owner, - after: pageParam, + after: pageParam === '' ? undefined : pageParam, }, }).then((res) => { const parsedRes = RequestSchema.safeParse(res?.data) if (!parsedRes.success) { - return Promise.reject({ + return rejectNetworkError({ status: 404, - data: null, + data: {}, + dev: 'ReposQueryOpts - 404 Failed to parse schema', + error: parsedRes.error, }) } - const owner = parsedRes?.data?.owner return { - repos: mapEdges(owner?.repositories), - pageInfo: owner?.repositories?.pageInfo, + repos: mapEdges(parsedRes?.data?.owner?.repositories), + pageInfo: parsedRes?.data?.owner?.repositories?.pageInfo, } }) }, - suspense: false, - getNextPageParam: (data) => - data?.pageInfo?.hasNextPage ? data.pageInfo.endCursor : undefined, - ...options, + initialPageParam: '', + getNextPageParam: (data) => { + return data?.pageInfo?.hasNextPage ? data.pageInfo.endCursor : null + }, }) } + +export { + type RepositoryResult, + ReposQueryOpts, + orderingOptions, + OrderingDirection, + TeamOrdering, + nonActiveOrderingOptions, +} diff --git a/src/services/repos/ReposTeamQueryOpts.test.tsx b/src/services/repos/ReposTeamQueryOpts.test.tsx index 7fc2e9711a..a3346823c0 100644 --- a/src/services/repos/ReposTeamQueryOpts.test.tsx +++ b/src/services/repos/ReposTeamQueryOpts.test.tsx @@ -100,7 +100,7 @@ afterAll(() => { server.close() }) -describe('useReposTeam', () => { +describe('ReposTeamQueryOpts', () => { function setup({ invalidResponse = false } = {}) { server.use( graphql.query('GetReposTeam', (info) => { @@ -156,7 +156,7 @@ describe('useReposTeam', () => { }, }, ], - pageParams: [undefined], + pageParams: [''], }) ) }) @@ -200,7 +200,7 @@ describe('useReposTeam', () => { pageInfo: { hasNextPage: false, endCursor: 'aa' }, }, ], - pageParams: [undefined, 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA='], + pageParams: ['', 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA='], }) ) }) diff --git a/src/services/repos/ReposTeamQueryOpts.tsx b/src/services/repos/ReposTeamQueryOpts.tsx index fba98fdc2e..5c0697d0b0 100644 --- a/src/services/repos/ReposTeamQueryOpts.tsx +++ b/src/services/repos/ReposTeamQueryOpts.tsx @@ -136,7 +136,7 @@ function ReposTeamQueryOpts({ variables: { ...variables, owner, - after: pageParam, + after: pageParam === '' ? undefined : pageParam, }, }).then((res) => { const parsedRes = RequestSchema.safeParse(res?.data) @@ -159,7 +159,7 @@ function ReposTeamQueryOpts({ } }) }, - initialPageParam: undefined as string | undefined, + initialPageParam: '', getNextPageParam: (data) => { if (data?.pageInfo?.hasNextPage) { return data.pageInfo.endCursor diff --git a/src/services/repos/index.ts b/src/services/repos/index.ts deleted file mode 100644 index ae1f8c951b..0000000000 --- a/src/services/repos/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './useRepos' -export * from './orderingOptions' diff --git a/src/shared/ListRepo/ListRepo.jsx b/src/shared/ListRepo/ListRepo.jsx index 3c33e5e4f6..0fe9dddccd 100644 --- a/src/shared/ListRepo/ListRepo.jsx +++ b/src/shared/ListRepo/ListRepo.jsx @@ -4,7 +4,7 @@ import { Suspense, useContext } from 'react' import { useParams } from 'react-router-dom' import { useLocationParams } from 'services/navigation' -import { orderingOptions } from 'services/repos' +import { orderingOptions } from 'services/repos/orderingOptions' import { TierNames, useTier } from 'services/tier' import { useUser } from 'services/user' import { ActiveContext } from 'shared/context' diff --git a/src/shared/ListRepo/OrgControlTable/OrgControlTable.test.jsx b/src/shared/ListRepo/OrgControlTable/OrgControlTable.test.jsx index a613b06d20..fed7066106 100644 --- a/src/shared/ListRepo/OrgControlTable/OrgControlTable.test.jsx +++ b/src/shared/ListRepo/OrgControlTable/OrgControlTable.test.jsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { orderingOptions } from 'services/repos' +import { orderingOptions } from 'services/repos/orderingOptions' import OrgControlTable from './OrgControlTable' diff --git a/src/shared/ListRepo/ReposTable/ReposTable.test.tsx b/src/shared/ListRepo/ReposTable/ReposTable.test.tsx index 0df200bfcc..8da3d49564 100644 --- a/src/shared/ListRepo/ReposTable/ReposTable.test.tsx +++ b/src/shared/ListRepo/ReposTable/ReposTable.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, @@ -19,10 +23,6 @@ import ReposTable from './ReposTable' import { repoDisplayOptions } from '../ListRepo' -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}) -const server = setupServer() const mockRepositories = ( { coverageEnabled = true, @@ -151,16 +151,12 @@ const mockUser = { }, } -beforeAll(() => { - server.listen() - console.error = () => {} +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, }) -afterEach(() => { - queryClient.clear() - server.resetHandlers() +const queryClientV5 = new QueryClientV5({ + defaultOptions: { queries: { retry: false } }, }) -afterAll(() => server.close) - const wrapper = ( repoDisplay: string, @@ -168,17 +164,31 @@ const wrapper = path: string = '/:provider' ): React.FC => ({ children }) => ( - - - - - {children} - - - - + + + + + + {children} + + + + + ) +const server = setupServer() +beforeAll(() => { + server.listen() + console.error = () => {} +}) +afterEach(() => { + queryClient.clear() + queryClientV5.clear() + server.resetHandlers() +}) +afterAll(() => server.close) + interface SetupArgs { edges?: any[] isCurrentUserPartOfOrg?: boolean diff --git a/src/shared/ListRepo/ReposTable/ReposTable.tsx b/src/shared/ListRepo/ReposTable/ReposTable.tsx index 4a3ba962fa..d0ca39b46d 100644 --- a/src/shared/ListRepo/ReposTable/ReposTable.tsx +++ b/src/shared/ListRepo/ReposTable/ReposTable.tsx @@ -1,3 +1,4 @@ +import { useInfiniteQuery as useInfiniteQueryV5 } from '@tanstack/react-queryV5' import { flexRender, getCoreRowModel, @@ -13,7 +14,10 @@ import { useParams } from 'react-router-dom' import config from 'config' -import { OrderingDirection, useRepos } from 'services/repos' +import { + OrderingDirection, + ReposQueryOpts, +} from 'services/repos/ReposQueryOpts' import { TierNames, useTier } from 'services/tier' import { useOwner, useUser } from 'services/user' import { ActiveContext } from 'shared/context' @@ -134,23 +138,27 @@ const ReposTable = ({ hasNextPage, isLoading: isReposLoading, isFetchingNextPage, - } = useRepos({ - provider, - owner, - activated, - sortItem: getOrderingDirection(sorting), - term: searchValue, - repoNames: filterValues, - isPublic: shouldDisplayPublicReposOnly, - }) + } = useInfiniteQueryV5( + ReposQueryOpts({ + provider, + owner, + activated, + sortItem: getOrderingDirection(sorting), + term: searchValue, + repoNames: filterValues, + isPublic: shouldDisplayPublicReposOnly, + }) + ) // fetch demo repo(s) - const { data: demoReposData } = useRepos({ - provider: DEMO_REPO.provider, - owner: DEMO_REPO.owner, - activated, - repoNames: [DEMO_REPO.repo], - }) + const { data: demoReposData } = useInfiniteQueryV5( + ReposQueryOpts({ + provider: DEMO_REPO.provider, + owner: DEMO_REPO.owner, + activated, + repoNames: [DEMO_REPO.repo], + }) + ) const isMyOwnerPage = currentUser?.user?.username === owner diff --git a/src/shared/ListRepo/ReposTable/getReposColumnsHelper.tsx b/src/shared/ListRepo/ReposTable/getReposColumnsHelper.tsx index e52b691da0..fa247d41f7 100644 --- a/src/shared/ListRepo/ReposTable/getReposColumnsHelper.tsx +++ b/src/shared/ListRepo/ReposTable/getReposColumnsHelper.tsx @@ -1,6 +1,6 @@ import { createColumnHelper } from '@tanstack/react-table' -import { RepositoryResult } from 'services/repos' +import { RepositoryResult } from 'services/repos/ReposQueryOpts' import { formatTimeToNow } from 'shared/utils/dates' import TotalsNumber from 'ui/TotalsNumber' diff --git a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx index 4d78b98db9..7fdbf23fa6 100644 --- a/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx +++ b/src/shared/ListRepo/ReposTableTeam/ReposTableTeam.test.tsx @@ -10,7 +10,7 @@ import { graphql, HttpResponse } from 'msw' import { setupServer } from 'msw/node' import { MemoryRouter, Route } from 'react-router-dom' -import { OrderingDirection, TeamOrdering } from 'services/repos' +import { OrderingDirection, TeamOrdering } from 'services/repos/orderingOptions' import { ActiveContext } from 'shared/context' import ReposTableTeam, { getSortingOption } from './ReposTableTeam' diff --git a/src/shared/utils/demo.ts b/src/shared/utils/demo.ts index d933cb90c9..4f5a1b9e9f 100644 --- a/src/shared/utils/demo.ts +++ b/src/shared/utils/demo.ts @@ -1,4 +1,6 @@ -import { useRepos } from 'services/repos' +import { ReposQueryOpts } from 'services/repos/ReposQueryOpts' + +import { ExtractInfiniteQueryDataType } from './queries' export const DEMO_REPO = { provider: 'github', @@ -7,31 +9,33 @@ export const DEMO_REPO = { displayName: 'Codecov demo', } -type UseReposData = ReturnType['data'] +type ReposQueryData = ExtractInfiniteQueryDataType export function formatDemoRepos( - demoReposData: UseReposData, + demoReposData: ReposQueryData | undefined, searchValue: string ) { - return ( - demoReposData?.pages - .flatMap((page) => page.repos) - .filter(isNotNull) - .map( - // tag the repo as demo and overwrite its display name - (repo) => ({ - ...repo, - isDemo: true, - name: DEMO_REPO.displayName, - }) - ) - .filter( - // filter if name does not match the search value - (repo: { name: string }) => - !searchValue || - repo.name.toLowerCase().includes(searchValue.toLowerCase()) - ) ?? [] - ) + if (!demoReposData) { + return [] + } + + return demoReposData?.pages + .flatMap((page) => page.repos) + .filter(isNotNull) + .map( + // tag the repo as demo and overwrite its display name + (repo) => ({ + ...repo, + isDemo: true, + name: DEMO_REPO.displayName, + }) + ) + .filter( + // filter if name does not match the search value + (repo: { name: string }) => + !searchValue || + repo.name.toLowerCase().includes(searchValue.toLowerCase()) + ) } // isNotNull can be used in filter to exclude null elements while conveying to diff --git a/src/shared/utils/queries.ts b/src/shared/utils/queries.ts new file mode 100644 index 0000000000..87e8648de5 --- /dev/null +++ b/src/shared/utils/queries.ts @@ -0,0 +1,50 @@ +import { + InfiniteData, + UseInfiniteQueryOptions, + UseQueryOptions, +} from '@tanstack/react-queryV5' + +type AnyFunction = (...args: any) => any + +type KnownInfiniteQueryFnReturn< + T extends (...args: any) => UseInfiniteQueryOptions, +> = NonNullable['queryFn']> + +/** + * Extracts the infinite query data from a query function - **Only works for TanStack Query V5**. + * + * @param T - The query function to extract the infinite query data from. + * @returns The type of infinite query data. + * + * @example + * ```ts + * import { InfiniteQueryOpts } from './InfiniteQueryOpts' + * + * type ReturnData = ExtractInfiniteQueryDataType + * ``` + */ +export type ExtractInfiniteQueryDataType = InfiniteData< + Awaited>> +> + +// ----------------------------------------------------------------------------- + +type KnownQueryFnReturn UseQueryOptions> = + NonNullable['queryFn']> + +/** + * Extracts the query data from a query function - **Only works for TanStack Query V5**. + * + * @param T - The query function to extract the query data from. + * @returns The type of the query data. + * + * @example + * ```ts + * import { QueryOpts } from './QueryOpts' + * + * type ReturnData = ExtractQueryDataType + * ``` + */ +export type ExtractQueryDataType = Awaited< + ReturnType> +>