diff --git a/package-lock.json b/package-lock.json index f8c8b6a6d7..22c4c132b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.43.9", "react-hot-toast": "^2.4.1", - "react-intersection-observer": "^9.4.1", + "react-intersection-observer": "^9.10.3", "react-modal": "^3.14.4", "react-router-dom": "^5.2.1", "react-router-dom-v5-compat": "^6.15.0", @@ -26681,11 +26681,17 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz", - "integrity": "sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==", + "version": "9.10.3", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz", + "integrity": "sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==", "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-is": { @@ -53241,9 +53247,9 @@ } }, "react-intersection-observer": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.2.tgz", - "integrity": "sha512-EmoV66/yvksJcGa1rdW0nDNc4I1RifDWkT50gXSFnPLYQ4xUptuDD4V7k+Rj1OgVAlww628KLGcxPXFlOkkU/Q==", + "version": "9.10.3", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.10.3.tgz", + "integrity": "sha512-9NYfKwPZRovB6QJee7fDg0zz/SyYrqXtn5xTZU0vwLtLVBtfu9aZt1pVmr825REE49VPDZ7Lm5SNHjJBOTZHpA==", "requires": {} }, "react-is": { diff --git a/package.json b/package.json index aa20353db5..8ce1fbe0ea 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.43.9", "react-hot-toast": "^2.4.1", - "react-intersection-observer": "^9.4.1", + "react-intersection-observer": "^9.10.3", "react-modal": "^3.14.4", "react-router-dom": "^5.2.1", "react-router-dom-v5-compat": "^6.15.0", diff --git a/src/pages/PullRequestPage/PullCoverage/routes/CommitsTab/CommitsTable/createCommitsTableData.tsx b/src/pages/PullRequestPage/PullCoverage/routes/CommitsTab/CommitsTable/createCommitsTableData.tsx index a322355cc3..40abb7d1c9 100644 --- a/src/pages/PullRequestPage/PullCoverage/routes/CommitsTab/CommitsTable/createCommitsTableData.tsx +++ b/src/pages/PullRequestPage/PullCoverage/routes/CommitsTab/CommitsTable/createCommitsTableData.tsx @@ -1,7 +1,12 @@ import isArray from 'lodash/isArray' import isEmpty from 'lodash/isEmpty' -import { Commit } from 'services/commits/useCommits' +import { + Commit, + COMMIT_STATUS_COMPLETED, + COMMIT_STATUS_ERROR, + COMMIT_STATUS_PENDING, +} from 'services/commits/useCommits' import { formatSizeToString } from 'shared/utils/bundleAnalysis' import TotalsNumber from 'ui/TotalsNumber' @@ -35,12 +40,12 @@ export const createCommitsTableData = ({ pages }: CommitsTableData) => { return commits.filter(Boolean).map((commit) => { let patch =

-

- if (commit?.coverageStatus === 'ERROR') { + if (commit?.coverageStatus === COMMIT_STATUS_ERROR) { patch = - } else if (commit?.coverageStatus === 'PENDING') { + } else if (commit?.coverageStatus === COMMIT_STATUS_PENDING) { patch = } else if ( - commit?.coverageStatus === 'COMPLETED' && + commit?.coverageStatus === COMMIT_STATUS_COMPLETED && commit?.compareWithParent?.__typename === 'Comparison' ) { const percent = @@ -57,12 +62,12 @@ export const createCommitsTableData = ({ pages }: CommitsTableData) => { } let bundleAnalysis =

-

- if (commit?.bundleStatus === 'ERROR') { + if (commit?.bundleStatus === COMMIT_STATUS_ERROR) { bundleAnalysis = - } else if (commit?.bundleStatus === 'PENDING') { + } else if (commit?.bundleStatus === COMMIT_STATUS_PENDING) { bundleAnalysis = } else if ( - commit?.bundleStatus === 'COMPLETED' && + commit?.bundleStatus === COMMIT_STATUS_COMPLETED && commit?.bundleAnalysisCompareWithParent?.__typename === 'BundleAnalysisComparison' ) { diff --git a/src/pages/RepoPage/CommitsTab/CommitsTable/CommitsTable.tsx b/src/pages/RepoPage/CommitsTab/CommitsTable/CommitsTable.tsx index f1ab8fd791..ce67e567a0 100644 --- a/src/pages/RepoPage/CommitsTab/CommitsTable/CommitsTable.tsx +++ b/src/pages/RepoPage/CommitsTab/CommitsTable/CommitsTable.tsx @@ -10,10 +10,7 @@ import { useEffect, useMemo } from 'react' import { useInView } from 'react-intersection-observer' import { useParams } from 'react-router-dom' -import { - type CommitStatusesEnum, - useCommits, -} from 'services/commits/useCommits' +import { CommitStatuses, useCommits } from 'services/commits/useCommits' import { useRepoOverview } from 'services/repo' import Spinner from 'ui/Spinner' @@ -69,7 +66,7 @@ interface URLParams { interface CommitsTableProps { branch: string search: string - coverageStatus: Array + coverageStatus: Array } const CommitsTable: React.FC = ({ diff --git a/src/pages/RepoPage/CommitsTab/CommitsTable/createCommitsTableData.tsx b/src/pages/RepoPage/CommitsTab/CommitsTable/createCommitsTableData.tsx index a322355cc3..40abb7d1c9 100644 --- a/src/pages/RepoPage/CommitsTab/CommitsTable/createCommitsTableData.tsx +++ b/src/pages/RepoPage/CommitsTab/CommitsTable/createCommitsTableData.tsx @@ -1,7 +1,12 @@ import isArray from 'lodash/isArray' import isEmpty from 'lodash/isEmpty' -import { Commit } from 'services/commits/useCommits' +import { + Commit, + COMMIT_STATUS_COMPLETED, + COMMIT_STATUS_ERROR, + COMMIT_STATUS_PENDING, +} from 'services/commits/useCommits' import { formatSizeToString } from 'shared/utils/bundleAnalysis' import TotalsNumber from 'ui/TotalsNumber' @@ -35,12 +40,12 @@ export const createCommitsTableData = ({ pages }: CommitsTableData) => { return commits.filter(Boolean).map((commit) => { let patch =

-

- if (commit?.coverageStatus === 'ERROR') { + if (commit?.coverageStatus === COMMIT_STATUS_ERROR) { patch = - } else if (commit?.coverageStatus === 'PENDING') { + } else if (commit?.coverageStatus === COMMIT_STATUS_PENDING) { patch = } else if ( - commit?.coverageStatus === 'COMPLETED' && + commit?.coverageStatus === COMMIT_STATUS_COMPLETED && commit?.compareWithParent?.__typename === 'Comparison' ) { const percent = @@ -57,12 +62,12 @@ export const createCommitsTableData = ({ pages }: CommitsTableData) => { } let bundleAnalysis =

-

- if (commit?.bundleStatus === 'ERROR') { + if (commit?.bundleStatus === COMMIT_STATUS_ERROR) { bundleAnalysis = - } else if (commit?.bundleStatus === 'PENDING') { + } else if (commit?.bundleStatus === COMMIT_STATUS_PENDING) { bundleAnalysis = } else if ( - commit?.bundleStatus === 'COMPLETED' && + commit?.bundleStatus === COMMIT_STATUS_COMPLETED && commit?.bundleAnalysisCompareWithParent?.__typename === 'BundleAnalysisComparison' ) { diff --git a/src/pages/RepoPage/PullsTab/PullsTab.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTab.spec.tsx index c33da6080e..2a6d7dfb0e 100644 --- a/src/pages/RepoPage/PullsTab/PullsTab.spec.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTab.spec.tsx @@ -1,89 +1,21 @@ import userEvent from '@testing-library/user-event' -import { graphql } from 'msw' -import { setupServer } from 'msw/node' - -import { TierNames } from 'services/tier' import PullsTab from './PullsTab' import { repoPageRender, screen } from '../repo-jest-setup' jest.mock('./PullsTable', () => () => 'PullsTable') -jest.mock('./PullsTableTeam', () => () => 'PullsTableTeam') - -const mockRepoSettings = (isPrivate = false) => ({ - owner: { - repository: { - __typename: 'Repository', - activated: true, - defaultBranch: 'master', - private: isPrivate, - uploadToken: 'token', - graphToken: 'token', - yaml: 'yaml', - bot: { - username: 'test', - }, - }, - }, -}) - -const server = setupServer() - -beforeAll(() => { - server.listen({ onUnhandledRequest: 'warn' }) -}) - -afterEach(() => { - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -interface SetupArgs { - tierValue?: 'pro' | 'team' - isPrivate?: boolean -} describe('Pulls Tab', () => { - function setup({ tierValue = 'pro', isPrivate = false }: SetupArgs) { + function setup() { const user = userEvent.setup() - server.use( - graphql.query('GetRepo', (req, res, ctx) => { - return res(ctx.status(200), ctx.data({})) - }), - graphql.query('OwnerTier', (req, res, ctx) => { - if (tierValue === TierNames.TEAM) { - return res( - ctx.status(200), - ctx.data({ - owner: { plan: { tierName: TierNames.TEAM } }, - }) - ) - } - - return res( - ctx.status(200), - ctx.data({ - owner: { plan: { tierName: 'pro' } }, - }) - ) - }), - graphql.query('GetRepoSettingsTeam', (req, res, ctx) => { - return res(ctx.status(200), ctx.data(mockRepoSettings(isPrivate))) - }) - ) - return { user } } describe('rendering table controls', () => { - beforeEach(() => setup({})) - it('renders select by updatestamp label', () => { + setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -94,6 +26,7 @@ describe('Pulls Tab', () => { }) it('renders view by state label', () => { + setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -104,6 +37,7 @@ describe('Pulls Tab', () => { }) it('renders default of select by updatestamp', () => { + setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -114,6 +48,7 @@ describe('Pulls Tab', () => { }) it('renders default of select by state', () => { + setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -125,51 +60,21 @@ describe('Pulls Tab', () => { }) describe('rendering table', () => { - describe('on non-team tier', () => { - it('renders PullsTable component', async () => { - setup({}) - repoPageRender({ - initialEntries: ['/gh/codecov/gazebo/pulls'], - renderPulls: () => , - }) - - const table = await screen.findByText('PullsTable') - expect(table).toBeInTheDocument() - }) - }) - - describe('on team tier', () => { - describe('repo is public', () => { - it('renders PullsTable component', async () => { - setup({ tierValue: TierNames.TEAM, isPrivate: false }) - repoPageRender({ - initialEntries: ['/gh/codecov/gazebo/pulls'], - renderPulls: () => , - }) - - const table = await screen.findByText('PullsTable') - expect(table).toBeInTheDocument() - }) + it('renders PullsTable component', async () => { + setup() + repoPageRender({ + initialEntries: ['/gh/codecov/gazebo/pulls'], + renderPulls: () => , }) - describe('repo is private', () => { - it('renders PullsTableTeam component', async () => { - setup({ tierValue: TierNames.TEAM, isPrivate: true }) - repoPageRender({ - initialEntries: ['/gh/codecov/gazebo/pulls'], - renderPulls: () => , - }) - - const table = await screen.findByText('PullsTableTeam') - expect(table).toBeInTheDocument() - }) - }) + const table = await screen.findByText('PullsTable') + expect(table).toBeInTheDocument() }) }) describe('view by state', () => { it('renders all options', async () => { - const { user } = setup({}) + const { user } = setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -191,7 +96,7 @@ describe('Pulls Tab', () => { describe('order by updatestamp', () => { it('renders all options', async () => { - const { user } = setup({}) + const { user } = setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -207,7 +112,7 @@ describe('Pulls Tab', () => { describe('order by oldest', () => { it('renders the selected option', async () => { - const { user } = setup({}) + const { user } = setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , @@ -229,7 +134,7 @@ describe('Pulls Tab', () => { describe('view by merged', () => { it('renders the number of selected options', async () => { - const { user } = setup({}) + const { user } = setup() repoPageRender({ initialEntries: ['/gh/codecov/gazebo/pulls'], renderPulls: () => , diff --git a/src/pages/RepoPage/PullsTab/PullsTab.tsx b/src/pages/RepoPage/PullsTab/PullsTab.tsx index 84377a7c9f..29040280dc 100644 --- a/src/pages/RepoPage/PullsTab/PullsTab.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTab.tsx @@ -1,9 +1,6 @@ -import { lazy, Suspense, useLayoutEffect, useState } from 'react' -import { useParams } from 'react-router-dom' +import { lazy, Suspense, useCallback, useLayoutEffect, useState } from 'react' import { useLocationParams } from 'services/navigation' -import { useRepoSettingsTeam } from 'services/repo' -import { TierNames, useTier } from 'services/tier' import MultiSelect from 'ui/MultiSelect' import Select from 'ui/Select' import Spinner from 'ui/Spinner' @@ -20,7 +17,6 @@ import { import { useSetCrumbs } from '../context' const PullsTable = lazy(() => import('./PullsTable')) -const PullsTableTeam = lazy(() => import('./PullsTableTeam')) const Loader = () => (
@@ -62,17 +58,12 @@ function useControlParams() { } } -interface URLParams { - provider: string - owner: string -} - function PullsTab() { - const { provider, owner } = useParams() const setCrumbs = useSetCrumbs() - const { data: repoSettingsTeam } = useRepoSettingsTeam() - const { data: tierData } = useTier({ provider, owner }) + useLayoutEffect(() => { + setCrumbs() + }, [setCrumbs]) const { updateParams, @@ -82,27 +73,26 @@ function PullsTab() { setSelectedStates, } = useControlParams() - useLayoutEffect(() => { - setCrumbs() - }, [setCrumbs]) - - const handleOrderChange = (selectedOrder: keyof typeof orderingEnum) => { - const { order } = orderingEnum[selectedOrder] - setSelectedOrder(selectedOrder) - updateParams({ order }) - } - - const handleStatesChange = (selectedStates: SelectedStatesEnum) => { - const prStates = selectedStates.map((filter) => { - const { state } = stateEnum[filter] - return state - }) - setSelectedStates(prStates) - updateParams({ prStates }) - } + const handleOrderChange = useCallback( + (selectedOrder: keyof typeof orderingEnum) => { + const { order } = orderingEnum[selectedOrder] + setSelectedOrder(selectedOrder) + updateParams({ order }) + }, + [setSelectedOrder, updateParams] + ) - const showTeamTable = - repoSettingsTeam?.repository?.private && tierData === TierNames.TEAM + const handleStatesChange = useCallback( + (selectedStates: SelectedStatesEnum) => { + const prStates = selectedStates.map((filter) => { + const { state } = stateEnum[filter] + return state + }) + setSelectedStates(prStates) + updateParams({ prStates }) + }, + [setSelectedStates, updateParams] + ) return (
@@ -136,7 +126,7 @@ function PullsTab() {
}> - {showTeamTable ? : } + ) diff --git a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.spec.tsx deleted file mode 100644 index 417acbca46..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.spec.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { render, screen } from '@testing-library/react' -import { MemoryRouter, Route } from 'react-router-dom' - -import Coverage from '.' - -jest.mock('services/repo') - -const wrapper: React.FC = ({ children }) => ( - - {children} - -) - -describe('Coverage', () => { - describe('when rendered with a pull coverage', () => { - it('renders id of the pull', () => { - render( - , - { - wrapper, - } - ) - - const id = screen.getByText(/#746/) - expect(id).toBeInTheDocument() - }) - - it('renders coverage of pull', () => { - render( - , - { - wrapper, - } - ) - - const coverage = screen.getByText(/45.00%/) - expect(coverage).toBeInTheDocument() - }) - - it('renders coverage state', () => { - render( - , - { - wrapper, - } - ) - - const coverage = screen.getByText(/merge.svg/) - expect(coverage).toBeInTheDocument() - }) - }) - - describe('when rendered with no coverage in pull', () => { - it('renders id of the pull', () => { - render(, { - wrapper, - }) - - const id = screen.getByText(/#746/) - expect(id).toBeInTheDocument() - }) - - it('renders no reports text', () => { - render(, { - wrapper, - }) - - const text = screen.getByText('-') - expect(text).toBeInTheDocument() - }) - }) -}) diff --git a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.tsx b/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.tsx deleted file mode 100644 index 611ad48f44..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/Coverage.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import A from 'ui/A' -import Icon from 'ui/Icon' -import TotalsNumber from 'ui/TotalsNumber' - -import { IconEnum, IconEnumState } from './enums' - -interface PullStateProps { - state: IconEnumState -} - -const PullState: React.FC = ({ state }) => { - const icon = IconEnum.find((item) => state === item.state) ?? IconEnum[0] - - return ( - - - - ) -} - -interface CoverageProps { - head?: { - totals?: { - percentCovered: number | null - } | null - bundleAnalysisReport?: { - __typename: string - } | null - } | null - pullId: number - state: IconEnumState - plain?: boolean -} - -const Coverage: React.FC = ({ - head, - state, - pullId, - plain = false, -}) => { - if (typeof head?.totals?.percentCovered === 'number') { - return ( -
- - {/* @ts-expect-error - disable because of non-ts component and type mismatch */} - - #{pullId} - -
- -
-
- ) - } - - return ( -
- - {/* @ts-expect-error - disable because of non-ts component and type mismatch */} - - #{pullId} - - - -
- ) -} - -export default Coverage diff --git a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/enums.ts b/src/pages/RepoPage/PullsTab/PullsTable/Coverage/enums.ts deleted file mode 100644 index 07b2f2dc92..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/enums.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const PullStateEnum = { - MERGED: 'MERGED', - OPEN: 'OPEN', - CLOSED: 'CLOSED', -} as const - -export const IconEnum = [ - { - state: PullStateEnum.MERGED, - name: 'merge', - }, - { - state: PullStateEnum.CLOSED, - name: 'pullRequestClosed', - }, - { - state: PullStateEnum.OPEN, - name: 'pullRequestOpen', - }, -] as const - -export type IconEnumState = (typeof IconEnum)[number]['state'] diff --git a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/index.ts b/src/pages/RepoPage/PullsTab/PullsTable/Coverage/index.ts deleted file mode 100644 index a089185183..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTable/Coverage/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Coverage' -export * from './enums' diff --git a/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.spec.tsx index af204cc6c6..4ce4b2cbb3 100644 --- a/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.spec.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.spec.tsx @@ -35,20 +35,19 @@ const node1 = { username: 'codecov-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, + head: { + bundleStatus: 'PENDING', + coverageStatus: 'COMPLETED', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 75, }, - changeCoverage: 1, - }, - head: { - totals: { - percentCovered: 45, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, }, } @@ -61,20 +60,19 @@ const node2 = { username: 'codecov-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, + head: { + bundleStatus: 'ERROR', + coverageStatus: 'COMPLETED', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 87, }, - changeCoverage: 0, - }, - head: { - totals: { - percentCovered: 45, - }, - bundleAnalysisReport: { - __typename: 'BundleAnalysisReport', - }, }, } @@ -87,20 +85,19 @@ const node3 = { username: 'codecov-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, + head: { + bundleStatus: 'ERROR', + coverageStatus: 'COMPLETED', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 92, }, - changeCoverage: 3, - }, - head: { - totals: { - percentCovered: 45, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, }, } @@ -215,27 +212,11 @@ describe('PullsTable', () => { expect(nameColumn).toBeInTheDocument() }) - it('renders coverage column', async () => { - const { queryClient } = setup({}) - render(, { wrapper: wrapper(queryClient) }) - - const patchColumn = await screen.findByText('Coverage on') - expect(patchColumn).toBeInTheDocument() - }) - - it('renders patch column', async () => { - const { queryClient } = setup({}) - render(, { wrapper: wrapper(queryClient) }) - - const patchColumn = await screen.findByText('Patch') - expect(patchColumn).toBeInTheDocument() - }) - - it('renders change column', async () => { + it('renders patch coverage column', async () => { const { queryClient } = setup({}) render(, { wrapper: wrapper(queryClient) }) - const patchColumn = await screen.findByText('Change from') + const patchColumn = await screen.findByText('Patch Coverage') expect(patchColumn).toBeInTheDocument() }) @@ -244,7 +225,7 @@ describe('PullsTable', () => { const { queryClient } = setup({ bundleAnalysisEnabled: true }) render(, { wrapper: wrapper(queryClient) }) - const bundleAnalysis = await screen.findByText('Bundle Analysis') + const bundleAnalysis = await screen.findByText('Bundle') expect(bundleAnalysis).toBeInTheDocument() }) }) @@ -257,7 +238,7 @@ describe('PullsTable', () => { const spinner = await screen.findByTestId('spinner') await waitForElementToBeRemoved(spinner) - const bundleAnalysis = screen.queryByText('Bundle Analysis') + const bundleAnalysis = screen.queryByText('Bundle') expect(bundleAnalysis).not.toBeInTheDocument() }) }) @@ -280,28 +261,12 @@ describe('PullsTable', () => { expect(patch).toBeInTheDocument() }) - it('renders change column', async () => { - const { queryClient } = setup({}) - render(, { wrapper: wrapper(queryClient) }) - - const change = await screen.findByText('0.00%') - expect(change).toBeInTheDocument() - }) - - it('renders coverage column', async () => { - const { queryClient } = setup({}) - render(, { wrapper: wrapper(queryClient) }) - - const coverage = await screen.findByText('75.00%') - expect(coverage).toBeInTheDocument() - }) - describe('bundle analysis is enabled', () => { it('renders bundle analysis column', async () => { const { queryClient } = setup({ bundleAnalysisEnabled: true }) render(, { wrapper: wrapper(queryClient) }) - const bundleAnalysis = await screen.findByText('Upload: ✅') + const bundleAnalysis = await screen.findByLabelText('Pending upload') expect(bundleAnalysis).toBeInTheDocument() }) }) @@ -314,7 +279,7 @@ describe('PullsTable', () => { const spinner = await screen.findByTestId('spinner') await waitForElementToBeRemoved(spinner) - const bundleAnalysis = screen.queryByText('Upload: ✅') + const bundleAnalysis = screen.queryByLabelText('Pending upload') expect(bundleAnalysis).not.toBeInTheDocument() }) }) diff --git a/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.tsx b/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.tsx index 391041dab3..22d9fc88a0 100644 --- a/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTable/PullsTable.tsx @@ -39,8 +39,6 @@ function LoadMoreTrigger({ intersectionRef }: { intersectionRef: any }) { const columnHelper = createColumnHelper<{ title: React.ReactElement patch: React.ReactElement - coverage: React.ReactElement - change: React.ReactElement bundleAnalysis: React.ReactElement }>() @@ -50,27 +48,9 @@ const baseColumns = [ header: () => 'Name', cell: ({ renderValue }) => renderValue(), }), - columnHelper.accessor('coverage', { - id: 'coverage', - header: () => ( - <> - Coverage on HEAD - - ), - cell: ({ renderValue }) => renderValue(), - }), columnHelper.accessor('patch', { id: 'patch', - header: () => 'Patch', - cell: ({ renderValue }) => renderValue(), - }), - columnHelper.accessor('change', { - id: 'change', - header: () => ( - <> - Change from BASE - - ), + header: () => 'Patch Coverage', cell: ({ renderValue }) => renderValue(), }), ] @@ -86,7 +66,7 @@ interface URLParams { repo: string } -export default function PullsTableTeam() { +export default function PullsTable() { const { provider, owner, repo } = useParams() const { ref, inView } = useInView() // we really need to TS'ify and generic'ify useLocationParams @@ -135,7 +115,7 @@ export default function PullsTableTeam() { return [ ...baseColumns, columnHelper.accessor('bundleAnalysis', { - header: 'Bundle Analysis', + header: 'Bundle', id: 'bundleAnalysis', cell: ({ renderValue }) => renderValue(), }), @@ -162,8 +142,6 @@ export default function PullsTableTeam() { - - {overview?.bundleAnalysisEnabled ? ( ) : null} diff --git a/src/pages/RepoPage/PullsTab/shared/Title/Title.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTable/Title/Title.spec.tsx similarity index 100% rename from src/pages/RepoPage/PullsTab/shared/Title/Title.spec.tsx rename to src/pages/RepoPage/PullsTab/PullsTable/Title/Title.spec.tsx diff --git a/src/pages/RepoPage/PullsTab/shared/Title/Title.tsx b/src/pages/RepoPage/PullsTab/PullsTable/Title/Title.tsx similarity index 100% rename from src/pages/RepoPage/PullsTab/shared/Title/Title.tsx rename to src/pages/RepoPage/PullsTab/PullsTable/Title/Title.tsx diff --git a/src/pages/RepoPage/PullsTab/shared/Title/index.ts b/src/pages/RepoPage/PullsTab/PullsTable/Title/index.ts similarity index 100% rename from src/pages/RepoPage/PullsTab/shared/Title/index.ts rename to src/pages/RepoPage/PullsTab/PullsTable/Title/index.ts diff --git a/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.spec.tsx index f9c946aedc..e7deb0be65 100644 --- a/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.spec.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.spec.tsx @@ -1,9 +1,11 @@ import TotalsNumber from 'ui/TotalsNumber' -import Coverage from './Coverage' -import { createPullsTableData } from './createPullsTableData' - -import Title from '../shared/Title' +import { + createPullsTableData, + ErroredUpload, + PendingUpload, +} from './createPullsTableData' +import Title from './Title' describe('createPullsTableData', () => { describe('pulls is undefined', () => { @@ -43,25 +45,28 @@ describe('createPullsTableData', () => { }) describe('pages has valid pulls', () => { - describe('compareWithBase __typename is not Comparison', () => { - it('returns undefined value for patch', () => { + describe('pull details are all non-null values', () => { + it('returns the title component', () => { const pullData = { - author: null, + author: { + username: 'cool-user', + avatarUrl: 'http://127.0.0.1/avatar-url', + }, pullId: 123, state: 'OPEN', - updatestamp: null, - title: null, + updatestamp: '2023-04-25T15:38:48.046832', + title: 'super cool pull request', + head: { + bundleStatus: 'COMPLETED', + coverageStatus: 'COMPLETED', + }, compareWithBase: { __typename: 'MissingBaseCommit', message: 'Missing base commit', }, - head: { - totals: { - percentCovered: 0, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', }, } as const @@ -69,56 +74,66 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.patch).toStrictEqual( - ) }) + }) - it('returns undefined value for change', () => { + describe('coverage upload has had an error', () => { + it('displays Upload: ❌', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'ERROR', + coverageStatus: 'ERROR', + }, compareWithBase: { + __typename: 'Comparison', + patchTotals: { + percentCovered: 100, + }, + changeCoverage: 0, + }, + bundleAnalysisCompareWithBase: { __typename: 'MissingBaseCommit', message: 'Missing base commit', }, - head: null, } as const const result = createPullsTableData({ pulls: [pullData], }) - expect(result[0]?.change).toStrictEqual( - - ) + expect(result[0]?.patch).toStrictEqual() }) }) - describe('compareWithBase __typename is Comparison', () => { - it('returns with patch value', () => { + describe('coverage upload is pending', () => { + it('displays Upload: ⏳', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'PENDING', + coverageStatus: 'PENDING', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { @@ -126,13 +141,9 @@ describe('createPullsTableData', () => { }, changeCoverage: 0, }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', }, } as const @@ -140,24 +151,22 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.patch).toStrictEqual( - - ) + expect(result[0]?.patch).toStrictEqual() }) + }) - it('returns with change value', () => { + describe('coverage upload is completed, and compareWithBase __typename is Comparison', () => { + it('renders the TotalsNumber component', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'COMPLETED', + coverageStatus: 'COMPLETED', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { @@ -165,13 +174,9 @@ describe('createPullsTableData', () => { }, changeCoverage: 0, }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', }, } as const @@ -179,68 +184,60 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.change).toStrictEqual( + expect(result[0]?.patch).toStrictEqual( ) }) + }) - describe('percent covered is null', () => { - it('returns patch total of 0', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: null, - }, - changeCoverage: 9, - }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableData({ - pulls: [pullData], - }) - - expect(result[0]?.patch).toStrictEqual( - - ) + describe('coverage upload does not match any conditions', () => { + it('displays `-`', () => { + const pullData = { + author: null, + pullId: 123, + state: 'OPEN', + updatestamp: null, + title: null, + head: { + bundleStatus: 'COMPLETED', + coverageStatus: 'COMPLETED', + }, + compareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', + }, + } as const + + const result = createPullsTableData({ + pulls: [pullData], }) + + expect(result[0]?.patch).toStrictEqual(

-

) }) }) - describe('pull details are all non-null values', () => { - it('returns with coverage value', () => { + describe('bundle upload has had an error', () => { + it('displays Upload: ❌', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'ERROR', + coverageStatus: 'COMPLETED', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { @@ -248,13 +245,9 @@ describe('createPullsTableData', () => { }, changeCoverage: 0, }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', }, } as const @@ -262,73 +255,55 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.coverage).toStrictEqual( - - ) + expect(result[0]?.bundleAnalysis).toStrictEqual() }) + }) - it('returns the title component', () => { + describe('bundle upload is pending', () => { + it('displays Upload: ⏳', () => { const pullData = { - author: { - username: 'cool-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, + author: null, pullId: 123, state: 'OPEN', - updatestamp: '2023-04-25T15:38:48.046832', - title: 'super cool pull request', + updatestamp: null, + title: null, + head: { + bundleStatus: 'PENDING', + coverageStatus: 'COMPLETED', + }, compareWithBase: { + __typename: 'Comparison', + patchTotals: { + percentCovered: 100, + }, + changeCoverage: 0, + }, + bundleAnalysisCompareWithBase: { __typename: 'MissingBaseCommit', message: 'Missing base commit', }, - head: { - totals: { - percentCovered: 0, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, } as const const result = createPullsTableData({ pulls: [pullData], }) - expect(result[0]?.title).toStrictEqual( - - ) + expect(result[0]?.bundleAnalysis).toStrictEqual(<PendingUpload />) }) }) - describe('bundleAnalysisReport __typename is not BundleAnalysisReport', () => { - it('returns x emoji', () => { + describe('bundleAnalysisCompareWithBase __typename is BundleAnalysisReport', () => { + it('returns successful upload', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'COMPLETED', + coverageStatus: 'COMPLETED', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { @@ -336,12 +311,12 @@ describe('createPullsTableData', () => { }, changeCoverage: 0, }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', + bundleAnalysisCompareWithBase: { + __typename: 'BundleAnalysisComparison', + bundleChange: { + size: { + uncompress: 1000, + }, }, }, } as const @@ -350,18 +325,22 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.bundleAnalysis).toStrictEqual(<>Upload: ❌</>) + expect(result[0]?.bundleAnalysis).toStrictEqual(<p>+1kB</p>) }) }) - describe('bundleAnalysisReport __typename is BundleAnalysisReport', () => { - it('returns successful upload', () => { + describe('bundle upload does not match any conditions', () => { + it('displays `-`', () => { const pullData = { author: null, pullId: 123, state: 'OPEN', updatestamp: null, title: null, + head: { + bundleStatus: 'COMPLETED', + coverageStatus: 'COMPLETED', + }, compareWithBase: { __typename: 'Comparison', patchTotals: { @@ -369,13 +348,9 @@ describe('createPullsTableData', () => { }, changeCoverage: 0, }, - head: { - totals: { - percentCovered: 9, - }, - bundleAnalysisReport: { - __typename: 'BundleAnalysisReport', - }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingBaseCommit', + message: 'Missing base commit', }, } as const @@ -383,7 +358,7 @@ describe('createPullsTableData', () => { pulls: [pullData], }) - expect(result[0]?.bundleAnalysis).toStrictEqual(<>Upload: ✅</>) + expect(result[0]?.bundleAnalysis).toStrictEqual(<p>-</p>) }) }) }) diff --git a/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.tsx b/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.tsx index 790e83ab9f..e51f67eb0a 100644 --- a/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.tsx +++ b/src/pages/RepoPage/PullsTab/PullsTable/createPullsTableData.tsx @@ -1,34 +1,72 @@ import isArray from 'lodash/isArray' -import { Pull } from 'services/pulls/usePulls' +import { + COMMIT_STATUS_COMPLETED, + COMMIT_STATUS_ERROR, + COMMIT_STATUS_PENDING, + Pull, +} from 'services/pulls/usePulls' +import { formatSizeToString } from 'shared/utils/bundleAnalysis' import TotalsNumber from 'ui/TotalsNumber' -import Coverage from './Coverage' +import Title from './Title' -import Title from '../shared/Title' +export const ErroredUpload = () => ( + <p> + Upload: <span aria-label="Errored upload">❌</span> + </p> +) +export const PendingUpload = () => ( + <p> + Upload: <span aria-label="Pending upload">⏳</span> + </p> +) export const createPullsTableData = ({ pulls }: { pulls?: Array<Pull> }) => { if (!isArray(pulls)) { return [] } + return pulls.filter(Boolean).map((pull: Pull) => { - let patch, change - if (pull?.compareWithBase?.__typename === 'Comparison') { - patch = pull?.compareWithBase?.patchTotals?.percentCovered ?? 0 - change = pull?.compareWithBase?.changeCoverage ?? 0 + let patch = <p>-</p> + if (pull?.head?.coverageStatus === COMMIT_STATUS_ERROR) { + patch = <ErroredUpload /> + } else if (pull?.head?.coverageStatus === COMMIT_STATUS_PENDING) { + patch = <PendingUpload /> + } else if ( + pull?.head?.coverageStatus === COMMIT_STATUS_COMPLETED && + pull?.compareWithBase?.__typename === 'Comparison' + ) { + const percent = pull?.compareWithBase?.patchTotals?.percentCovered ?? NaN + patch = ( + <TotalsNumber + plain={true} + large={false} + light={false} + value={percent} + showChange={false} + /> + ) } const updatestamp = pull?.updatestamp ?? undefined const title = pull?.title ?? 'Pull Request' const pullId = pull?.pullId ?? NaN - let bundleAnalysis = undefined - if ( - pull?.head?.bundleAnalysisReport?.__typename === 'BundleAnalysisReport' + let bundleAnalysis = <p>-</p> + if (pull?.head?.bundleStatus === COMMIT_STATUS_ERROR) { + bundleAnalysis = <ErroredUpload /> + } else if (pull?.head?.bundleStatus === COMMIT_STATUS_PENDING) { + bundleAnalysis = <PendingUpload /> + } else if ( + pull?.head?.bundleStatus === COMMIT_STATUS_COMPLETED && + pull?.bundleAnalysisCompareWithBase?.__typename === + 'BundleAnalysisComparison' ) { - bundleAnalysis = <>Upload: ✅</> - } else { - bundleAnalysis = <>Upload: ❌</> + const change = + pull?.bundleAnalysisCompareWithBase?.bundleChange?.size.uncompress + const content = `${change > 0 ? '+' : ''}${formatSizeToString(change)}` + bundleAnalysis = <p>{content}</p> } return { @@ -44,32 +82,7 @@ export const createPullsTableData = ({ pulls }: { pulls?: Array<Pull> }) => { compareWithBaseType={pull?.compareWithBase?.__typename} /> ), - patch: ( - <TotalsNumber - plain={true} - large={false} - light={false} - value={patch} - showChange={false} - /> - ), - coverage: ( - <Coverage - head={pull?.head} - state={pull?.state ?? 'OPEN'} - pullId={pullId} - /> - ), - change: ( - <TotalsNumber - value={change} - showChange - data-testid="change-value" - plain={true} - light={false} - large={false} - /> - ), + patch, bundleAnalysis, } }) diff --git a/src/pages/RepoPage/PullsTab/PullsTable/index.js b/src/pages/RepoPage/PullsTab/PullsTable/index.ts similarity index 100% rename from src/pages/RepoPage/PullsTab/PullsTable/index.js rename to src/pages/RepoPage/PullsTab/PullsTable/index.ts diff --git a/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.spec.tsx deleted file mode 100644 index 5a7d94863c..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.spec.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { - render, - screen, - waitForElementToBeRemoved, -} from '@testing-library/react' -import { graphql } from 'msw' -import { setupServer } from 'msw/node' -import { mockIsIntersecting } from 'react-intersection-observer/test-utils' -import { MemoryRouter, Route } from 'react-router-dom' - -import PullsTableTeam from './PullsTableTeam' - -const mockRepoOverview = (bundleAnalysisEnabled = false) => ({ - owner: { - repository: { - __typename: 'Repository', - private: false, - defaultBranch: 'main', - oldestCommitAt: '2022-10-10T11:59:59', - coverageEnabled: false, - bundleAnalysisEnabled, - languages: ['javascript'], - testAnalyticsEnabled: true, - }, - }, -}) - -const node1 = { - pullId: 1, - title: 'first pull', - state: 'MERGED', - updatestamp: '2023-10-11T00:00.000000', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 75, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'BundleAnalysisReport', - }, - }, -} - -const node2 = { - pullId: 2, - title: 'second pull', - state: 'MERGED', - updatestamp: '2023-10-12T00:00.000000', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 87, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, -} - -const node3 = { - pullId: 3, - title: 'third pull', - state: 'MERGED', - updatestamp: '2023-10-13T00:00.000000', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 92, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, -} - -const server = setupServer() -const wrapper = - (queryClient: QueryClient): React.FC<React.PropsWithChildren> => - ({ children }) => - ( - <QueryClientProvider client={queryClient}> - <MemoryRouter initialEntries={['/gh/codecov/cool-repo/pulls']}> - <Route path="/:provider/:owner/:repo/pulls">{children}</Route> - </MemoryRouter> - </QueryClientProvider> - ) - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -interface SetupArgs { - noEntries?: boolean - bundleAnalysisEnabled?: boolean -} - -describe('PullsTableTeam', () => { - function setup({ - noEntries = false, - bundleAnalysisEnabled = false, - }: SetupArgs) { - const queryClient = new QueryClient() - - server.use( - graphql.query('GetRepoOverview', (req, res, ctx) => { - return res( - ctx.status(200), - ctx.data(mockRepoOverview(bundleAnalysisEnabled)) - ) - }), - graphql.query('GetPullsTeam', (req, res, ctx) => { - if (noEntries) { - return res( - ctx.status(200), - ctx.data({ - owner: { - repository: { - __typename: 'Repository', - pulls: { - edges: [], - pageInfo: { - hasNextPage: false, - endCursor: null, - }, - }, - }, - }, - }) - ) - } - - return res( - ctx.status(200), - ctx.data({ - owner: { - repository: { - __typename: 'Repository', - pulls: { - edges: req.variables.after - ? [ - { - node: node3, - }, - ] - : [ - { - node: node1, - }, - { - node: node2, - }, - ], - pageInfo: { - hasNextPage: req.variables.after ? false : true, - endCursor: req.variables.after - ? 'aa' - : 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', - }, - }, - }, - }, - }) - ) - }) - ) - - return { queryClient } - } - - describe('renders table headers', () => { - it('renders name column', async () => { - const { queryClient } = setup({}) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const nameColumn = await screen.findByText('Name') - expect(nameColumn).toBeInTheDocument() - }) - - it('renders patch column', async () => { - const { queryClient } = setup({}) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const patchColumn = await screen.findByText('Patch %') - expect(patchColumn).toBeInTheDocument() - }) - - describe('bundle analysis is enabled', () => { - it('renders bundle analysis column', async () => { - const { queryClient } = setup({ bundleAnalysisEnabled: true }) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const bundleAnalysis = await screen.findByText('Bundle Analysis') - expect(bundleAnalysis).toBeInTheDocument() - }) - }) - - describe('bundle analysis is disabled', () => { - it('does not render bundle analysis column', async () => { - const { queryClient } = setup({ bundleAnalysisEnabled: false }) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const spinner = await screen.findByTestId('spinner') - await waitForElementToBeRemoved(spinner) - - const bundleAnalysis = screen.queryByText('Bundle Analysis') - expect(bundleAnalysis).not.toBeInTheDocument() - }) - }) - }) - - describe('renders table body', () => { - it('renders name column', async () => { - const { queryClient } = setup({}) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const title = await screen.findByText('first pull') - expect(title).toBeInTheDocument() - }) - - it('renders patch column', async () => { - const { queryClient } = setup({}) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const patch = await screen.findByText('87.00%') - expect(patch).toBeInTheDocument() - }) - - describe('bundle analysis is enabled', () => { - it('renders bundle analysis column', async () => { - const { queryClient } = setup({ bundleAnalysisEnabled: true }) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const bundleAnalysis = await screen.findByText('Upload: ✅') - expect(bundleAnalysis).toBeInTheDocument() - }) - }) - - describe('bundle analysis is disabled', () => { - it('does not render bundle analysis column', async () => { - const { queryClient } = setup({ bundleAnalysisEnabled: false }) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const spinner = await screen.findByTestId('spinner') - await waitForElementToBeRemoved(spinner) - - const bundleAnalysis = screen.queryByText('Upload: ✅') - expect(bundleAnalysis).not.toBeInTheDocument() - }) - }) - }) - - describe('no data is returned', () => { - it('renders error message', async () => { - const { queryClient } = setup({ noEntries: true }) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const errorMessage = await screen.findByText('No pulls found') - expect(errorMessage).toBeInTheDocument() - }) - }) - - describe('infinite scrolling', () => { - it('loads next page', async () => { - const { queryClient } = setup({}) - render(<PullsTableTeam />, { wrapper: wrapper(queryClient) }) - - const loading = await screen.findByText('Loading') - mockIsIntersecting(loading, true) - await waitForElementToBeRemoved(loading) - - const thirdPR = await screen.findByText('third pull') - expect(thirdPR).toBeInTheDocument() - }) - }) -}) diff --git a/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.tsx b/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.tsx deleted file mode 100644 index 4807e6e6f2..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTableTeam/PullsTableTeam.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, -} from '@tanstack/react-table' -import cs from 'classnames' -import isEmpty from 'lodash/isEmpty' -import { useEffect, useMemo } from 'react' -import { useInView } from 'react-intersection-observer' -import { useParams } from 'react-router-dom' - -import { useLocationParams } from 'services/navigation' -import { usePullsTeam } from 'services/pulls/usePullsTeam' -import { useRepoOverview } from 'services/repo' -import Spinner from 'ui/Spinner' - -import { createPullsTableTeamData } from './createPullsTableTeamData' - -import { orderingEnum } from '../enums' - -const Loader = () => ( - <div className="mb-4 flex justify-center pt-4"> - <Spinner /> - </div> -) - -function LoadMoreTrigger({ intersectionRef }: { intersectionRef: any }) { - return ( - <span - ref={intersectionRef} - className="invisible relative top-[-65px] block leading-[0]" - > - Loading - </span> - ) -} - -const columnHelper = createColumnHelper<{ - title: React.ReactElement - patch: React.ReactElement - bundleAnalysis: React.ReactElement -}>() - -const baseColumns = [ - columnHelper.accessor('title', { - id: 'title', - header: () => 'Name', - cell: ({ renderValue }) => renderValue(), - }), - columnHelper.accessor('patch', { - id: 'patch', - header: () => 'Patch %', - cell: ({ renderValue }) => renderValue(), - }), -] - -const defaultParams = { - order: orderingEnum.Newest.order, - prStates: [], -} - -interface URLParams { - provider: string - owner: string - repo: string -} - -export default function PullsTableTeam() { - const { provider, owner, repo } = useParams<URLParams>() - const { ref, inView } = useInView() - // we really need to TS'ify and generic'ify useLocationParams - const { params } = useLocationParams(defaultParams) - const { data: overview } = useRepoOverview({ provider, owner, repo }) - - const { - data: pullsData, - isLoading: pullsIsLoading, - fetchNextPage: fetchNextPullsPage, - hasNextPage: pullsHasNextPage, - isFetchingNextPage: pullsIsFetchingNextPage, - } = usePullsTeam({ - provider, - owner, - repo, - filters: { - // useLocationParams needs to be updated to have full types - // @ts-expect-errors - state: params?.prStates, - }, - // useLocationParams needs to be updated to have full types - // @ts-expect-error - orderingDirection: params?.order, - }) - - useEffect(() => { - if (inView && pullsHasNextPage) { - fetchNextPullsPage() - } - }, [fetchNextPullsPage, inView, pullsHasNextPage]) - - const tableData = useMemo( - () => - createPullsTableTeamData({ - pages: pullsData?.pages, - }), - [pullsData?.pages] - ) - - const columns = useMemo(() => { - if ( - overview?.bundleAnalysisEnabled && - !baseColumns.some((column) => column.id === 'bundleAnalysis') - ) { - return [ - ...baseColumns, - columnHelper.accessor('bundleAnalysis', { - header: 'Bundle Analysis', - id: 'bundleAnalysis', - cell: ({ renderValue }) => renderValue(), - }), - ] - } - - return baseColumns - }, [overview?.bundleAnalysisEnabled]) - - const table = useReactTable({ - columns, - data: tableData, - getCoreRowModel: getCoreRowModel(), - }) - - if (isEmpty(tableData) && !pullsIsLoading) { - return <p className="m-4">No pulls found</p> - } - - return ( - <> - <div className="tableui"> - <table> - <colgroup> - <col className="w-full @sm/table:w-10/12" /> - <col className="@sm/table:w-2/12" /> - {overview?.bundleAnalysisEnabled ? ( - <col className="@sm/table:w-1/12" /> - ) : null} - </colgroup> - <thead> - {table.getHeaderGroups().map((headerGroup) => ( - <tr key={headerGroup.id}> - {headerGroup.headers.map((header) => ( - <th key={header.id} scope="col"> - <div - className={cs('text-xs', { - 'text-left': header.id === 'title', - 'text-right': header.id !== 'title', - })} - > - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - </div> - </th> - ))} - </tr> - ))} - </thead> - <tbody> - {pullsIsLoading ? ( - <tr> - <td> - <Loader /> - </td> - </tr> - ) : ( - table.getRowModel().rows.map((row) => ( - <tr key={row.id}> - {row.getVisibleCells().map((cell) => ( - <td - key={cell.id} - className={cs('text-sm', { - 'w-full max-w-0 font-medium @md/table:w-auto @md/table:max-w-none': - cell.column.id === 'title', - 'text-right': cell.column.id !== 'title', - })} - > - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - </td> - ))} - </tr> - )) - )} - </tbody> - </table> - </div> - {pullsIsFetchingNextPage ? <Loader /> : null} - {pullsHasNextPage ? <LoadMoreTrigger intersectionRef={ref} /> : null} - </> - ) -} diff --git a/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.spec.tsx b/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.spec.tsx deleted file mode 100644 index bb50c7ee4b..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.spec.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import TotalsNumber from 'ui/TotalsNumber' - -import { createPullsTableTeamData } from './createPullsTableTeamData' - -import Title from '../shared/Title' - -describe('createPullsTableTeamData', () => { - describe('pages is undefined', () => { - it('returns an empty array', () => { - const result = createPullsTableTeamData({}) - - expect(result).toStrictEqual([]) - }) - }) - - describe('pages is an empty array', () => { - it('returns an empty array', () => { - const result = createPullsTableTeamData({ pages: [] }) - - expect(result).toStrictEqual([]) - }) - }) - - describe('pulls in pages is empty', () => { - it('returns an empty array', () => { - const result = createPullsTableTeamData({ - pages: [{ pulls: [] }, { pulls: [] }], - }) - - expect(result).toStrictEqual([]) - }) - }) - - describe('pages has valid pulls', () => { - describe('compareWithBase __typename is not Comparison', () => { - it('returns dash', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'MissingBaseCommit', - message: 'Missing base commit', - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.patch).toStrictEqual(<p className="text-right">-</p>) - }) - }) - - describe('compareWithBase __typename is Comparison', () => { - it('returns with patch value', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 100, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.patch).toStrictEqual( - <TotalsNumber - large={false} - light={false} - plain={true} - showChange={false} - value={100} - /> - ) - }) - - describe('percent covered is null', () => { - it('returns patch total of 0', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: null, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.patch).toStrictEqual( - <TotalsNumber - large={false} - light={false} - plain={true} - showChange={false} - value={0} - /> - ) - }) - }) - }) - - describe('bundleAnalysisReport __typename is not BundleAnalysisReport', () => { - it('returns x emoji', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 100, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.bundleAnalysis).toStrictEqual(<>Upload: ❌</>) - }) - }) - - describe('bundleAnalysisReport __typename is BundleAnalysisReport', () => { - it('returns checkmark emoji', () => { - const pullData = { - author: null, - pullId: 123, - state: 'OPEN', - updatestamp: null, - title: null, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 100, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'BundleAnalysisReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.bundleAnalysis).toStrictEqual(<>Upload: ✅</>) - }) - }) - - describe('pull details are all non-null values', () => { - it('returns the title component', () => { - const pullData = { - author: { - username: 'cool-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - pullId: 123, - state: 'OPEN', - updatestamp: '2023-04-25T15:38:48.046832', - title: 'super cool pull request', - compareWithBase: { - __typename: 'MissingBaseCommit', - message: 'Missing base commit', - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, - } as const - - const result = createPullsTableTeamData({ - pages: [{ pulls: [pullData] }], - }) - - expect(result[0]?.title).toStrictEqual( - <Title - author={{ - avatarUrl: 'http://127.0.0.1/avatar-url', - username: 'cool-user', - }} - compareWithBaseType="MissingBaseCommit" - pullId={123} - title="super cool pull request" - updatestamp="2023-04-25T15:38:48.046832" - /> - ) - }) - }) - }) -}) diff --git a/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.tsx b/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.tsx deleted file mode 100644 index 41e4ab8093..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTableTeam/createPullsTableTeamData.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import isArray from 'lodash/isArray' -import isEmpty from 'lodash/isEmpty' - -import { Pull } from 'services/pulls/usePullsTeam' -import TotalsNumber from 'ui/TotalsNumber' - -import Title from '../shared/Title' - -export const createPullsTableTeamData = ({ - pages, -}: { - pages?: Array<{ pulls: Array<Pull> }> -}) => { - if (!isArray(pages)) { - return [] - } - - const pulls = pages?.map((pull) => pull?.pulls).flat() - - if (isEmpty(pulls)) { - return [] - } - - return pulls.filter(Boolean).map((pull) => { - let patch = <p className="text-right">-</p> - if (pull?.compareWithBase?.__typename === 'Comparison') { - const patchPercentage = - pull?.compareWithBase?.patchTotals?.percentCovered ?? 0 - patch = ( - <TotalsNumber - plain={true} - large={false} - light={false} - value={patchPercentage} - showChange={false} - /> - ) - } - - let bundleAnalysis = undefined - if ( - pull?.head?.bundleAnalysisReport?.__typename === 'BundleAnalysisReport' - ) { - bundleAnalysis = <>Upload: ✅</> - } else { - bundleAnalysis = <>Upload: ❌</> - } - - const updatestamp = pull?.updatestamp ?? undefined - const title = pull?.title ?? 'Pull Request' - const pullId = pull?.pullId ?? NaN - - return { - title: ( - <Title - author={{ - username: pull?.author?.username, - avatarUrl: pull?.author?.avatarUrl, - }} - pullId={pullId} - title={title} - updatestamp={updatestamp} - compareWithBaseType={pull?.compareWithBase?.__typename} - /> - ), - patch, - bundleAnalysis, - } - }) -} diff --git a/src/pages/RepoPage/PullsTab/PullsTableTeam/index.ts b/src/pages/RepoPage/PullsTab/PullsTableTeam/index.ts deleted file mode 100644 index e42e4e5d23..0000000000 --- a/src/pages/RepoPage/PullsTab/PullsTableTeam/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './PullsTableTeam' diff --git a/src/services/commits/useCommits.tsx b/src/services/commits/useCommits.tsx index 9dfa6ca7bc..ffd568aa05 100644 --- a/src/services/commits/useCommits.tsx +++ b/src/services/commits/useCommits.tsx @@ -21,15 +21,9 @@ import Api from 'shared/api' import { mapEdges } from 'shared/utils/graphql' import A from 'ui/A' -const CommitStatuses = { - COMPLETE: 'COMPLETED', - PENDING: 'PENDING', - ERROR: 'ERROR', -} as const - -const CommitStatusesEnumSchema = z.nativeEnum(CommitStatuses) - -export type CommitStatusesEnum = z.infer<typeof CommitStatusesEnumSchema> +export const COMMIT_STATUS_COMPLETED = 'COMPLETED' +export const COMMIT_STATUS_ERROR = 'ERROR' +export const COMMIT_STATUS_PENDING = 'PENDING' const AuthorSchema = z.object({ username: z.string().nullable(), @@ -37,11 +31,13 @@ const AuthorSchema = z.object({ }) const CommitStatusSchema = z.union([ - z.literal(CommitStatusesEnumSchema.enum.COMPLETE), - z.literal(CommitStatusesEnumSchema.enum.ERROR), - z.literal(CommitStatusesEnumSchema.enum.PENDING), + z.literal(COMMIT_STATUS_COMPLETED), + z.literal(COMMIT_STATUS_ERROR), + z.literal(COMMIT_STATUS_PENDING), ]) +export type CommitStatuses = z.infer<typeof CommitStatusSchema> + const CommitSchema = z.object({ ciPassed: z.boolean().nullable(), message: z.string().nullable(), @@ -233,7 +229,7 @@ interface UseCommitsArgs { branchName?: string pullId?: number search?: string - coverageStatus?: Array<CommitStatusesEnum> + coverageStatus?: Array<CommitStatuses> } opts?: UseInfiniteQueryOptions<GetCommitsReturn> } diff --git a/src/services/pulls/index.ts b/src/services/pulls/index.ts index cea6b1eac2..76d7b6e620 100644 --- a/src/services/pulls/index.ts +++ b/src/services/pulls/index.ts @@ -1,2 +1 @@ export * from './usePulls' -export { usePullsTeam } from './usePullsTeam' diff --git a/src/services/pulls/usePulls.spec.tsx b/src/services/pulls/usePulls.spec.tsx index 02a68c1fad..0645067177 100644 --- a/src/services/pulls/usePulls.spec.tsx +++ b/src/services/pulls/usePulls.spec.tsx @@ -11,23 +11,22 @@ const node1 = { state: 'MERGED', updatestamp: '20-2-2021', author: { - username: 'Rula', + username: 'cool-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, head: { - totals: { - percentCovered: 90, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleStatus: 'PENDING', + coverageStatus: 'PENDING', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 87, }, - changeCoverage: 20, }, } @@ -37,23 +36,22 @@ const node2 = { state: 'MERGED', updatestamp: '20-2-2021', author: { - username: 'Rula', + username: 'cool-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, head: { - totals: { - percentCovered: 90, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleStatus: 'PENDING', + coverageStatus: 'PENDING', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 87, }, - changeCoverage: 20, }, } @@ -63,23 +61,22 @@ const node3 = { state: 'MERGED', updatestamp: '20-2-2021', author: { - username: 'Rula', + username: 'cool-user', avatarUrl: 'http://127.0.0.1/avatar-url', }, head: { - totals: { - percentCovered: 90, - }, - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, + bundleStatus: 'PENDING', + coverageStatus: 'PENDING', + }, + bundleAnalysisCompareWithBase: { + __typename: 'MissingHeadReport', + message: 'Missing head report', }, compareWithBase: { __typename: 'Comparison', patchTotals: { percentCovered: 87, }, - changeCoverage: 20, }, } diff --git a/src/services/pulls/usePulls.tsx b/src/services/pulls/usePulls.tsx index 8cafa9f3b1..8f89d48620 100644 --- a/src/services/pulls/usePulls.tsx +++ b/src/services/pulls/usePulls.tsx @@ -26,6 +26,16 @@ const PullStatesSchema = z.union([ z.literal('MERGED'), ]) +export const COMMIT_STATUS_COMPLETED = 'COMPLETED' +export const COMMIT_STATUS_ERROR = 'ERROR' +export const COMMIT_STATUS_PENDING = 'PENDING' + +const CommitStatusSchema = z.union([ + z.literal(COMMIT_STATUS_COMPLETED), + z.literal(COMMIT_STATUS_ERROR), + z.literal(COMMIT_STATUS_PENDING), +]) + type PullStates = z.infer<typeof PullStatesSchema> const PullSchema = z @@ -42,19 +52,27 @@ const PullSchema = z .nullable(), head: z .object({ - totals: z - .object({ - percentCovered: z.number().nullable(), - }) - .nullable(), - bundleAnalysisReport: z - .discriminatedUnion('__typename', [ - z.object({ __typename: z.literal('BundleAnalysisReport') }), - z.object({ __typename: z.literal('MissingHeadReport') }), - ]) - .nullable(), + bundleStatus: CommitStatusSchema.nullable(), + coverageStatus: CommitStatusSchema.nullable(), }) .nullable(), + bundleAnalysisCompareWithBase: z + .discriminatedUnion('__typename', [ + z.object({ + __typename: z.literal('BundleAnalysisComparison'), + bundleChange: z.object({ + size: z.object({ + uncompress: z.number(), + }), + }), + }), + FirstPullRequestSchema, + MissingBaseCommitSchema, + MissingBaseReportSchema, + MissingHeadCommitSchema, + MissingHeadReportSchema, + ]) + .nullable(), compareWithBase: z .discriminatedUnion('__typename', [ z.object({ @@ -64,7 +82,6 @@ const PullSchema = z percentCovered: z.number().nullable(), }) .nullable(), - changeCoverage: z.number().nullable(), }), FirstPullRequestSchema, MissingBaseCommitSchema, @@ -141,11 +158,32 @@ query GetPulls( avatarUrl } head { - totals { - percentCovered + bundleStatus + coverageStatus + } + bundleAnalysisCompareWithBase { + __typename + ... on BundleAnalysisComparison { + bundleChange { + size { + uncompress + } + } + } + ... on FirstPullRequest { + message } - bundleAnalysisReport { - __typename + ... on MissingBaseCommit { + message + } + ... on MissingHeadCommit { + message + } + ... on MissingBaseReport { + message + } + ... on MissingHeadReport { + message } } compareWithBase { @@ -154,7 +192,6 @@ query GetPulls( patchTotals { percentCovered } - changeCoverage } ... on FirstPullRequest { message diff --git a/src/services/pulls/usePullsTeam.spec.tsx b/src/services/pulls/usePullsTeam.spec.tsx deleted file mode 100644 index 64d7b8794b..0000000000 --- a/src/services/pulls/usePullsTeam.spec.tsx +++ /dev/null @@ -1,433 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { renderHook, waitFor } from '@testing-library/react' -import { graphql } from 'msw' -import { setupServer } from 'msw/node' - -import { usePullsTeam } from './usePullsTeam' - -const node1 = { - pullId: 1, - title: 'first pull', - state: 'MERGED', - updatestamp: '20-2-2021', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 87, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, -} - -const node2 = { - pullId: 2, - title: 'second pull', - state: 'MERGED', - updatestamp: '20-2-2021', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 87, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, -} - -const node3 = { - pullId: 3, - title: 'third pull', - state: 'MERGED', - updatestamp: '20-2-2021', - author: { - username: 'codecov-user', - avatarUrl: 'http://127.0.0.1/avatar-url', - }, - compareWithBase: { - __typename: 'Comparison', - patchTotals: { - percentCovered: 87, - }, - }, - head: { - bundleAnalysisReport: { - __typename: 'MissingHeadReport', - }, - }, -} - -const mockNotFoundError = { - owner: { - repository: { - __typename: 'NotFoundError', - message: 'commit not found', - }, - }, -} - -const mockOwnerNotActivatedError = { - owner: { - repository: { - __typename: 'OwnerNotActivatedError', - message: 'owner not activated', - }, - }, -} - -const mockNullOwnerData = { - owner: null, -} - -const mockUnsuccessfulParseError = {} - -const server = setupServer() -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, -}) - -const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => ( - <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> -) - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - queryClient.clear() - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -const provider = 'gh' -const owner = 'codecov' -const repo = 'gazebo' - -interface SetupArgs { - isNotFoundError?: boolean - isOwnerNotActivatedError?: boolean - isUnsuccessfulParseError?: boolean - isNullOwner?: boolean -} - -describe('GetPulls', () => { - function setup({ - isNotFoundError = false, - isOwnerNotActivatedError = false, - isUnsuccessfulParseError = false, - isNullOwner = false, - }: SetupArgs) { - server.use( - graphql.query('GetPullsTeam', (req, res, ctx) => { - if (isNotFoundError) { - return res(ctx.status(200), ctx.data(mockNotFoundError)) - } else if (isOwnerNotActivatedError) { - return res(ctx.status(200), ctx.data(mockOwnerNotActivatedError)) - } else if (isUnsuccessfulParseError) { - return res(ctx.status(200), ctx.data(mockUnsuccessfulParseError)) - } else if (isNullOwner) { - return res(ctx.status(200), ctx.data(mockNullOwnerData)) - } - - return res( - ctx.status(200), - ctx.data({ - owner: { - repository: { - __typename: 'Repository', - pulls: { - edges: req.variables.after - ? [ - { - node: node3, - }, - ] - : [ - { - node: node1, - }, - { - node: node2, - }, - ], - pageInfo: { - hasNextPage: req.variables.after ? false : true, - endCursor: req.variables.after - ? 'aa' - : 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', - }, - }, - }, - }, - }) - ) - }) - ) - } - - describe('when __typename is Repository', () => { - describe('when valid data is returned', () => { - describe('when data is loaded', () => { - it('returns expected pulls nodes', async () => { - setup({}) - - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => result.current.isLoading) - await waitFor(() => !result.current.isLoading) - - await waitFor(() => - expect(result.current.data?.pages).toEqual([ - { - pageInfo: { - endCursor: 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', - hasNextPage: true, - }, - pulls: [node1, node2], - }, - ]) - ) - }) - }) - - describe('when call next page', () => { - it('returns prev and next page pulls of the user', async () => { - setup({}) - - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => result.current.isFetching) - await waitFor(() => !result.current.isFetching) - - result.current.fetchNextPage() - - await waitFor(() => result.current.isFetching) - await waitFor(() => !result.current.isFetching) - - await waitFor(() => - expect(result.current.data?.pages).toEqual([ - { - pageInfo: { - endCursor: 'MjAyMC0wOC0xMSAxNzozMDowMiswMDowMHwxMDA=', - hasNextPage: true, - }, - pulls: [node1, node2], - }, - { - pageInfo: { - endCursor: 'aa', - hasNextPage: false, - }, - pulls: [node3], - }, - ]) - ) - }) - }) - }) - - describe('when null owner is returned', () => { - it('returns an empty list', async () => { - setup({ isNullOwner: true }) - - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => result.current.isLoading) - await waitFor(() => !result.current.isLoading) - - await waitFor(() => - expect(result.current.data?.pages).toEqual([ - { - pageInfo: null, - pulls: [], - }, - ]) - ) - }) - }) - }) - - describe('when __typename is NotFoundError', () => { - let oldConsoleError = console.error - - beforeEach(() => { - console.error = () => null - }) - - afterEach(() => { - console.error = oldConsoleError - }) - - it('throws a 404', async () => { - setup({ isNotFoundError: true }) - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => expect(result.current.isError).toBeTruthy()) - await waitFor(() => - expect(result.current.error).toEqual( - expect.objectContaining({ - status: 404, - }) - ) - ) - }) - }) - - describe('when __typename is OwnerNotActivatedError', () => { - let oldConsoleError = console.error - - beforeEach(() => { - console.error = () => null - }) - - afterEach(() => { - console.error = oldConsoleError - }) - - it('throws a 403', async () => { - setup({ isOwnerNotActivatedError: true }) - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => expect(result.current.isError).toBeTruthy()) - await waitFor(() => - expect(result.current.error).toEqual( - expect.objectContaining({ - status: 403, - }) - ) - ) - }) - }) - - describe('unsuccessful parse of zod schema', () => { - let oldConsoleError = console.error - - beforeEach(() => { - console.error = () => null - }) - - afterEach(() => { - console.error = oldConsoleError - }) - - it('throws a 404', async () => { - setup({ isUnsuccessfulParseError: true }) - const { result } = renderHook( - () => - usePullsTeam({ - provider, - owner, - repo, - filters: { - state: 'MERGED', - }, - orderingDirection: 'ASC', - }), - { - wrapper, - } - ) - - await waitFor(() => expect(result.current.isError).toBeTruthy()) - await waitFor(() => - expect(result.current.error).toEqual( - expect.objectContaining({ - status: 404, - }) - ) - ) - }) - }) -}) diff --git a/src/services/pulls/usePullsTeam.tsx b/src/services/pulls/usePullsTeam.tsx deleted file mode 100644 index 1eadb1908d..0000000000 --- a/src/services/pulls/usePullsTeam.tsx +++ /dev/null @@ -1,278 +0,0 @@ -import { - useInfiniteQuery, - type UseInfiniteQueryOptions, -} from '@tanstack/react-query' -import { z } from 'zod' - -import { - FirstPullRequestSchema, - MissingBaseCommitSchema, - MissingBaseReportSchema, - MissingComparisonSchema, - MissingHeadCommitSchema, - MissingHeadReportSchema, -} from 'services/comparison' -import { - RepoNotFoundErrorSchema, - RepoOwnerNotActivatedErrorSchema, -} from 'services/repo' -import Api from 'shared/api' -import { mapEdges } from 'shared/utils/graphql' -import A from 'ui/A' - -const PullStatesSchema = z.union([ - z.literal('OPEN'), - z.literal('CLOSED'), - z.literal('MERGED'), -]) - -type PullStates = z.infer<typeof PullStatesSchema> - -const PullSchema = z - .object({ - pullId: z.number(), - title: z.string().nullable(), - state: PullStatesSchema, - updatestamp: z.string().nullable(), - author: z - .object({ - username: z.string().nullable(), - avatarUrl: z.string(), - }) - .nullable(), - head: z - .object({ - bundleAnalysisReport: z - .discriminatedUnion('__typename', [ - z.object({ __typename: z.literal('BundleAnalysisReport') }), - z.object({ __typename: z.literal('MissingHeadReport') }), - ]) - .nullable(), - }) - .nullable(), - compareWithBase: z - .discriminatedUnion('__typename', [ - z.object({ - __typename: z.literal('Comparison'), - patchTotals: z - .object({ - percentCovered: z.number().nullable(), - }) - .nullable(), - }), - FirstPullRequestSchema, - MissingBaseCommitSchema, - MissingBaseReportSchema, - MissingComparisonSchema, - MissingHeadCommitSchema, - MissingHeadReportSchema, - ]) - .nullable(), - }) - .nullable() - -export type Pull = z.infer<typeof PullSchema> - -const PageInfoSchema = z.object({ - hasNextPage: z.boolean(), - endCursor: z.string().nullable(), -}) - -type PageInfo = z.infer<typeof PageInfoSchema> - -const GetPullsSchema = z.object({ - owner: z - .object({ - repository: z.discriminatedUnion('__typename', [ - z.object({ - __typename: z.literal('Repository'), - pulls: z - .object({ - edges: z.array( - z - .object({ - node: PullSchema, - }) - .nullable() - ), - pageInfo: PageInfoSchema, - }) - .nullable(), - }), - RepoNotFoundErrorSchema, - RepoOwnerNotActivatedErrorSchema, - ]), - }) - .nullable(), -}) - -const query = ` -query GetPullsTeam( - $owner: String! - $repo: String! - $orderingDirection: OrderingDirection - $filters: PullsSetFilters - $after: String -) { - owner(username: $owner) { - repository(name: $repo) { - __typename - ... on Repository { - pulls( - orderingDirection: $orderingDirection - filters: $filters - first: 20 - after: $after - ) { - edges { - node { - pullId - title - state - updatestamp - author { - username - avatarUrl - } - head { - bundleAnalysisReport { - __typename - } - } - compareWithBase { - __typename - ... on Comparison { - patchTotals { - percentCovered - } - } - ... on FirstPullRequest { - message - } - ... on MissingBaseCommit { - message - } - ... on MissingHeadCommit { - message - } - ... on MissingComparison { - message - } - ... on MissingBaseReport { - message - } - ... on MissingHeadReport { - message - } - } - } - } - pageInfo { - hasNextPage - endCursor - } - } - } - ... on NotFoundError { - message - } - ... on OwnerNotActivatedError { - message - } - } - } -}` - -type GetPullsTeamReturn = { pulls: Array<Pull>; pageInfo: PageInfo | null } - -interface UsePullsTeamArgs { - provider: string - owner: string - repo: string - filters: { - state: PullStates - } - orderingDirection: 'ASC' | 'DESC' - opts?: UseInfiniteQueryOptions<GetPullsTeamReturn> -} - -export function usePullsTeam({ - provider, - owner, - repo, - filters, - orderingDirection, - opts = {}, -}: UsePullsTeamArgs) { - return useInfiniteQuery({ - queryKey: [ - 'GetPullsTeam', - provider, - owner, - repo, - filters, - orderingDirection, - ], - queryFn: ({ pageParam, signal }) => { - return Api.graphql({ - provider, - query, - signal, - variables: { - owner, - repo, - filters, - orderingDirection, - after: pageParam, - }, - }).then((res) => { - const parsedData = GetPullsSchema.safeParse(res?.data) - - if (!parsedData.success) { - return Promise.reject({ - status: 404, - data: {}, - }) - } - - const data = parsedData.data - - if (data?.owner?.repository?.__typename === 'NotFoundError') { - return Promise.reject({ - status: 404, - data: {}, - }) - } - - if (data?.owner?.repository?.__typename === 'OwnerNotActivatedError') { - return Promise.reject({ - status: 403, - data: { - detail: ( - <p> - Activation is required to view this repo, please{' '} - {/* @ts-expect-error - disable because of non-ts component and type mismatch */} - <A to={{ pageName: 'membersTab' }}>click here </A> to activate - your account. - </p> - ), - }, - }) - } - - const pulls = mapEdges(data?.owner?.repository?.pulls) - const pageInfo = data?.owner?.repository?.pulls?.pageInfo ?? null - - return { pulls, pageInfo } - }) - }, - getNextPageParam: (data) => { - if (data?.pageInfo?.hasNextPage) { - return data?.pageInfo?.endCursor - } - - return undefined - }, - ...opts, - }) -}