Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new UI to clarify adding token #3552

Merged
merged 12 commits into from
Dec 13, 2024
Binary file added src/assets/onboarding/org_secret_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/onboarding/org_secret_light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/onboarding/repo_secret_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/onboarding/repo_secret_light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

319 changes: 45 additions & 274 deletions src/pages/RepoPage/CoverageOnboarding/GitHubActions/GitHubActions.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { useState } from 'react'
import { useRef, useState } from 'react'
import { useParams } from 'react-router-dom'

import {
EVENT_METRICS,
useStoreCodecovEventMetric,
} from 'services/codecovEventMetrics'
import { useOrgUploadToken } from 'services/orgUploadToken'
import { useRepo } from 'services/repo'
import { useUploadTokenRequired } from 'services/uploadTokenRequired'
import { Provider } from 'shared/api/helpers'
import { useFlags } from 'shared/featureFlags'
import A from 'ui/A'
import { Card } from 'ui/Card'
import { CodeSnippet } from 'ui/CodeSnippet'
import { ExpandableSection } from 'ui/ExpandableSection'
import Select from 'ui/Select'

import LearnMoreBlurb from '../LearnMoreBlurb'
import MergeStep from './MergeStep'
import OutputCoverageStep from './OutputCoverageStep'
import TokenStep from './TokenStep'
import { Framework } from './types'
import WorkflowYMLStep from './WorkflowYMLStep'

import LearnMoreBlurb from '../LearnMoreBlurb'
interface URLParams {
provider: string
provider: Provider
owner: string
repo: string
}
Expand All @@ -27,15 +27,37 @@ function GitHubActions() {
newRepoFlag: false,
})
const { provider, owner, repo } = useParams<URLParams>()
const { data } = useRepo({ provider, owner, repo })
const { data: orgUploadToken } = useOrgUploadToken({
provider,
owner,
enabled: showOrgToken,
})

const uploadToken = orgUploadToken ?? data?.repository?.uploadToken ?? ''
const tokenCopy = orgUploadToken ? 'global' : 'repository'
const [isUsingGlobalToken, setIsUsingGlobalToken] = useState<boolean>(true)
const { data: repoData } = useRepo({ provider, owner, repo })
const repoUploadToken = repoData?.repository?.uploadToken ?? ''
const previouslyGeneratedOrgToken = useRef<string | null | undefined>()
const { data: uploadTokenRequiredData } = useUploadTokenRequired({
provider,
owner,
})
const hasPreviouslyGeneratedOrgToken = !!previouslyGeneratedOrgToken.current
const isUploadTokenRequired = uploadTokenRequiredData?.uploadTokenRequired
const showTokenSelector =
!isUploadTokenRequired || !previouslyGeneratedOrgToken.current
// token step is shown if upload token is required and org token has been previously generated
// or if global token is selected and org token has been generated when not previously generated
// or if repo token picker is selected and exists
const showAddTokenStep =
(isUploadTokenRequired && hasPreviouslyGeneratedOrgToken) ||
(isUsingGlobalToken && !!orgUploadToken) ||
(!isUsingGlobalToken && !!repoUploadToken)

// If orgUploadToken does not exist on initial render, set it to null and we
// do not touch it again on rerenders
if (previouslyGeneratedOrgToken.current === undefined) {
previouslyGeneratedOrgToken.current = orgUploadToken ?? null
}

const [framework, setFramework] = useState<Framework>('Jest')

