Skip to content

Commit

Permalink
feat: Add in bundle chart (#2984)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholas-codecov authored and spalmurray-codecov committed Jul 4, 2024
1 parent 5ea665c commit 50ed086
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen } from '@testing-library/react'
import { graphql } from 'msw'
import { setupServer } from 'msw/node'
import { MemoryRouter, Route } from 'react-router-dom'

import { BundleChart } from './BundleChart'

const mockRepoOverview = {
owner: {
repository: {
__typename: 'Repository',
private: false,
defaultBranch: 'main',
oldestCommitAt: '2022-10-10T11:59:59',
coverageEnabled: true,
bundleAnalysisEnabled: true,
languages: ['typescript'],
testAnalyticsEnabled: false,
},
},
}

const mockBundleTrendData = {
owner: {
repository: {
__typename: 'Repository',
branch: {
head: {
bundleAnalysisReport: {
__typename: 'BundleAnalysisReport',
bundle: {
measurements: [
{
assetType: 'REPORT_SIZE',
measurements: [
{
timestamp: '2024-06-15T00:00:00+00:00',
avg: null,
},
{
timestamp: '2024-06-16T00:00:00+00:00',
avg: null,
},
{
timestamp: '2024-06-17T00:00:00+00:00',
avg: 6834699.8,
},
{
timestamp: '2024-06-18T00:00:00+00:00',
avg: 6822037.27273,
},
{
timestamp: '2024-06-19T00:00:00+00:00',
avg: 6824833.33333,
},
{
timestamp: '2024-06-20T00:00:00+00:00',
avg: 6812341,
},
],
},
],
},
},
},
},
},
},
}

const queryClient = new QueryClient()
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter
initialEntries={['/gh/codecov/test-repo/bundles/main/test-bundle']}
>
<Route path="/:provider/:owner/:repo/bundles/:branch/:bundle">
{children}
</Route>
</MemoryRouter>
</QueryClientProvider>
)

const server = setupServer()

beforeAll(() => {
server.listen()
})

afterEach(() => {
queryClient.clear()
server.resetHandlers()
})

afterAll(() => {
server.close()
})

