Skip to content

Commit

Permalink
feat: Create alert for paid plans (#2841)
Browse files Browse the repository at this point in the history
* feat: Add free plan seats alert

* feat: Create alerts for paid plan

* fix: Remove repositoryDeprecated from path contents hooks (#2845)

* update usePrefetchBranchDirEntry

* update useRepoBranchContents

* update index file

* update query off of repositoryDeprecated

* update tests

* small change to have commit signed

* update constants to ts

* small tweaks to the query keys

* Update useBranchCoverageMeasurements to use repository instead of repositoryDeprecated (#2843)

* feat: Paid/Free plan seats limit banner (#2830)

* feat: Add activation banner for trial eligible owners

* pull out interface + spec stuff

* Update to reflect paid plan activation banner

* Refactor CircleCI repo onboarding into one file (#2806)

* Refactor Other CI repo onboarding into one file (#2807)

* Update repo onboarding title position and page alignment (#2818)

* sec: 390 - Add validation for potential XSS vuln (#2797)

* add tests, and validation for provider

* add back supportServiceless param

* ref: 1548 Part 1: Convert all Header files to TS (#2821)

* ref all header files to TS

* remove prop types and rebase

* fix: Remove repository from GUT settings page header (#2823)

Small tweak removing `repository` from the GUT settings page.

* Install radix-ui react radio group (#2825)

* Update repo onboarding steps with new Card component (#2819)

GH codecov/engineering-team#1665

* feat: Add hasSeatsLeft to plan query

* Update to reflect SeatsLimitReachedBanner

* feat: paid plan activation banner

* update with from FreePlanSeatsLimitBanner

* value duplicate

* feat: paid plan seats limit banner

* just one more small tweak

* update name to FreePlanSeatsLimitBanner

* remove queryclient call

* update tests

* fix padding

* Update to have a const for plan value

---------

Co-authored-by: Spencer Murray <159931558+spalmurray-codecov@users.noreply.github.com>
Co-authored-by: ajay-sentry <159853603+ajay-sentry@users.noreply.github.com>
Co-authored-by: nicholas-codecov <nicholas.deschenes@sentry.io>

* feat: Paid plans activation required banner (#2832)

* feat: Add activation banner for trial eligible owners

* pull out interface + spec stuff

* Update to reflect paid plan activation banner

* Refactor CircleCI repo onboarding into one file (#2806)

* Refactor Other CI repo onboarding into one file (#2807)

* Update repo onboarding title position and page alignment (#2818)

* sec: 390 - Add validation for potential XSS vuln (#2797)

* add tests, and validation for provider

* add back supportServiceless param

* ref: 1548 Part 1: Convert all Header files to TS (#2821)

* ref all header files to TS

* remove prop types and rebase

* fix: Remove repository from GUT settings page header (#2823)

Small tweak removing `repository` from the GUT settings page.

* Install radix-ui react radio group (#2825)

* Update repo onboarding steps with new Card component (#2819)

GH codecov/engineering-team#1665

* feat: Add hasSeatsLeft to plan query

* Update to reflect SeatsLimitReachedBanner

* feat: paid plan activation banner

* update with from FreePlanSeatsLimitBanner

* value duplicate

* feat: Activation required banner

* clean up previous commit

* match design

* fix style

* Resolve conflicts

---------

Co-authored-by: Spencer Murray <159931558+spalmurray-codecov@users.noreply.github.com>
Co-authored-by: ajay-sentry <159853603+ajay-sentry@users.noreply.github.com>
Co-authored-by: nicholas-codecov <nicholas.deschenes@sentry.io>

* Unlink PR author on pulls table (#2846)

* ref: Use Repository instead of RepositoryDeprecated in useCommitYaml (#2844)

* Convert UseCommitYaml to Repository from RepositoryDeprecated

* Update query key

* ref: Convert useCommitErrors to TS and remove repositoryDeprecated (#2847)

* init conversion and remove deprecated, testing

* fix tests for useCommitErrors

* add additional spec

* add dev stuff

* update test coverage

* feat: Route to plan if user session + to param == plan (#2837)

* add plan route logic if to param set as plan

* help w/ test

* remove some unneeded mocks

* remove to

* fix spec mocks

* fix: Show banner for private repos only (#2853)

* fix: Footer Codecov icon displaying incorrect color  (#2858)

* fill color update

* update hex val

* Update useRepoSettings to move to new repository type (#2851)

* Update usereposettings

* Update tests

* update test

* Update more tests

* Update more tests

* more test updates

* Update type

* Update

* Update query

* Refactor

* dep: Update to Sentry RC-1 (#2849)

* pin @sentry/react to the latest release candidated

* update lockfile

* fix: Flaky tests around file explorer tables (#2856)

* Fix coverage tab file list table

* Fix coverage tab code tree table tests

* Fix pull indirect changed files table

* Fix commit file explorer table

* Fix missed instance of getByText

* ref: Convert SessionsTable to tanstack table (#2842)

* Convert Access.jsx -> tsx

* Fix type issue with TokensTable

* Remove unnecessary import

* Convert SessionsTable to ts

* Add tests

* Fix tests

* Fix all testLocation: any

* Use msw for mutation mock

* Improve flatmap callback for readability

* Better type hint for flat map return

* Fix flake

* Fix imports

* Fix indirect coverage loss

* style: Use Card component in bundle onboarding (#2861)

* Fix copy pasta border/padding

* Page width change

* Vite onboarding overhaul

* Webpack onboarding overhaul

* Rollup onboarding overhaul

* Fix tests

* chore: Update codecov.yml so that PR comment isn't sent until 9 uploads (#2865)

* Update to 8 uploads before comment to avoid many comments

* Oopsie it's actually the ceiling

* ref: Convert useComparisonForCommitAndParent to TS and remove repositoryDeprecated (#2857)

* fix tests, confirm it works

* export type and use instead

* feat: Add radio button navigation to repo onboarding (#2839)

* Use RadioTileGroup for navigation on the repo onboarding page

* Pull getInitalProvider out of component

* Fix tests

* Remove unnecessary div

* fix: A potential fix for the flaky test (#2854)

* ref: Clean up pull file explorer tests (#2852)

* Clean up pull file explorer tests

* Fix tests

* Revert "Fix tests"

This reverts commit f5aeccd.

* components over time display name instead of id (#2868)

* style: Fix radio tile group indicator shifting on certain screen sizes (#2869)

* Fix responsiveness issue on RadioTileGroup

* Add longer description to story

* Left align the label text

* ref: Remove repositoryDeprecated from usePrefetchSingleFileComp (#2870)

* update usePrefetchSingleFileComp to TS

* update NameColumn tests

* move fragment schema next to gql fragment string

* Capitlize view in free alert

* oops conflict stuff

---------

Co-authored-by: nicholas-codecov <nicholas.deschenes@sentry.io>
Co-authored-by: Spencer Murray <159931558+spalmurray-codecov@users.noreply.github.com>
Co-authored-by: ajay-sentry <159853603+ajay-sentry@users.noreply.github.com>
Co-authored-by: Rohit Vinnakota <148245014+rohitvinnakota-codecov@users.noreply.github.com>
Co-authored-by: JerrySentry <142266253+JerrySentry@users.noreply.github.com>
  • Loading branch information
6 people committed May 13, 2024
1 parent 3ccb37c commit 4702b47
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 2 deletions.
22 changes: 22 additions & 0 deletions src/pages/RepoPage/ActivationAlert/ActivationAlert.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { MemoryRouter, Route } from 'react-router-dom'
import ActivationAlert from './ActivationAlert'

jest.mock('./FreePlanSeatsTakenAlert', () => () => 'FreePlanSeatsTakenAlert')
jest.mock('./PaidPlanSeatsTakenAlert', () => () => 'PaidPlanSeatsTakenAlert')
jest.mock('./ActivationRequiredAlert', () => () => 'ActivationRequiredAlert')
jest.mock('./UnauthorizedRepoDisplay', () => () => 'UnauthorizedRepoDisplay')

const queryClient = new QueryClient()
Expand Down Expand Up @@ -100,4 +102,24 @@ describe('ActivationAlert', () => {
)
expect(freePlanSeatsTakenAlert).toBeInTheDocument()
})

it('renders PaidPlanSeatsTakenAlert when on paid plan and no seats left', async () => {
setup(false, 'users-pro', false)
render(<ActivationAlert />, { wrapper })

const paidPlanSeatsTakenAlert = await screen.findByText(
/PaidPlanSeatsTakenAlert/
)
expect(paidPlanSeatsTakenAlert).toBeInTheDocument()
})

it('renders ActivationRequiredAlert when on paid plan and some seats left', async () => {
setup(false, 'users-pro', true)
render(<ActivationAlert />, { wrapper })

const activationRequiredAlert = await screen.findByText(
/ActivationRequiredAlert/
)
expect(activationRequiredAlert).toBeInTheDocument()
})
})
16 changes: 16 additions & 0 deletions src/pages/RepoPage/ActivationAlert/ActivationAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { useParams } from 'react-router-dom'
import { usePlanData } from 'services/account'
import { isFreePlan } from 'shared/utils/billing'

import ActivationRequiredAlert from './ActivationRequiredAlert'
import FreePlanSeatsTakenAlert from './FreePlanSeatsTakenAlert'
import PaidPlanSeatsTakenAlert from './PaidPlanSeatsTakenAlert'
import UnauthorizedRepoDisplay from './UnauthorizedRepoDisplay'

interface URLParams {
Expand All @@ -21,10 +23,24 @@ function ActivationAlert() {
const renderFreePlanSeatsTakenAlert =
isFreePlan(planData?.plan?.value) && !planData?.plan?.hasSeatsLeft

const renderPaidPlanSeatsTakenAlert =
!isFreePlan(planData?.plan?.value) && !planData?.plan?.hasSeatsLeft

const renderActivationRequiredAlert =
!isFreePlan(planData?.plan?.value) && planData?.plan?.hasSeatsLeft

if (renderFreePlanSeatsTakenAlert) {
return <FreePlanSeatsTakenAlert />
}

if (renderPaidPlanSeatsTakenAlert) {
return <PaidPlanSeatsTakenAlert />
}

if (renderActivationRequiredAlert) {
return <ActivationRequiredAlert />
}

return <UnauthorizedRepoDisplay />
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, screen } from '@testing-library/react'
import { MemoryRouter, Route } from 'react-router-dom'

import ActivationRequiredAlert from './ActivationRequiredAlert'

const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<MemoryRouter initialEntries={['/gh/codecov/gazebo/new']}>
<Route path="/:provider/:owner/:repo/new">{children}</Route>
</MemoryRouter>
)

describe('ActivationRequiredAlert', () => {
it('renders the banner with correct heading', () => {
render(<ActivationRequiredAlert />, { wrapper })

const bannerHeading = screen.getByRole('heading', {
name: /Activation Required/,
})
expect(bannerHeading).toBeInTheDocument()
})

it('renders the banner with correct description', () => {
render(<ActivationRequiredAlert />, { wrapper })

const description = screen.getByText(
/You have available seats, but activation is needed./
)
expect(description).toBeInTheDocument()
})

it('renders the banner with correct link', () => {
render(<ActivationRequiredAlert />, { wrapper })

const link = screen.getByRole('link', {
name: /Manage members/,
})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '/members/gh/codecov')
})

it('renders the correct img', () => {
render(<ActivationRequiredAlert />, { wrapper })

const img = screen.getByAltText('Forbidden')
expect(img).toBeInTheDocument()
expect(img).toHaveAttribute('src', 'error-403.svg')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import img403 from 'layouts/shared/NetworkErrorBoundary/assets/error-403.svg'
import Button from 'ui/Button'

const ActivationRequiredAlert = () => {
return (
<div className="flex flex-col items-center justify-center gap-8 bg-ds-gray-primary pb-28 pt-12 text-center">
<img src={img403} alt="Forbidden" className="w-36" />
<div className="flex w-2/5 flex-col gap-1">
<h1 className="text-2xl">Activation Required</h1>
<p>You have available seats, but activation is needed.</p>
</div>
<Button
to={{ pageName: 'membersTab' }}
disabled={undefined}
hook={undefined}
variant="primary"
>
Manage members
</Button>
</div>
)
}

export default ActivationRequiredAlert
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ActivationRequiredAlert'
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('FreePlanSeatsTakenAlert', () => {
render(<FreePlanSeatsTakenAlert />, { wrapper })

const link = screen.getByRole('link', {
name: /view plan options/,
name: /View plan options/,
})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '/plan/gh/codecov')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const FreePlanSeatsTakenAlert = () => {
hook={undefined}
variant="primary"
>
view plan options
View plan options
</Button>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, screen } from '@testing-library/react'
import { MemoryRouter, Route } from 'react-router-dom'

import PaidPlanSeatsTakenAlert from './PaidPlanSeatsTakenAlert'

const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
<MemoryRouter initialEntries={['/gh/codecov/gazebo/new']}>
<Route path="/:provider/:owner/:repo/new">{children}</Route>
</MemoryRouter>
)

describe('PaidPlanSeatsTakenAlert', () => {
it('renders the banner with correct heading', () => {
render(<PaidPlanSeatsTakenAlert />, { wrapper })

const bannerHeading = screen.getByRole('heading', {
name: /Seats Limit Reached/,
})
expect(bannerHeading).toBeInTheDocument()
})

it('renders the banner with correct description', () => {
render(<PaidPlanSeatsTakenAlert />, { wrapper })

const description = screen.getByText(
/Your organization has utilized all available seats on this plan/
)
expect(description).toBeInTheDocument()
})

it('renders the banner with correct link', () => {
render(<PaidPlanSeatsTakenAlert />, { wrapper })

const link = screen.getByRole('link', {
name: /Increase seat count/,
})
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '/plan/gh/codecov/upgrade')
})

it('renders the correct img', () => {
render(<PaidPlanSeatsTakenAlert />, { wrapper })

const img = screen.getByAltText('Forbidden')
expect(img).toBeInTheDocument()
expect(img).toHaveAttribute('src', 'error-403.svg')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import img403 from 'layouts/shared/NetworkErrorBoundary/assets/error-403.svg'
import A from 'ui/A'
import Button from 'ui/Button'

const PaidPlanSeatsTakenAlert = () => {
return (
<div className="flex flex-col items-center justify-center gap-8 bg-ds-gray-primary pb-28 pt-12 text-center">
<img src={img403} alt="Forbidden" className="w-36" />
<div className="flex w-2/5 flex-col gap-1">
<h1 className="text-2xl">Seats Limit Reached</h1>
<p>
Your organization has utilized all available seats on this plan. To
add more members, please increase your seat count.{' '}
<A
to={{ pageName: 'membersTab' }}
isExternal={false}
hook="repo-page-to-members-tab"
variant="semibold"
>
manage members
</A>
</p>
</div>
<Button
to={{ pageName: 'upgradeOrgPlan' }}
disabled={undefined}
hook={undefined}
variant="primary"
>
Increase seat count
</Button>
</div>
)
}

export default PaidPlanSeatsTakenAlert
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './PaidPlanSeatsTakenAlert'

0 comments on commit 4702b47

Please sign in to comment.