Expand Down Expand Up @@ -193,283 +215,32 @@ jobs:
`,
},
}

return (
<div className="flex flex-col gap-5">
<Step1
<OutputCoverageStep
framework={framework}
frameworkInstructions={frameworkInstructions}
owner={owner}
setFramework={setFramework}
/>
<Step2 tokenCopy={tokenCopy} uploadToken={uploadToken} />
<Step3
<TokenStep
isUsingGlobalToken={isUsingGlobalToken}
setIsUsingGlobalToken={setIsUsingGlobalToken}
showAddTokenStep={showAddTokenStep}
showTokenSelector={showTokenSelector}
/>
<WorkflowYMLStep
framework={framework}
frameworkInstructions={frameworkInstructions}
orgUploadToken={orgUploadToken}
owner={owner}
repo={repo}
stepNum={showTokenSelector && showAddTokenStep ? 4 : 3}
/>
<Step4 />
<MergeStep stepNum={showTokenSelector && showAddTokenStep ? 5 : 4} />
<FeedbackCTA />
<LearnMoreBlurb />
</div>
)
}

type Framework = 'Jest' | 'Vitest' | 'Pytest' | 'Go'
type FrameworkInstructions = {
[key in Framework]: { install?: string; run?: string; workflow?: string }
}

interface Step1Props {
framework: Framework
frameworkInstructions: FrameworkInstructions
owner: string
setFramework: (value: Framework) => void
}

function Step1({
framework,
frameworkInstructions,
owner,
setFramework,
}: Step1Props) {
const { mutate: storeEventMetric } = useStoreCodecovEventMetric()

return (
<div>
<Card>
<Card.Header>
<Card.Title size="base">
Step 1: Output a Coverage report file in your CI
</Card.Title>
</Card.Header>
<Card.Content className="flex flex-col gap-4">
<p>
Codecov generally supports xml and json format. Select your language
below to generate your coverage reports. If your language isn&apos;t
listed, visit our{' '}
<A
to={{ pageName: 'exampleRepos' }}
isExternal
hook="supported-languages-docs"
>
supported languages doc
</A>{' '}
for example repositories. Codecov generally supports xml and json
formats.
</p>

<div className="max-w-64">
<Select
// @ts-expect-error - Select has some TS issues because it's still written in JS
items={Object.keys(frameworkInstructions)}
value={framework}
onChange={(value: Framework) => setFramework(value)}
/>
</div>

{frameworkInstructions[framework].install ? (
<>
<p>Install requirements in your terminal:</p>
<CodeSnippet
clipboard={frameworkInstructions[framework].install}
clipboardOnClick={() =>
storeEventMetric({
owner,
event: EVENT_METRICS.COPIED_TEXT,
jsonPayload: { text: `coverage GHA ${framework} install` },
})
}
>
{frameworkInstructions[framework].install}
</CodeSnippet>
</>
) : null}

<p>In a GitHub Action, run tests and generate a coverage report:</p>
<CodeSnippet
clipboard={frameworkInstructions[framework].run}
clipboardOnClick={() =>
storeEventMetric({
owner,
event: EVENT_METRICS.COPIED_TEXT,
jsonPayload: { text: `coverage GHA ${framework} run` },
})
}
>
{frameworkInstructions[framework].run}
</CodeSnippet>
</Card.Content>
</Card>
</div>
)
}

interface Step2Props {
tokenCopy: string
uploadToken: string
}

function Step2({ tokenCopy, uploadToken }: Step2Props) {
const { mutate: storeEventMetric } = useStoreCodecovEventMetric()
const { owner } = useParams<URLParams>()
return (
<Card>
<Card.Header>
<Card.Title size="base">
Step 2: add {tokenCopy} token as{' '}
<A
to={{ pageName: 'githubRepoSecrets' }}
isExternal
hook="GitHub-repo-secrets-link"
>
repository secret
</A>
</Card.Title>
</Card.Header>
<Card.Content className="flex flex-col gap-4">
<p>
Admin required to access repo configuration &gt; secrets and variable
&gt; actions
</p>
<div className="flex gap-4">
<CodeSnippet
className="basis-1/3"
clipboard="CODECOV_TOKEN"
data-testid="token-key"
>
CODECOV_TOKEN
</CodeSnippet>
<CodeSnippet
className="basis-2/3"
clipboard={uploadToken}
clipboardOnClick={() =>
storeEventMetric({
owner,
event: EVENT_METRICS.COPIED_TEXT,
jsonPayload: { text: 'Step 2 GHA' },
})
}
>
{uploadToken}
</CodeSnippet>
</div>
</Card.Content>
</Card>
)
}

interface Step3Props {
framework: Framework
frameworkInstructions: FrameworkInstructions
orgUploadToken: string | null | undefined
owner: string
repo: string
}

function Step3({
framework,
frameworkInstructions,
orgUploadToken,
owner,
repo,
}: Step3Props) {
const { mutate: storeEventMetric } = useStoreCodecovEventMetric()

const step3Config = `- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: \${{ secrets.CODECOV_TOKEN }}${
orgUploadToken
? `
slug: ${owner}/${repo}`
: ''
}`

return (
<div>
<Card>
<Card.Header>
<Card.Title size="base">
Step 3: Add Codecov to your GitHub Actions workflow yaml file
</Card.Title>
</Card.Header>
<Card.Content className="flex flex-col gap-4">
<p>
After tests run, this will upload your coverage report to Codecov:
</p>
<CodeSnippet
clipboard={step3Config}
clipboardOnClick={() =>
storeEventMetric({
owner,
event: EVENT_METRICS.COPIED_TEXT,
jsonPayload: { text: `coverage GHA ${framework} upload` },
})
}
>
{step3Config}
</CodeSnippet>
</Card.Content>
</Card>
<ExpandableSection className="-mt-px">
<ExpandableSection.Trigger>
<p className="font-normal">
Your final GitHub Actions workflow for a project using{' '}
<span className="text-codecov-code">{framework}</span> could look
something like this:
</p>
</ExpandableSection.Trigger>
<ExpandableSection.Content>
<CodeSnippet
clipboard={frameworkInstructions[framework].workflow}
clipboardOnClick={() =>
storeEventMetric({
owner,
event: EVENT_METRICS.COPIED_TEXT,
jsonPayload: { text: `coverage GHA ${framework} action` },
})
}
>
{frameworkInstructions[framework].workflow}
</CodeSnippet>
<p className="pt-4">
<A
to={{ pageName: 'exampleRepos' }}
isExternal
hook="supported-languages-docs"
>
Learn more
</A>{' '}
about generating coverage reports with {framework}.
</p>
</ExpandableSection.Content>
</ExpandableSection>
</div>
)
}

function Step4() {
return (
<Card>
<Card.Header>
<Card.Title size="base">
Step 4: merge to main or your preferred feature branch
</Card.Title>
</Card.Header>
<Card.Content>
<p>
Once merged to your default branch, subsequent pull requests will have
Codecov checks and comments. Additionally, you’ll find your repo
coverage dashboard here. If you have merged, try reloading the page.
</p>
</Card.Content>
</Card>
)
}

function FeedbackCTA() {
return (
<Card>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { render, screen } from '@testing-library/react'

import MergeStep from './MergeStep'

vi.mock('config')

describe('MergeStep', () => {
it('renders body', async () => {
render(<MergeStep stepNum={4} />)

const body = await screen.findByText(/Once merged to your default branch,/)
expect(body).toBeInTheDocument()
})
})
Loading
Loading