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

616 add patch setction pr page team tier #2337

Merged
merged 5 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions src/pages/PullRequestPage/Header/Header.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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 { TierNames } from 'services/tier'
import { useFlags } from 'shared/featureFlags'

import Header from './Header'

jest.mock('./HeaderDefault', () => () => 'Default Header')
jest.mock('./HeaderTeam', () => () => 'Team Header')
jest.mock('shared/featureFlags')
const mockedUseFlags = useFlags as jest.Mock<{ multipleTiers: boolean }>

const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
const server = setupServer()
const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={['/gh/test-org/test-repo/pull/12']}>
<Route path="/:provider/:owner/:repo/pull/:pullId">{children}</Route>
</MemoryRouter>
</QueryClientProvider>
)

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

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

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

interface SetupArgs {
multipleTiers: boolean
}

describe('Header', () => {
function setup({ multipleTiers = false }: SetupArgs) {
mockedUseFlags.mockReturnValue({
multipleTiers,
})

server.use(
graphql.query('OwnerTier', (req, res, ctx) => {
if (multipleTiers) {
return res(
ctx.status(200),
ctx.data({ owner: { plan: { tierName: TierNames.TEAM } } })
)
}
return res(
ctx.status(200),
ctx.data({ owner: { plan: { tierName: TierNames.PRO } } })
)
})
)
}

describe('when rendered and customer is not team tier', () => {
beforeEach(() => {
setup({ multipleTiers: false })
})

it('renders the default header component', async () => {
render(<Header />, { wrapper })

const defaultHeader = await screen.findByText(/Default Header/)
expect(defaultHeader).toBeInTheDocument()

const teamHeader = screen.queryByText(/Team Header/)
expect(teamHeader).not.toBeInTheDocument()
})
})

describe('when rendered and customer has team tier', () => {
beforeEach(() => {
setup({ multipleTiers: true })
})

it('renders the team header component', async () => {
render(<Header />, { wrapper })

const teamHeader = await screen.findByText(/Team Header/)
expect(teamHeader).toBeInTheDocument()

const defaultHeader = screen.queryByText(/Default Header/)
expect(defaultHeader).not.toBeInTheDocument()
})
})
})
28 changes: 28 additions & 0 deletions src/pages/PullRequestPage/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useParams } from 'react-router-dom'

import { TierNames, useTier } from 'services/tier'
import { useFlags } from 'shared/featureFlags'

import HeaderDefault from './HeaderDefault'
import HeaderTeam from './HeaderTeam'

interface URLParams {
provider: string
owner: string
}

function Header() {
const { provider, owner } = useParams<URLParams>()
const { data: tierData } = useTier({ provider, owner })
const { multipleTiers } = useFlags({
multipleTiers: false,
})

if (multipleTiers && tierData === TierNames.TEAM) {
return <HeaderTeam />
}

return <HeaderDefault />
}

export default Header
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,58 @@ import CIStatusLabel from 'ui/CIStatus'
import Icon from 'ui/Icon'

import { usePullHeadData } from './hooks'
import PendoLink from './PendoLink'

const pullStateToColor = {
OPEN: 'bg-ds-primary-green',
CLOSED: 'bg-ds-primary-red',
MERGED: 'bg-ds-primary-purple',
}
import { pullStateToColor } from '../constants'
import PendoLink from '../PendoLink'

