Skip to content

Commit

Permalink
fix: Shift GH config banner location and hide tokenless banner for ne…
Browse files Browse the repository at this point in the history
…wly onboarded
  • Loading branch information
calvin-codecov committed Jan 24, 2025
1 parent ad28027 commit 8b56e12
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const GithubConfigBanner = () => {
if (!isGh) return null

return (
<div className="mb-2">
<div className="mb-2 mt-4">
<Banner>
<BannerHeading>
<h2 className="flex justify-center gap-2 font-semibold">
Expand Down
20 changes: 2 additions & 18 deletions src/pages/OwnerPage/HeaderBanners/HeaderBanners.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { useParams } from 'react-router-dom'
import config from 'config'

import { useOwnerPageData } from 'pages/OwnerPage/hooks'
import { useAccountDetails, usePlanData } from 'services/account'
import { usePlanData } from 'services/account'

import ExceededUploadsAlert from './ExceededUploadsAlert'
import GithubConfigBanner from './GithubConfigBanner'
import ReachingUploadLimitAlert from './ReachingUploadLimitAlert'

const useUploadsInfo = () => {
Expand All @@ -31,14 +30,9 @@ const useUploadsInfo = () => {
return { isUploadLimitExceeded, isApproachingUploadLimit }
}

const AlertBanners = ({
isUploadLimitExceeded,
isApproachingUploadLimit,
hasGhApp,
}) => {
const AlertBanners = ({ isUploadLimitExceeded, isApproachingUploadLimit }) => {
return (
<>
{!hasGhApp && <GithubConfigBanner />}
{isUploadLimitExceeded ? (
<ExceededUploadsAlert />
) : isApproachingUploadLimit ? (
Expand All @@ -55,17 +49,8 @@ AlertBanners.propTypes = {
}

export default function HeaderBanners() {
const { owner, provider } = useParams()
// TODO: refactor this to add a gql field for the integration id used to determine if the org has a GH app
const { data: accountDetails } = useAccountDetails({
provider,
owner,
})

const { isUploadLimitExceeded, isApproachingUploadLimit } = useUploadsInfo()

const hasGhApp = !!accountDetails?.integrationId

if (config.IS_SELF_HOSTED) {
return null
}
Expand All @@ -75,7 +60,6 @@ export default function HeaderBanners() {
<AlertBanners
isUploadLimitExceeded={isUploadLimitExceeded}
isApproachingUploadLimit={isApproachingUploadLimit}
hasGhApp={hasGhApp}
/>
</>
)
Expand Down
25 changes: 1 addition & 24 deletions src/pages/OwnerPage/HeaderBanners/HeaderBanners.test.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen, waitFor } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import { graphql, http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { MemoryRouter, Route } from 'react-router-dom'
Expand Down Expand Up @@ -225,29 +225,6 @@ describe('HeaderBanners', () => {
})
})

describe('user does not have gh app installed', () => {
beforeEach(() => {
setup({
integrationId: null,
})
})

it('displays github app config banner', async () => {
render(
<HeaderBanners
provider="gh"
owner={{ username: 'codecov', isCurrentUserPartOfOrg: true }}
/>,
{ wrapper }
)

await waitFor(() => {
const banner = screen.getByText("Codecov's GitHub app")
return expect(banner).toBeInTheDocument()
})
})
})

describe('user is running in self hosted mode', () => {
beforeEach(() => {
setup({
Expand Down
17 changes: 14 additions & 3 deletions src/pages/OwnerPage/OwnerPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useHistory, useParams } from 'react-router-dom'
import SilentNetworkErrorWrapper from 'layouts/shared/SilentNetworkErrorWrapper'
import NotFound from 'pages/NotFound'
import { useOwnerPageData } from 'pages/OwnerPage/hooks'
import { useSentryToken } from 'services/account'
import { useAccountDetails, useSentryToken } from 'services/account'
import { useLocationParams } from 'services/navigation'
import { renderToast } from 'services/toast'
import { ActiveContext } from 'shared/context'
Expand Down Expand Up @@ -38,7 +38,7 @@ const useSentryTokenRedirect = ({ ownerData }) => {
}

function OwnerPage() {
const { provider } = useParams()
const { owner, provider } = useParams()
const { data: ownerData } = useOwnerPageData()
const { params } = useLocationParams({
repoDisplay: 'All',
Expand Down Expand Up @@ -66,6 +66,14 @@ function OwnerPage() {
}
}, [userStartedTrial])

// TODO: refactor this to add a gql field for the integration id used to determine if the org has a GH app
const { data: accountDetails } = useAccountDetails({
provider,
owner,
})

const hasGhApp = !!accountDetails?.integrationId

if (!ownerData) {
return <NotFound />
}
Expand All @@ -83,7 +91,10 @@ function OwnerPage() {
<Tabs owner={ownerData} provider={provider} />
)}
<ActiveContext.Provider value={params?.repoDisplay}>
<ListRepo canRefetch={ownerData?.isCurrentUserPartOfOrg} />
<ListRepo
canRefetch={ownerData?.isCurrentUserPartOfOrg}
hasGhApp={hasGhApp}
/>
</ActiveContext.Provider>
</div>
</div>
Expand Down
22 changes: 20 additions & 2 deletions src/pages/OwnerPage/OwnerPage.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen, waitFor } from '@testing-library/react'
import { graphql, HttpResponse } from 'msw'
import { graphql, http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { MemoryRouter, Route } from 'react-router-dom'

Expand Down Expand Up @@ -59,9 +59,10 @@ afterAll(() => {

describe('OwnerPage', () => {
function setup(
{ owner, successfulMutation = true } = {
{ owner, successfulMutation = true, integrationId = 9 } = {
owner: null,
successfulMutation: true,
integrationId: 9,
}
) {
server.use(
Expand All @@ -85,6 +86,9 @@ describe('OwnerPage', () => {
return HttpResponse.json({
data: { saveSentryState: null },
})
}),
http.get('/internal/gh/codecov/account-details/', () => {
return HttpResponse.json({ integrationId })
})
)
}
Expand Down Expand Up @@ -242,4 +246,18 @@ describe('OwnerPage', () => {
await waitFor(() => expect(mockRemoveItem).toHaveBeenCalled())
})
})

describe('when user does not have gh app installed', () => {
beforeEach(() => {
setup({
integrationId: null,
})
})

it('displays github app config banner', async () => {
render(<OwnerPage />, { wrapper })
const banner = await screen.findByText("Codecov's GitHub app")

Check failure on line 259 in src/pages/OwnerPage/OwnerPage.test.jsx

View workflow job for this annotation

GitHub Actions / Test Runner #1 - Vitest

src/pages/OwnerPage/OwnerPage.test.jsx > OwnerPage > when user does not have gh app installed > displays github app config banner

TestingLibraryElementError: Unable to find an element with the text: Codecov's GitHub app. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <article class="mx-auto flex h-full flex-col items-center justify-center" > <img alt="illustration error" class="_illustrationError_a04e95 mx-auto" src="/src/pages/NotFound/assets/error-404.svg" /> <h1 class="mt-6 text-2xl" > Not found </h1> <p class="my-4 px-3 sm:px-0" > You may be able to locate the content by visiting <a class=" font-sans cursor-pointer hover:underline focus:ring-2 text-ds-blue-default inline-flex items-center gap-1" data-cy="home" data-marketing="home" data-testid="home" href="https://app.codecov.io/" rel="noreferrer" target="blank" > Codecov’s home page <span class="text-ds-gray-quinary" > <svg class="w-4 h-4" data-icon="" data-testid="" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /> </svg> </span> </a> and browsing to it. </p> <p> <strong> Error 404 </strong> </p> </article> </div> </body> Ignored nodes: comments, script, style <body> <div> <article class="mx-auto flex h-full flex-col items-center justify-center" > <img alt="illustration error" class="_illustrationError_a04e95 mx-auto" src="/src/pages/NotFound/assets/error-404.svg" /> <h1 class="mt-6 text-2xl" > Not found </h1> <p class="my-4 px-3 sm:px-0" > You may be able to locate the content by visiting <a class=" font-sans cursor-pointer hover:underline focus:ring-2 text-ds-blue-default inline-flex items-center gap-1" data-cy="home" data-marketing="home" data-testid="home" href="https://app.codecov.io/" rel="noreferrer" target="blank" > Codecov’s home page <span class="text-ds-gray-quinary" > <svg class="w-4 h-4" data-icon="" data-testid="" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /> </svg> </span> </a> and browsing to it. </p> <p> <strong> Error 404 </strong> </p> </article> </div> </body> ❯ waitForWrapper node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ src/pages/OwnerPage/OwnerPage.test.jsx:259:35
expect(banner).toBeInTheDocument()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,23 @@ describe('TokenlessBanner', () => {

it('renders nothing when tokenlessSection flag is false', () => {
setup({ tokenlessSection: false })
const { container } = render(<TokenlessBanner />, { wrapper: wrapper() })
const { container } = render(<TokenlessBanner />, {
wrapper: wrapper(['/gh/codecov']),
})
expect(container).toBeEmptyDOMElement()
})

it('renders nothing when owner is not provided', () => {
setup()
const { container } = render(<TokenlessBanner />, {
wrapper: wrapper(['/gh/']),
wrapper: wrapper(['/gh/codecov']),
})
expect(container).toBeEmptyDOMElement()
})

it('renders TokenRequiredBanner when uploadTokenRequired is true', async () => {
setup({ uploadTokenRequired: true })
render(<TokenlessBanner />, { wrapper: wrapper() })
render(<TokenlessBanner />, { wrapper: wrapper(['/gh/codecov']) })

await waitFor(() => {
const banner = screen.getByText('TokenRequiredBanner')
Expand All @@ -104,11 +106,26 @@ describe('TokenlessBanner', () => {

it('renders TokenNotRequiredBanner when uploadTokenRequired is false', async () => {
setup({ uploadTokenRequired: false })
render(<TokenlessBanner />, { wrapper: wrapper() })
render(<TokenlessBanner />, { wrapper: wrapper(['/gh/codecov']) })

await waitFor(() => {
const banner = screen.getByText('TokenNotRequiredBanner')
expect(banner).toBeInTheDocument()
})
})

it('renders nothing if coming from onboarding', async () => {
setup({ uploadTokenRequired: true })
render(<TokenlessBanner />, {
wrapper: wrapper(['/gh/codecov?source=onboarding']),
})
await waitFor(() => {
expect(screen.queryByText('TokenRequiredBanner')).not.toBeInTheDocument()
})
await waitFor(() => {
expect(
screen.queryByText('TokenNotRequiredBanner')
).not.toBeInTheDocument()
})
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { lazy } from 'react'
import { useParams } from 'react-router-dom'

import { ONBOARDING_SOURCE } from 'pages/TermsOfService/constants'
import { useLocationParams } from 'services/navigation'
import { useUploadTokenRequired } from 'services/uploadTokenRequired'
import { useFlags } from 'shared/featureFlags'

Expand All @@ -18,8 +20,11 @@ const TokenlessBanner: React.FC = () => {
})
const { provider, owner } = useParams<UseParams>()
const { data } = useUploadTokenRequired({ provider, owner, enabled: !!owner })
const { params } = useLocationParams()
// @ts-expect-error useLocationParams needs to be typed
const cameFromOnboarding = params['source'] === ONBOARDING_SOURCE

if (!tokenlessSection || !owner || !data) return null
if (!tokenlessSection || !owner || !data || cameFromOnboarding) return null

return data?.uploadTokenRequired ? (
<TokenRequiredBanner />
Expand Down
5 changes: 4 additions & 1 deletion src/shared/ListRepo/ListRepo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import { Suspense, useContext } from 'react'
import { useParams } from 'react-router-dom'

import GithubConfigBanner from 'pages/OwnerPage/HeaderBanners/GithubConfigBanner'
import { ONBOARDING_SOURCE } from 'pages/TermsOfService/constants'
import { useLocationParams } from 'services/navigation'
import { orderingOptions } from 'services/repos/orderingOptions'
Expand All @@ -29,7 +30,7 @@ export const repoDisplayOptions = Object.freeze({
ALL: { text: 'All', status: undefined },
})

function ListRepo({ canRefetch }) {
function ListRepo({ canRefetch, hasGhApp }) {
const { provider, owner } = useParams()
const { params, updateParams } = useLocationParams(defaultQueryParams)
const { data: isTeamPlan } = useIsTeamPlan({ provider, owner })
Expand Down Expand Up @@ -59,6 +60,7 @@ function ListRepo({ canRefetch }) {

return (
<>
{!hasGhApp && <GithubConfigBanner />}
<OrgControlTable
searchValue={params.search}
repoDisplay={repoDisplay}
Expand Down Expand Up @@ -103,6 +105,7 @@ function ListRepo({ canRefetch }) {

ListRepo.propTypes = {
canRefetch: PropTypes.bool.isRequired,
hasGhApp: PropTypes.bool,
}

export default ListRepo
Loading

0 comments on commit 8b56e12

Please sign in to comment.