describe('BundleChart', () => {
function setup() {
server.use(
graphql.query('GetBundleTrend', (req, res, ctx) => {
return res(ctx.status(200), ctx.data(mockBundleTrendData))
}),
graphql.query('GetRepoOverview', (req, res, ctx) => {
return res(ctx.status(200), ctx.data(mockRepoOverview))
})
)
}

it('renders placeholder while loading', () => {
setup()
render(<BundleChart />, { wrapper })

const placeholder = screen.getByTestId('bundle-chart-placeholder')
expect(placeholder).toBeInTheDocument()
})

it('renders the chart after loading', async () => {
setup()
render(<BundleChart />, { wrapper })

const bundleChart = await screen.findByTestId('bundle-trend-chart')
expect(bundleChart).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useParams } from 'react-router-dom'

import { BundleTrendChart } from 'ui/BundleTrendChart'

import { TrendDropdown } from './TrendDropdown'
import { useBundleChartData } from './useBundleChartData'

const Placeholder = () => (
<div
data-testid="bundle-chart-placeholder"
className="h-[23rem] animate-pulse rounded bg-ds-gray-tertiary"
/>
)

interface URLParams {
provider: string
owner: string
repo: string
branch: string
bundle: string
}

export function BundleChart() {
const { provider, owner, repo, branch, bundle } = useParams<URLParams>()
const { data, maxY, multiplier, isLoading } = useBundleChartData({
provider,
owner,
repo,
branch,
bundle,
})

return (
<div className="pb-4 pt-6">
<TrendDropdown />
{isLoading ? (
<Placeholder />
) : (
<BundleTrendChart
title="Bundle Size"
desc="The size of the bundle over time"
data={{
measurements: data,
maxY,
multiplier,
}}
/>
)}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, screen } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { MemoryRouter, Route, useLocation } from 'react-router-dom'

import qs from 'querystring'

import { TrendDropdown } from './TrendDropdown'

let testLocation: ReturnType<typeof useLocation>
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<MemoryRouter initialEntries={['/']}>
<Route path="*">
{({ location }) => {
testLocation = location
return children
}}
</Route>
</MemoryRouter>
)

describe('TrendDropdown', () => {
function setup() {
const user = userEvent.setup()
return { user }
}

describe('when the trend is not set', () => {
it('renders the default trend', () => {
render(<TrendDropdown />, { wrapper })

const trend = screen.getByText(/3 months/)
expect(trend).toBeInTheDocument()
})
})

describe('when the trend is set', () => {
it('renders the selected trend', async () => {
const { user } = setup()
render(<TrendDropdown />, { wrapper })

const trendDropdown = screen.getByText(/3 months/)
await user.click(trendDropdown)

const option = screen.getByRole('option', { name: /30 days/ })
await user.click(option)

const trend = screen.getByRole('button', {
name: /bundle-chart-trend-dropdown/,
})
expect(trend).toBeInTheDocument()
expect(trend).toHaveTextContent(/30 days/)
})

it('updates the URL params', async () => {
const { user } = setup()
render(<TrendDropdown />, { wrapper })

const trendDropdown = screen.getByText(/3 months/)
await user.click(trendDropdown)

const option = screen.getByRole('option', { name: /30 days/ })
await user.click(option)

expect(testLocation.search).toBe(`?${qs.stringify({ trend: '30 days' })}`)
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useLocationParams } from 'services/navigation'
import { Trend } from 'shared/utils/timeseriesCharts'
import Select from 'ui/Select'

const defaultQueryParams = {
trend: null,
}

export function TrendDropdown() {
const { params, updateParams } = useLocationParams(defaultQueryParams)
const items = Object.values(Trend)

return (
<h3 className="flex min-w-40 items-center text-sm font-semibold text-ds-gray-octonary">
<Select
// @ts-expect-error Select needs to be typed
ariaName="bundle-chart-trend-dropdown"
dataMarketing="Bundle chart trend dropdown"
variant="text"
items={items}
onChange={(selected: string) => updateParams({ trend: selected })}
// @ts-expect-error useLocationParams needs to be typed
value={params?.trend || Trend.THREE_MONTHS}
renderItem={(item: string) => <p className="capitalize">{item}</p>}
/>
<span className="text-ds-gray-senary">trend</span>
</h3>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { BundleChart } from './BundleChart'
51 changes: 51 additions & 0 deletions src/pages/RepoPage/BundlesTab/BundleContent/BundleContent.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,54 @@ const mockMissingHeadReportAssets = {
},
}

const mockBundleTrendData = {
owner: {
repository: {
__typename: 'Repository',
branch: {
head: {
bundleAnalysisReport: {
__typename: 'BundleAnalysisReport',
bundle: {
measurements: [
{
assetType: 'REPORT_SIZE',
measurements: [
{
timestamp: '2024-06-15T00:00:00+00:00',
avg: null,
},
{
timestamp: '2024-06-16T00:00:00+00:00',
avg: null,
},
{
timestamp: '2024-06-17T00:00:00+00:00',
avg: 6834699.8,
},
{
timestamp: '2024-06-18T00:00:00+00:00',
avg: 6822037.27273,
},
{
timestamp: '2024-06-19T00:00:00+00:00',
avg: 6824833.33333,
},
{
timestamp: '2024-06-20T00:00:00+00:00',
avg: 6812341,
},
],
},
],
},
},
},
},
},
},
}

const server = setupServer()
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, suspense: true } },
Expand Down Expand Up @@ -211,6 +259,9 @@ describe('BundleContent', () => {
}

return res(ctx.status(200), ctx.data(mockAssets))
}),
graphql.query('GetBundleTrend', (req, res, ctx) => {
return res(ctx.status(200), ctx.data(mockBundleTrendData))
})
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { metrics } from 'shared/utils/metrics'
import Spinner from 'ui/Spinner'

import AssetsTable from './AssetsTable'
import { BundleChart } from './BundleChart'
import BundleSummary from './BundleSummary'
import InfoBanner from './InfoBanner'

Expand Down Expand Up @@ -46,7 +47,10 @@ const BundleContent: React.FC = () => {
{bundleType === 'BundleAnalysisReport' ? (
<Switch>
<SentryRoute path="/:provider/:owner/:repo/bundles/:branch/:bundle">
<AssetsTable />
<BundleChart />
<Suspense fallback={<Loader />}>
<AssetsTable />
</Suspense>
</SentryRoute>
<SentryRoute
path={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const BundleSummary: React.FC = () => {
}, [])

return (
<div className="flex flex-col gap-8 py-4 md:flex-row md:justify-between">
<div className="flex flex-col gap-8 border-b border-ds-gray-tertiary pb-6 pt-4 md:flex-row md:justify-between">
<div className="flex flex-col gap-4 md:flex-row">
<BranchSelector resetBundleSelect={resetBundleSelect} />
<BundleSelector ref={bundleSelectRef} />
Expand Down
Loading

0 comments on commit 50ed086

Please sign in to comment.