function Header() {
function HeaderDefault() {
const { provider, owner, repo, pullId } = useParams()
const { data } = usePullHeadData({ provider, owner, repo, pullId })

const pull = data?.pull

return (
<div className="flex flex-col justify-between gap-2 border-b border-ds-gray-secondary pb-2 text-xs md:flex-row">
<div>
<h1 className="flex items-center gap-2 text-lg font-semibold">
{data?.pull?.title}
{pull?.title}
<span
className={cs(
'text-white font-bold px-3 py-0.5 text-xs rounded',
pullStateToColor[data?.pull?.state]
pullStateToColor[pull?.state]
)}
>
{capitalize(data?.pull?.state)}
{capitalize(pull?.state)}
</span>
</h1>
<p className="flex items-center gap-2">
<span>
{data?.pull?.updatestamp &&
formatTimeToNow(data?.pull?.updatestamp)}{' '}
<span className="bold">{data?.pull?.author?.username}</span>{' '}
authored{' '}
{data?.pull?.pullId && (
{pull?.updatestamp && formatTimeToNow(pull?.updatestamp)}{' '}
<span className="bold">{pull?.author?.username}</span> authored{' '}
{pull?.pullId && (
<A
href={getProviderPullURL({
provider,
owner,
repo,
pullId: data?.pull?.pullId,
pullId: pull?.pullId,
})}
hook="provider-pr-link"
isExternal={true}
>
#{data?.pull?.pullId}
#{pull?.pullId}
</A>
)}
</span>
<CIStatusLabel ciPassed={data?.pull?.head?.ciPassed} />
<CIStatusLabel ciPassed={pull?.head?.ciPassed} />
<span className="flex items-center">
<Icon name="branch" variant="developer" size="sm" />
{data?.pull?.head?.branchName}
{pull?.head?.branchName}
</span>
</p>
</div>
<PendoLink />
</div>
)
}

export default Header
export default HeaderDefault
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { graphql } from 'msw'
import { setupServer } from 'msw/node'
import { MemoryRouter, Route } from 'react-router-dom'

import Header from './Header'

jest.mock('shared/featureFlags')
import Header from './HeaderDefault'

const mockPullData = {
owner: {
Expand Down
1 change: 1 addition & 0 deletions src/pages/PullRequestPage/Header/HeaderDefault/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './HeaderDefault'
79 changes: 79 additions & 0 deletions src/pages/PullRequestPage/Header/HeaderTeam/HeaderTeam.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import cs from 'classnames'
import capitalize from 'lodash/capitalize'
import { useParams } from 'react-router-dom'

import { formatTimeToNow } from 'shared/utils/dates'
import { getProviderPullURL } from 'shared/utils/provider'
import A from 'ui/A'
import CIStatusLabel from 'ui/CIStatus'
import Icon from 'ui/Icon'
import TotalsNumber from 'ui/TotalsNumber'

import { usePullHeadDataTeam } from './hooks'

import { pullStateToColor } from '../constants'
import PendoLink from '../PendoLink'

function HeaderTeam() {
const { provider, owner, repo, pullId } = useParams()
const { data } = usePullHeadDataTeam({ provider, owner, repo, pullId })

const pull = data?.pull

return (
<div className="flex flex-col justify-between gap-2 border-b border-ds-gray-secondary pb-2 text-xs md:flex-row">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JW do we generally use CSS classes with attributes? In my experience it tends to be neater, more scalable, and maintains good separation of concerns. For example, this div could have a single className with relevant css attribs defined accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Gazebo we use tailwind css, https://tailwindcss.com/, which is a popular framework to style your markup; if you aren't familiar I urge to to check it out, it's pretty neat. I've found this to be a very nice + easy way to style your code and the more and more I've gotten to know tailwind syntax the more I value its power. We do have certain files with dedicated css files and their respective attributes (for example Table.css) that I think we use sparingly for code whose style we likely don't want to change.

I find using separate .css files does bring separation of concerns but it leads to an extra file for any file you have, which can be a bit extra for certain files that have very little styling to them. Tailwind's main advantage imo is it speeds up our development, so writing custom classes can be quite easy + fast. That's kinda my take, although I'll summon our css guru @terry-codecov for a more in depth answer as to why we opted for this choice

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting thanks for the link.

<div className="flex flex-row flex-wrap items-center gap-6 divide-x divide-ds-gray-secondary">
<div>
<h1 className="flex items-center gap-2 text-lg font-semibold">
{pull?.title}
<span
className={cs(
'text-white font-bold px-3 py-0.5 text-xs rounded',
pullStateToColor[pull?.state]
)}
>
{capitalize(pull?.state)}
</span>
</h1>
<p className="flex flex-row items-center gap-2">
<span>
{pull?.updatestamp && formatTimeToNow(pull?.updatestamp)}{' '}
<span className="bold">{pull?.author?.username}</span> authored{' '}
{pull?.pullId && (
<A
href={getProviderPullURL({
provider,
owner,
repo,
pullId: pull?.pullId,
})}
hook="provider-pr-link"
isExternal={true}
>
#{pull?.pullId}
</A>
)}
</span>
<CIStatusLabel ciPassed={pull?.head?.ciPassed} />
<span className="flex items-center">
<Icon name="branch" variant="developer" size="sm" />
{pull?.head?.branchName}
</span>
</p>
</div>
<div className="flex flex-col justify-center gap-2 px-6">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New logic that extends the normal header component from 64-73

<h4 className="gap-2 font-mono text-xs text-ds-gray-quinary">
Patch Coverage
</h4>
<TotalsNumber
value={pull?.compareWithBase?.patchTotals?.percentCovered}
plain
large
/>
</div>
</div>
<PendoLink />
</div>
)
}
export default HeaderTeam
Loading