Skip to content

Commit

Permalink
[feat] Add AI features tab at the org level (#3185)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitvinnakota-codecov authored Sep 23, 2024
1 parent ebf4847 commit 42f4e89
Show file tree
Hide file tree
Showing 23 changed files with 706 additions and 121 deletions.
6 changes: 6 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ThemeContextProvider } from 'shared/ThemeContext'
import AccountSettings from './pages/AccountSettings'
import AdminSettings from './pages/AdminSettings'
const AnalyticsPage = lazy(() => import('./pages/AnalyticsPage'))
const CodecovAIPage = lazy(() => import('./pages/CodecovAIPage'))
const CommitDetailPage = lazy(() => import('./pages/CommitDetailPage'))
const EnterpriseLandingPage = lazy(() => import('pages/EnterpriseLandingPage'))
const LoginPage = lazy(() => import('./pages/LoginPage'))
Expand Down Expand Up @@ -120,6 +121,11 @@ const MainAppRoutes = () => (
<AnalyticsPage />
</BaseLayout>
</SentryRoute>
<SentryRoute path="/codecovai/:provider/:owner" exact>
<BaseLayout>
<CodecovAIPage />
</BaseLayout>
</SentryRoute>
<SentryRoute path="/:provider" exact>
<BaseLayout>
<HomePageRedirect />
Expand Down
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const defaultConfig = {
SENTRY_SESSION_SAMPLE_RATE: 0.1,
SENTRY_ERROR_SAMPLE_RATE: 1.0,
GH_APP: 'codecov',
GH_APP_AI: 'codecov', // TODO: Update to proper GH app name once it is live
}

// To be removed after we're satisfied session_expiry cookie cleanup is complete
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import { MemoryRouter, Route } from 'react-router-dom'

import config from 'config'

import { useFlags } from 'shared/featureFlags'

import Header from './Header'

vi.mock('config')
vi.mock('layouts/MyContextSwitcher', () => () => 'MyContextSwitcher')
vi.mock('shared/featureFlags')

const mockedUseFlags = useFlags as jest.Mock

const queryClient = new QueryClient()
const server = setupServer()

const wrapper = ({ children }) => (
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={['/account/gh/codecov']}>
<Route path="/account/:provider/:owner">{children}</Route>
Expand All @@ -33,12 +39,9 @@ afterAll(() => {
})

describe('Header', () => {
function setup(
{ isSelfHosted = false } = {
isSelfHosted: false,
}
) {
function setup(isSelfHosted: boolean = false) {
config.IS_SELF_HOSTED = isSelfHosted
mockedUseFlags.mockReturnValue({ codecovAiFeaturesTab: true })
}

describe('when users is part of the org', () => {
Expand Down Expand Up @@ -100,7 +103,7 @@ describe('Header', () => {

describe('when rendered with enterprise account', () => {
it('does not render link to members page', () => {
setup({ isSelfHosted: true })
setup(true)
render(<Header />, { wrapper })

expect(
Expand All @@ -111,7 +114,7 @@ describe('Header', () => {
})

it('does not render link to plan page', () => {
setup({ isSelfHosted: true })
setup(true)
render(<Header />, { wrapper })

expect(
Expand All @@ -121,4 +124,28 @@ describe('Header', () => {
).not.toBeInTheDocument()
})
})

describe('ai features tab', () => {
it('does not render tab when flag is off', () => {
mockedUseFlags.mockReturnValue({ codecovAiFeaturesTab: false })
render(<Header />, { wrapper })

expect(
screen.queryByRole('link', {
name: /Codecov AI beta/i,
})
).not.toBeInTheDocument()
})

it('renders tab when flag is on', () => {
setup()
render(<Header />, { wrapper })

expect(
screen.getByRole('link', {
name: /Codecov AI beta/i,
})
).toBeInTheDocument()
})
})
})
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import config from 'config'

import { useFlags } from 'shared/featureFlags'
import Badge from 'ui/Badge'
import TabNavigation from 'ui/TabNavigation'

function Header() {
const { codecovAiFeaturesTab } = useFlags({
codecovAiFeaturesTab: false,
})

return (
<TabNavigation
tabs={[
{ pageName: 'owner', children: 'Repos' },
{ pageName: 'analytics', children: 'Analytics' },
...(codecovAiFeaturesTab
? [
{
pageName: 'codecovAI',
children: (
<>
Codecov AI <Badge>beta</Badge>{' '}
</>
),
},
]
: []),
...(config.IS_SELF_HOSTED
? []
: [{ pageName: 'membersTab' }, { pageName: 'planTab' }]),
Expand Down
29 changes: 0 additions & 29 deletions src/pages/AnalyticsPage/Tabs/Tabs.jsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ import { MemoryRouter, Route } from 'react-router-dom'

import config from 'config'

import { useFlags } from 'shared/featureFlags'

import Tabs from './Tabs'

jest.mock('config')

jest.mock('shared/featureFlags')

const mockedUseFlags = useFlags as jest.Mock

const queryClient = new QueryClient()
const server = setupServer()

const wrapper = ({ children }) => (
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<QueryClientProvider client={queryClient}>
<MemoryRouter initialEntries={['/analytics/gh/codecov']}>
<Route path="/analytics/:provider/:owner">{children}</Route>
Expand All @@ -33,17 +39,14 @@ afterAll(() => {
})

describe('Tabs', () => {
function setup(
{ isSelfHosted = false } = {
isSelfHosted: false,
}
) {
function setup(isSelfHosted: boolean = false) {
config.IS_SELF_HOSTED = isSelfHosted
mockedUseFlags.mockReturnValue({ codecovAiFeaturesTab: true })
}

describe('when user is part of the org', () => {
it('renders links to the home page', () => {
setup({})
setup()
render(<Tabs />, { wrapper })

expect(
Expand All @@ -54,7 +57,7 @@ describe('Tabs', () => {
})

it('renders links to the analytics page', () => {
setup({})
setup()
render(<Tabs />, { wrapper })

expect(
Expand All @@ -65,7 +68,7 @@ describe('Tabs', () => {
})

it('renders links to the settings page', () => {
setup({})
setup()
render(<Tabs />, { wrapper })

expect(
Expand All @@ -76,7 +79,7 @@ describe('Tabs', () => {
})

it('renders link to plan page', () => {
setup({})
setup()
render(<Tabs />, { wrapper })

expect(
Expand All @@ -87,7 +90,7 @@ describe('Tabs', () => {
})

it('renders link to members page', () => {
setup({})
setup()
render(<Tabs />, { wrapper })

expect(
Expand All @@ -100,7 +103,7 @@ describe('Tabs', () => {

describe('when should render tabs is false', () => {
it('does not render link to members page', () => {
setup({ isSelfHosted: true })
setup(true)
render(<Tabs />, { wrapper })

expect(
Expand All @@ -111,7 +114,7 @@ describe('Tabs', () => {
})

it('does not render link to plan page', () => {
setup({ isSelfHosted: true })
setup(true)
render(<Tabs />, { wrapper })

expect(
Expand All @@ -121,4 +124,28 @@ describe('Tabs', () => {
).not.toBeInTheDocument()
})
})

describe('ai features tab', () => {
it('does not render tab when flag is off', () => {
mockedUseFlags.mockReturnValue({ codecovAiFeaturesTab: false })
render(<Tabs />, { wrapper })

expect(
screen.queryByRole('link', {
name: /Codecov AI beta/i,
})
).not.toBeInTheDocument()
})

it('renders tab when flag is on', () => {
setup()
render(<Tabs />, { wrapper })

expect(
screen.getByRole('link', {
name: /Codecov AI beta/i,
})
).toBeInTheDocument()
})
})
})
41 changes: 41 additions & 0 deletions src/pages/AnalyticsPage/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import config from 'config'

import { useFlags } from 'shared/featureFlags'
import Badge from 'ui/Badge'
import TabNavigation from 'ui/TabNavigation'

function Tabs() {
const { codecovAiFeaturesTab } = useFlags({
codecovAiFeaturesTab: false,
})

return (
<TabNavigation
tabs={[
{ pageName: 'owner', children: 'Repos' },
{ pageName: 'analytics', children: 'Analytics' },
...(codecovAiFeaturesTab
? [
{
pageName: 'codecovAI',
children: (
<>
Codecov AI <Badge>beta</Badge>{' '}
</>
),
},
]
: []),
...(config.IS_SELF_HOSTED
? []
: [{ pageName: 'membersTab' }, { pageName: 'planTab' }]),
{
pageName: 'accountAdmin',
children: 'Settings',
},
]}
/>
)
}

export default Tabs
55 changes: 55 additions & 0 deletions src/pages/CodecovAIPage/CodecovAICommands/CodecovAICommands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Card } from 'ui/Card'
import { ExpandableSection } from 'ui/ExpandableSection'

const CodecovAICommands: React.FC = () => {
return (
<div>
<Card>
<Card.Header>
<Card.Title size="base">Codecov AI Commands</Card.Title>
</Card.Header>
<Card.Content>
After installing the app, use these commands in your PR comments:
<ul className="mt-2 list-inside list-disc space-y-1">
<li>
<span className="rounded border border-gray-200 bg-gray-100 px-1 font-semibold">
@codecov-ai-reviewer test
</span>{' '}
--the assistant will generate tests for the PR.
</li>
<li>
<span className="rounded border border-gray-200 bg-gray-100 px-1 font-semibold">
@codecov-ai-reviewer review
</span>{' '}
--the assistant will review the PR and make suggestions.
</li>
</ul>
</Card.Content>
</Card>
<ExpandableSection className="-mt-px">
<ExpandableSection.Trigger>
<p>
Here is an example of Codecov AI Reviewer in PR comments. Comment
generation may take time.
</p>
</ExpandableSection.Trigger>
<ExpandableSection.Content>
Screenshot goes here
</ExpandableSection.Content>
</ExpandableSection>
<ExpandableSection className="-mt-2 border-t-0">
<ExpandableSection.Trigger>
<p>
Here is an example of Codecov AI Test Generator in PR comments.
Comment generation may take time.
</p>
</ExpandableSection.Trigger>
<ExpandableSection.Content>
Screenshot goes here
</ExpandableSection.Content>
</ExpandableSection>
</div>
)
}

export default CodecovAICommands
Loading

0 comments on commit 42f4e89

Please sign in to comment.