diff --git a/src/App.tsx b/src/App.tsx index 35d2f35651..cc0bf8db40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,6 @@ 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')) @@ -118,11 +117,6 @@ const MainAppRoutes = () => ( - - - - - diff --git a/src/assets/codecovAI/pr-review-example-dark-mode.png b/src/assets/codecovAI/pr-review-example-dark-mode.png deleted file mode 100644 index 535d9866a9..0000000000 Binary files a/src/assets/codecovAI/pr-review-example-dark-mode.png and /dev/null differ diff --git a/src/assets/codecovAI/pr-review-example-light-mode.png b/src/assets/codecovAI/pr-review-example-light-mode.png deleted file mode 100644 index ee45b62cc7..0000000000 Binary files a/src/assets/codecovAI/pr-review-example-light-mode.png and /dev/null differ diff --git a/src/assets/codecovAI/test-gen-example-dark-mode.png b/src/assets/codecovAI/test-gen-example-dark-mode.png deleted file mode 100644 index cca9bf1002..0000000000 Binary files a/src/assets/codecovAI/test-gen-example-dark-mode.png and /dev/null differ diff --git a/src/assets/codecovAI/test-gen-example-light-mode.png b/src/assets/codecovAI/test-gen-example-light-mode.png deleted file mode 100644 index 16ba50dd87..0000000000 Binary files a/src/assets/codecovAI/test-gen-example-light-mode.png and /dev/null differ diff --git a/src/config.js b/src/config.js index bd3aedf9e2..039cd4e0fb 100644 --- a/src/config.js +++ b/src/config.js @@ -11,7 +11,6 @@ const defaultConfig = { SENTRY_SESSION_SAMPLE_RATE: 0.1, SENTRY_ERROR_SAMPLE_RATE: 1.0, GH_APP: DEFAULT_GH_APP, - GH_APP_AI: 'codecov-ai', SUNBURST_ENABLED: true, DISPLAY_SELF_HOSTED_EXPIRATION_BANNER: true, } diff --git a/src/pages/AccountSettings/shared/Header/Header.test.tsx b/src/pages/AccountSettings/shared/Header/Header.test.tsx index 16e6a2b057..c61a137d5f 100644 --- a/src/pages/AccountSettings/shared/Header/Header.test.tsx +++ b/src/pages/AccountSettings/shared/Header/Header.test.tsx @@ -6,15 +6,8 @@ import config from 'config' import Header from './Header' -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) - vi.mock('config') vi.mock('layouts/MyContextSwitcher', () => () => 'MyContextSwitcher') -vi.mock('shared/featureFlags', () => ({ - useFlags: mocks.useFlags, -})) const queryClient = new QueryClient() @@ -36,7 +29,6 @@ beforeEach(() => { describe('Header', () => { function setup(isSelfHosted: boolean = false) { config.IS_SELF_HOSTED = isSelfHosted - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) } describe('when users is part of the org', () => { @@ -119,28 +111,4 @@ describe('Header', () => { ).not.toBeInTheDocument() }) }) - - describe('ai features tab', () => { - it('does not render tab when flag is off', () => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - render(
, { wrapper }) - - expect( - screen.queryByRole('link', { - name: /Codecov AI beta/i, - }) - ).not.toBeInTheDocument() - }) - - it('renders tab when flag is on', () => { - setup() - render(
, { wrapper }) - - expect( - screen.getByRole('link', { - name: /Codecov AI beta/i, - }) - ).toBeInTheDocument() - }) - }) }) diff --git a/src/pages/AccountSettings/shared/Header/Header.tsx b/src/pages/AccountSettings/shared/Header/Header.tsx index 8a5ca809fb..b4470f964a 100644 --- a/src/pages/AccountSettings/shared/Header/Header.tsx +++ b/src/pages/AccountSettings/shared/Header/Header.tsx @@ -1,31 +1,13 @@ 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 ( - Codecov AI beta{' '} - - ), - }, - ] - : []), ...(config.IS_SELF_HOSTED ? [] : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), diff --git a/src/pages/AnalyticsPage/Tabs/Tabs.test.tsx b/src/pages/AnalyticsPage/Tabs/Tabs.test.tsx index 704ad486a0..20840c4517 100644 --- a/src/pages/AnalyticsPage/Tabs/Tabs.test.tsx +++ b/src/pages/AnalyticsPage/Tabs/Tabs.test.tsx @@ -6,20 +6,8 @@ import config from 'config' import Tabs from './Tabs' -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) - vi.mock('config') -vi.mock('shared/featureFlags', async () => { - const actual = await vi.importActual('shared/featureFlags') - return { - ...actual, - useFlags: mocks.useFlags, - } -}) - const queryClient = new QueryClient() const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( @@ -37,7 +25,6 @@ beforeEach(() => { describe('Tabs', () => { function setup(isSelfHosted: boolean = false) { config.IS_SELF_HOSTED = isSelfHosted - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) } describe('when user is part of the org', () => { @@ -102,22 +89,4 @@ describe('Tabs', () => { expect(link).not.toBeInTheDocument() }) }) - - describe('ai features tab', () => { - it('does not render tab when flag is off', () => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - render(, { wrapper }) - - const link = screen.queryByRole('link', { name: /Codecov AI beta/i }) - expect(link).not.toBeInTheDocument() - }) - - it('renders tab when flag is on', () => { - setup() - render(, { wrapper }) - - const link = screen.getByRole('link', { name: /Codecov AI beta/i }) - expect(link).toBeInTheDocument() - }) - }) }) diff --git a/src/pages/AnalyticsPage/Tabs/Tabs.tsx b/src/pages/AnalyticsPage/Tabs/Tabs.tsx index c36e711a14..6e6cfff15c 100644 --- a/src/pages/AnalyticsPage/Tabs/Tabs.tsx +++ b/src/pages/AnalyticsPage/Tabs/Tabs.tsx @@ -1,31 +1,13 @@ 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 ( - Codecov AI beta{' '} - - ), - }, - ] - : []), ...(config.IS_SELF_HOSTED ? [] : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), diff --git a/src/pages/CodecovAIPage/CodecovAICommands/CodecovAICommands.tsx b/src/pages/CodecovAIPage/CodecovAICommands/CodecovAICommands.tsx deleted file mode 100644 index 925d2b8e6e..0000000000 --- a/src/pages/CodecovAIPage/CodecovAICommands/CodecovAICommands.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import darkModeImage from 'assets/codecovAI/pr-review-example-dark-mode.png' -import lightModeImage from 'assets/codecovAI/pr-review-example-light-mode.png' -import darkModeImageTests from 'assets/codecovAI/test-gen-example-dark-mode.png' -import lightModeImageTests from 'assets/codecovAI/test-gen-example-light-mode.png' -import { Card } from 'ui/Card' -import { ExpandableSection } from 'ui/ExpandableSection' -import LightDarkImg from 'ui/LightDarkImg' - -const CodecovAICommands: React.FC = () => { - return ( -
- - - Codecov AI Commands - - - After installing the app, use these commands in your PR comments: -
    -
  • - - @codecov-ai-reviewer test - - -- the assistant will generate tests for the PR. -
  • -
  • - - @codecov-ai-reviewer review - - -- the assistant will review the PR and make suggestions. -
  • -
-
-
- - -

- Here is an example of Codecov AI Reviewer in PR comments. Comment - generation may take time. -

-
- -
- -
-
-
- - -

- Here is an example of Codecov AI Test Generation. Test generation - may take time. -

-
- -
- -
-
-
-
- ) -} - -export default CodecovAICommands diff --git a/src/pages/CodecovAIPage/CodecovAIPage.test.tsx b/src/pages/CodecovAIPage/CodecovAIPage.test.tsx deleted file mode 100644 index 088f4838ca..0000000000 --- a/src/pages/CodecovAIPage/CodecovAIPage.test.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { cleanup, render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { graphql, HttpResponse } from 'msw' -import { setupServer } from 'msw/node' -import { Suspense } from 'react' -import { MemoryRouter, Route } from 'react-router-dom' - -import { ThemeContextProvider } from 'shared/ThemeContext' - -import CodecovAIPage from './CodecovAIPage' - -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) - -vi.mock('shared/featureFlags', async () => { - const actual = await vi.importActual('shared/featureFlags') - return { - ...actual, - useFlags: mocks.useFlags, - } -}) - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: true, - suspense: true, - }, - }, -}) - -const server = setupServer() - -const wrapper: React.FC = ({ children }) => ( - - - - - {children} - - - - -) - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - queryClient.clear() - server.resetHandlers() -}) - -afterEach(() => { - cleanup() - vi.clearAllMocks() - queryClient.clear() -}) - -afterAll(() => { - server.close() -}) - -describe('CodecovAIPage', () => { - function setup( - aiFeaturesEnabled = false, - aiEnabledRepos = ['repo-1', 'repo-2'] - ) { - server.use( - graphql.query('GetCodecovAIAppInstallInfo', () => { - return HttpResponse.json({ - data: { - owner: { - aiFeaturesEnabled, - }, - }, - }) - }), - graphql.query('GetCodecovAIInstalledRepos', () => { - return HttpResponse.json({ - data: { - owner: { - aiEnabledRepos, - }, - }, - }) - }) - ) - } - beforeEach(() => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) - setup() - }) - - it('renders top section', async () => { - render(, { wrapper }) - - const topSection = await screen.findByText(/Codecov AI is a/) - expect(topSection).toBeInTheDocument() - }) - - it('renders the install card', async () => { - render(, { wrapper }) - - const installHeader = await screen.findByText(/Install the Codecov AI/) - expect(installHeader).toBeInTheDocument() - }) - - it('renders install instructions', async () => { - render(, { wrapper }) - - const installHeader = await screen.findByText( - /To enable the Codecov AI assistant/ - ) - expect(installHeader).toBeInTheDocument() - }) - - it('renders the install button', async () => { - render(, { wrapper }) - const buttonEl = await screen.findByText(/Install Codecov AI/i) - expect(buttonEl).toBeInTheDocument() - }) - - it('renders approve install text', async () => { - render(, { wrapper }) - - const linkApproveText = await screen.findByText( - /Hello, could you help approve/ - ) - expect(linkApproveText).toBeInTheDocument() - }) - - it('renders codecov ai commands', async () => { - render(, { wrapper }) - - const commandText = await screen.findByText(/Codecov AI Commands/) - expect(commandText).toBeInTheDocument() - - const commandOneText = await screen.findByText( - / the assistant will review the PR/ - ) - expect(commandOneText).toBeInTheDocument() - - const commandTwoText = await screen.findByText( - / the assistant will generate tests/ - ) - expect(commandTwoText).toBeInTheDocument() - }) - - it('renders examples', async () => { - render(, { wrapper }) - - const reviewExample = await screen.findByText( - /Here is an example of Codecov AI Reviewer in PR comments/ - ) - expect(reviewExample).toBeInTheDocument() - }) - - it('renders screenshots', async () => { - render(, { wrapper }) - const user = userEvent.setup() - const trigger = await screen.findAllByText((content) => - content.startsWith('Here is an example') - ) - expect(trigger).toHaveLength(2) - - await user.click(trigger[0]!) - - const screenshotReview = await screen.findByRole('img', { - name: /codecov pr review example/, - }) - expect(screenshotReview).toBeInTheDocument() - - await user.click(trigger[1]!) - - const screenshotTestGen = await screen.findByRole('img', { - name: /codecov test generation example/, - }) - expect(screenshotTestGen).toBeInTheDocument() - }) - - it('renders a link to the docs', async () => { - render(, { wrapper }) - - const docLink = await screen.findByText(/Visit our guide/) - expect(docLink).toBeInTheDocument() - }) - - describe('AI features are enabled and configured', () => { - beforeEach(() => { - setup(true) - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) - }) - - it('does not render install link', () => { - setup(true) - render(, { wrapper }) - const topSection = screen.queryByText(/Install Codecov AI/) - expect(topSection).not.toBeInTheDocument() - }) - - it('renders list of repos', async () => { - render(, { wrapper }) - - const repo1Link = await screen.findByText(/repo-1/) - expect(repo1Link).toBeInTheDocument() - const repo2Link = await screen.findByText(/repo-2/) - expect(repo2Link).toBeInTheDocument() - }) - - describe('No repos returned', () => { - it('renders install link', async () => { - setup(true, []) - render(, { wrapper }) - const buttonEl = await screen.findByText(/Install Codecov AI/i) - expect(buttonEl).toBeInTheDocument() - }) - }) - }) - - describe('flag is off', () => { - it('does not render page', async () => { - setup(true) - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - - render(, { wrapper }) - - const topSection = screen.queryByText(/Codecov AI is a/) - expect(topSection).not.toBeInTheDocument() - }) - }) -}) diff --git a/src/pages/CodecovAIPage/CodecovAIPage.tsx b/src/pages/CodecovAIPage/CodecovAIPage.tsx deleted file mode 100644 index 1cd95adf31..0000000000 --- a/src/pages/CodecovAIPage/CodecovAIPage.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Redirect, useParams } from 'react-router-dom' - -import { useCodecovAIInstallation } from 'services/codecovAI/useCodecovAIInstallation' -import { useFlags } from 'shared/featureFlags' - -import CodecovAICommands from './CodecovAICommands/CodecovAICommands' -import ConfiguredRepositories from './ConfiguredRepositories/ConfiguredRepositories' -import InstallCodecovAI from './InstallCodecovAI/InstallCodecovAI' -import LearnMoreBlurb from './LearnMoreBlurb/LearnMoreBlurb' -import Tabs from './Tabs/Tabs' - -interface URLParams { - provider: string - owner: string -} - -const CodecovAIPage: React.FC = () => { - const { provider, owner } = useParams() - const { codecovAiFeaturesTab } = useFlags({ - codecovAiFeaturesTab: false, - }) - - const { data: installationData } = useCodecovAIInstallation({ - owner, - provider, - }) - - if (!codecovAiFeaturesTab) { - return - } - - return ( - <> - -

Codecov AI

-
-

- Codecov AI is a generative AI assistant developed by Codecov at - Sentry. It helps you review your code changes, offering suggestions - for improvement before merging pull requests. -

-
-
- {installationData?.aiFeaturesEnabled ? ( - - ) : ( - - )} - - -
- - ) -} - -export default CodecovAIPage diff --git a/src/pages/CodecovAIPage/ConfiguredRepositories/ConfiguredRepositories.tsx b/src/pages/CodecovAIPage/ConfiguredRepositories/ConfiguredRepositories.tsx deleted file mode 100644 index 9d178479ab..0000000000 --- a/src/pages/CodecovAIPage/ConfiguredRepositories/ConfiguredRepositories.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { - createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, -} from '@tanstack/react-table' -import { useMemo, useState } from 'react' -import { useParams } from 'react-router-dom' - -import { useCodecovAIInstalledRepos } from 'services/codecovAI/useCodecovAIInstalledRepos' -import A from 'ui/A' -import { Card } from 'ui/Card' -import Icon from 'ui/Icon' -import Spinner from 'ui/Spinner' - -import InstallCodecovAI from '../InstallCodecovAI/InstallCodecovAI' - -interface URLParams { - owner: string - provider: string -} - -const Loader = () => ( -
- -
-) - -const columnHelper = createColumnHelper<{ name: string }>() - -function ConfiguredRepositories() { - const { owner, provider } = useParams() - const { data, isLoading } = useCodecovAIInstalledRepos({ - owner, - provider, - }) - - const [isSortedAscending, setIsSortedAscending] = useState(true) - - const sortRepos = () => { - setIsSortedAscending(!isSortedAscending) - } - - const tableData = useMemo(() => { - if (!data?.aiEnabledRepos) return [] - const sortedRepos = [...data.aiEnabledRepos].sort((a, b) => - isSortedAscending ? a.localeCompare(b) : b.localeCompare(a) - ) - return sortedRepos.map((name) => ({ name })) - }, [data?.aiEnabledRepos, isSortedAscending]) - - const columns = useMemo( - () => [ - columnHelper.accessor('name', { - header: 'Repo Name', - cell: (info) => { - const repoName = info.getValue() - const link = `/${provider}/${owner}/${repoName}` - return ( - - {repoName} - - ) - }, - }), - ], - [provider, owner] - ) - - const table = useReactTable({ - data: tableData, - columns, - getCoreRowModel: getCoreRowModel(), - }) - - // This should technically never happen, but render a fallback just in case - if (tableData.length === 0) { - return - } - - return ( -
- - - - {tableData.length} configured repositories - -

- To install more repos, please manage your Codecov AI app on GitHub. -
- To uninstall the app, please go to your GitHub Apps settings. -

-
-
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {isLoading ? ( - - - - ) : ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - )) - )} - -
-
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - - -
-
- -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} -
-
-
- ) -} - -export default ConfiguredRepositories diff --git a/src/pages/CodecovAIPage/InstallCodecovAI/InstallCodecovAI.tsx b/src/pages/CodecovAIPage/InstallCodecovAI/InstallCodecovAI.tsx deleted file mode 100644 index 4e72f85f5c..0000000000 --- a/src/pages/CodecovAIPage/InstallCodecovAI/InstallCodecovAI.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Theme, useThemeContext } from 'shared/ThemeContext' -import { loginProviderImage } from 'shared/utils/loginProviders' -import Button from 'ui/Button' -import { Card } from 'ui/Card' -import { CodeSnippet } from 'ui/CodeSnippet' - -const COPY_APP_INSTALL_STRING = - "Hello, could you help approve the installation of the Codecov AI Reviewer app on GitHub for our organization? Here's the link: [Codecov AI Installation](https://github.com/apps/codecov-ai)" - -const InstallCodecovAI: React.FC = () => { - const { theme } = useThemeContext() - const isDarkMode = theme === Theme.DARK - const githubImage = loginProviderImage('GitHub', !isDarkMode) - - return ( -
- - - - Install the Codecov AI app on GitHub - - - - To enable the Codecov AI assistant in your GitHub organization, or on - specific repositories, you need to install the Codecov AI GitHub App - Integration. This will allow the assistant to analyze pull requests - and provide insights. -
- -
-

- If you're not an admin, copy the link below and share it with - your organization's admin or owner to install: -

- -
- {COPY_APP_INSTALL_STRING} -
-
-
-
-
- ) -} - -export default InstallCodecovAI diff --git a/src/pages/CodecovAIPage/LearnMoreBlurb/LearnMoreBlurb.tsx b/src/pages/CodecovAIPage/LearnMoreBlurb/LearnMoreBlurb.tsx deleted file mode 100644 index b684e277e8..0000000000 --- a/src/pages/CodecovAIPage/LearnMoreBlurb/LearnMoreBlurb.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import A from 'ui/A' -import { Card } from 'ui/Card' - -// TODO: Update link to docs once they are available -function LearnMoreBlurb() { - return ( -
- - -

- Visit our guide to{' '} - - learn more - {' '} - about Codecov AI. -

-
-
-
- ) -} - -export default LearnMoreBlurb diff --git a/src/pages/CodecovAIPage/Tabs/Tabs.tsx b/src/pages/CodecovAIPage/Tabs/Tabs.tsx deleted file mode 100644 index d5185079c1..0000000000 --- a/src/pages/CodecovAIPage/Tabs/Tabs.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import config from 'config' - -import Badge from 'ui/Badge' -import TabNavigation from 'ui/TabNavigation' - -function Tabs() { - return ( - - Codecov AI beta{' '} - - ), - }, - ...(config.IS_SELF_HOSTED - ? [] - : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), - { - pageName: 'accountAdmin', - children: 'Settings', - }, - ]} - /> - ) -} - -export default Tabs diff --git a/src/pages/CodecovAIPage/index.tsx b/src/pages/CodecovAIPage/index.tsx deleted file mode 100644 index c8374549c5..0000000000 --- a/src/pages/CodecovAIPage/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CodecovAIPage' diff --git a/src/pages/MembersPage/Tabs/Tabs.test.tsx b/src/pages/MembersPage/Tabs/Tabs.test.tsx index 119911b629..08febece88 100644 --- a/src/pages/MembersPage/Tabs/Tabs.test.tsx +++ b/src/pages/MembersPage/Tabs/Tabs.test.tsx @@ -5,20 +5,8 @@ import config from 'config' import Tabs from './Tabs' -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) - vi.mock('config') -vi.mock('shared/featureFlags', async () => { - const actual = await vi.importActual('shared/featureFlags') - return { - ...actual, - useFlags: mocks.useFlags, - } -}) - const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} @@ -28,7 +16,6 @@ const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( describe('Tabs', () => { function setup(isSelfHosted: boolean = false) { config.IS_SELF_HOSTED = isSelfHosted - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) } describe('when user is part of the org', () => { @@ -111,28 +98,4 @@ describe('Tabs', () => { ).not.toBeInTheDocument() }) }) - - describe('ai features tab', () => { - it('does not render tab when flag is off', () => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - render(, { wrapper }) - - expect( - screen.queryByRole('link', { - name: /Codecov AI beta/i, - }) - ).not.toBeInTheDocument() - }) - - it('renders tab when flag is on', () => { - setup() - render(, { wrapper }) - - expect( - screen.getByRole('link', { - name: /Codecov AI beta/i, - }) - ).toBeInTheDocument() - }) - }) }) diff --git a/src/pages/MembersPage/Tabs/Tabs.tsx b/src/pages/MembersPage/Tabs/Tabs.tsx index c36e711a14..6e6cfff15c 100644 --- a/src/pages/MembersPage/Tabs/Tabs.tsx +++ b/src/pages/MembersPage/Tabs/Tabs.tsx @@ -1,31 +1,13 @@ 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 ( - Codecov AI beta{' '} - - ), - }, - ] - : []), ...(config.IS_SELF_HOSTED ? [] : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), diff --git a/src/pages/OwnerPage/Tabs/Tabs.test.tsx b/src/pages/OwnerPage/Tabs/Tabs.test.tsx index e422a61f10..6d2f46723b 100644 --- a/src/pages/OwnerPage/Tabs/Tabs.test.tsx +++ b/src/pages/OwnerPage/Tabs/Tabs.test.tsx @@ -6,16 +6,6 @@ import config from 'config' import Tabs from './Tabs' -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) -vi.mock('shared/featureFlags', async () => { - const actual = await vi.importActual('shared/featureFlags') - return { - ...actual, - useFlags: mocks.useFlags, - } -}) vi.mock('config') vi.mock('./TrialReminder', () => ({ default: () => 'TrialReminder' })) @@ -39,7 +29,6 @@ beforeEach(() => { describe('Tabs', () => { function setup(isSelfHosted: boolean = false) { config.IS_SELF_HOSTED = isSelfHosted - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) } describe('when user is part of the org', () => { @@ -114,28 +103,4 @@ describe('Tabs', () => { expect(trialReminder).toBeInTheDocument() }) }) - - describe('ai features tab', () => { - it('does not render tab when flag is off', () => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - render(, { wrapper }) - - expect( - screen.queryByRole('link', { - name: /Codecov AI beta/i, - }) - ).not.toBeInTheDocument() - }) - - it('renders tab when flag is on', () => { - setup() - render(, { wrapper }) - - expect( - screen.getByRole('link', { - name: /Codecov AI beta/i, - }) - ).toBeInTheDocument() - }) - }) }) diff --git a/src/pages/OwnerPage/Tabs/Tabs.tsx b/src/pages/OwnerPage/Tabs/Tabs.tsx index bec85cfc06..6e7a599e2d 100644 --- a/src/pages/OwnerPage/Tabs/Tabs.tsx +++ b/src/pages/OwnerPage/Tabs/Tabs.tsx @@ -3,17 +3,11 @@ import { Suspense } from 'react' import config from 'config' -import { useFlags } from 'shared/featureFlags' -import Badge from 'ui/Badge' import TabNavigation from 'ui/TabNavigation' import TrialReminder from './TrialReminder' function Tabs() { - const { codecovAiFeaturesTab } = useFlags({ - codecovAiFeaturesTab: false, - }) - return ( - Codecov AI beta{' '} - - ), - }, - ] - : []), ...(config.IS_SELF_HOSTED ? [] : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), diff --git a/src/pages/PlanPage/Tabs/Tabs.test.tsx b/src/pages/PlanPage/Tabs/Tabs.test.tsx index 99c5c31115..f573c78f7c 100644 --- a/src/pages/PlanPage/Tabs/Tabs.test.tsx +++ b/src/pages/PlanPage/Tabs/Tabs.test.tsx @@ -5,18 +5,7 @@ import config from 'config' import Tabs from './Tabs' -const mocks = vi.hoisted(() => ({ - useFlags: vi.fn(), -})) - vi.mock('config') -vi.mock('shared/featureFlags', async () => { - const actual = await vi.importActual('shared/featureFlags') - return { - ...actual, - useFlags: mocks.useFlags, - } -}) const wrapper: React.FC = ({ children }) => ( @@ -27,7 +16,6 @@ const wrapper: React.FC = ({ children }) => ( describe('Tabs', () => { function setup(isSelfHosted: boolean = false) { config.IS_SELF_HOSTED = isSelfHosted - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: true }) } describe('when user is part of the org', () => { @@ -110,28 +98,4 @@ describe('Tabs', () => { ).not.toBeInTheDocument() }) }) - - describe('ai features tab', () => { - it('does not render tab when flag is off', () => { - mocks.useFlags.mockReturnValue({ codecovAiFeaturesTab: false }) - render(, { wrapper }) - - expect( - screen.queryByRole('link', { - name: /Codecov AI beta/i, - }) - ).not.toBeInTheDocument() - }) - - it('renders tab when flag is on', () => { - setup() - render(, { wrapper }) - - expect( - screen.getByRole('link', { - name: /Codecov AI beta/i, - }) - ).toBeInTheDocument() - }) - }) }) diff --git a/src/pages/PlanPage/Tabs/Tabs.tsx b/src/pages/PlanPage/Tabs/Tabs.tsx index c36e711a14..6e6cfff15c 100644 --- a/src/pages/PlanPage/Tabs/Tabs.tsx +++ b/src/pages/PlanPage/Tabs/Tabs.tsx @@ -1,31 +1,13 @@ 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 ( - Codecov AI beta{' '} - - ), - }, - ] - : []), ...(config.IS_SELF_HOSTED ? [] : [{ pageName: 'membersTab' }, { pageName: 'planTab' }]), diff --git a/src/services/codecovAI/useCodecovAIInstallation.test.tsx b/src/services/codecovAI/useCodecovAIInstallation.test.tsx deleted file mode 100644 index fb17140351..0000000000 --- a/src/services/codecovAI/useCodecovAIInstallation.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { renderHook, waitFor } from '@testing-library/react' -import { graphql, HttpResponse } from 'msw' -import { setupServer } from 'msw/node' - -import { useCodecovAIInstallation } from './useCodecovAIInstallation' - -const mockAiFeaturesEnabled = { - owner: { - aiFeaturesEnabled: true, - }, -} - -const mockUnsuccessfulParseError = { - owner: { - wrong: 'schema', - }, -} - -const server = setupServer() -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}) - -const wrapper: React.FC = ({ children }) => ( - {children} -) - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - queryClient.clear() - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -interface SetupArgs { - isUnsuccessfulParseError?: boolean -} - -describe('useCodecovAIInstallation', () => { - function setup({ isUnsuccessfulParseError = false }: SetupArgs) { - server.use( - graphql.query('GetCodecovAIAppInstallInfo', () => { - if (isUnsuccessfulParseError) { - return HttpResponse.json({ data: mockUnsuccessfulParseError }) - } - return HttpResponse.json({ data: mockAiFeaturesEnabled }) - }) - ) - } - - describe('there is valid data', () => { - it('fetches the owner app installation info', async () => { - setup({}) - const { result } = renderHook( - () => - useCodecovAIInstallation({ - owner: 'codecov', - provider: 'gh', - }), - { wrapper } - ) - - await waitFor(() => - expect(result.current.data).toStrictEqual({ - aiFeaturesEnabled: true, - }) - ) - }) - }) - - describe('unsuccessful parse of zod schema', () => { - const oldConsoleError = console.error - - beforeEach(() => { - console.error = () => null - }) - - afterEach(() => { - console.error = oldConsoleError - }) - - it('throws a 400', async () => { - setup({ isUnsuccessfulParseError: true }) - const { result } = renderHook( - () => - useCodecovAIInstallation({ - owner: 'codecov', - provider: 'gh', - }), - { wrapper } - ) - - await waitFor(() => expect(result.current.isError).toBeTruthy()) - await waitFor(() => - expect(result.current.error).toEqual( - expect.objectContaining({ - dev: 'useCodecovAIInstallation - Parsing Error', - status: 400, - }) - ) - ) - }) - }) -}) diff --git a/src/services/codecovAI/useCodecovAIInstallation.tsx b/src/services/codecovAI/useCodecovAIInstallation.tsx deleted file mode 100644 index 06fa5a3964..0000000000 --- a/src/services/codecovAI/useCodecovAIInstallation.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import z from 'zod' - -import Api from 'shared/api' -import { rejectNetworkError } from 'shared/api/rejectNetworkError' - -const ResponseSchema = z.object({ - owner: z - .object({ - aiFeaturesEnabled: z.boolean(), - }) - .nullable(), -}) - -const query = ` - query GetCodecovAIAppInstallInfo($username: String!) { - owner(username: $username) { - aiFeaturesEnabled - } - } -` - -interface CodecovAIInstallationProps { - owner: string - provider: string -} - -export function useCodecovAIInstallation({ - owner, - provider, -}: CodecovAIInstallationProps) { - return useQuery({ - queryKey: ['GetCodecovAIAppInstallInfo', provider, owner], - queryFn: ({ signal }) => { - return Api.graphql({ - provider, - query, - signal, - variables: { - username: owner, - }, - }).then((res) => { - const callingFn = 'useCodecovAIInstallation' - const parsedRes = ResponseSchema.safeParse(res?.data) - - if (!parsedRes.success) { - return rejectNetworkError({ - errorName: 'Parsing Error', - errorDetails: { callingFn, error: parsedRes.error }, - }) - } - - return { - aiFeaturesEnabled: parsedRes.data.owner?.aiFeaturesEnabled, - } - }) - }, - }) -} diff --git a/src/services/codecovAI/useCodecovAIInstalledRepos.test.tsx b/src/services/codecovAI/useCodecovAIInstalledRepos.test.tsx deleted file mode 100644 index 229e1c6e30..0000000000 --- a/src/services/codecovAI/useCodecovAIInstalledRepos.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { renderHook, waitFor } from '@testing-library/react' -import { graphql, HttpResponse } from 'msw' -import { setupServer } from 'msw/node' - -import { useCodecovAIInstalledRepos } from './useCodecovAIInstalledRepos' - -const mockAiInstalledRepos = { - owner: { - aiEnabledRepos: ['repo-1', 'repo-2'], - }, -} - -const mockUnsuccessfulParseError = { - owner: { - wrong: 'schema', - }, -} - -const server = setupServer() -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}) - -const wrapper: React.FC = ({ children }) => ( - {children} -) - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - queryClient.clear() - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -interface SetupArgs { - isUnsuccessfulParseError?: boolean -} - -describe('useCodecovAIInstalledRepos', () => { - function setup({ isUnsuccessfulParseError = false }: SetupArgs) { - server.use( - graphql.query('GetCodecovAIInstalledRepos', () => { - if (isUnsuccessfulParseError) { - return HttpResponse.json({ data: mockUnsuccessfulParseError }) - } - return HttpResponse.json({ data: mockAiInstalledRepos }) - }) - ) - } - - describe('there is valid data', () => { - it('fetches the correct list of repos', async () => { - setup({}) - const { result } = renderHook( - () => - useCodecovAIInstalledRepos({ - owner: 'codecov', - provider: 'gh', - }), - { wrapper } - ) - - await waitFor(() => - expect(result.current.data).toStrictEqual({ - aiEnabledRepos: ['repo-1', 'repo-2'], - }) - ) - }) - }) - - describe('unsuccessful parse of zod schema', () => { - const oldConsoleError = console.error - - beforeEach(() => { - console.error = () => null - }) - - afterEach(() => { - console.error = oldConsoleError - }) - - it('throws a 400', async () => { - setup({ isUnsuccessfulParseError: true }) - const { result } = renderHook( - () => - useCodecovAIInstalledRepos({ - owner: 'codecov', - provider: 'gh', - }), - { wrapper } - ) - - await waitFor(() => expect(result.current.isError).toBeTruthy()) - await waitFor(() => - expect(result.current.error).toEqual( - expect.objectContaining({ - dev: 'useCodecovAIInstalledRepos - Parsing Error', - status: 400, - }) - ) - ) - }) - }) -}) diff --git a/src/services/codecovAI/useCodecovAIInstalledRepos.tsx b/src/services/codecovAI/useCodecovAIInstalledRepos.tsx deleted file mode 100644 index 8d9074f03c..0000000000 --- a/src/services/codecovAI/useCodecovAIInstalledRepos.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import z from 'zod' - -import Api from 'shared/api' -import { rejectNetworkError } from 'shared/api/rejectNetworkError' - -const ResponseSchema = z.object({ - owner: z - .object({ - aiEnabledRepos: z.array(z.string()).nullable(), - }) - .nullable(), -}) - -const query = ` - query GetCodecovAIInstalledRepos($username: String!) { - owner(username: $username) { - aiEnabledRepos - } - } -` - -interface CodecovAIInstalledReposProps { - owner: string - provider: string -} - -export function useCodecovAIInstalledRepos({ - owner, - provider, -}: CodecovAIInstalledReposProps) { - return useQuery({ - queryKey: ['GetCodecovAIInstalledRepos', provider, owner], - queryFn: ({ signal }) => { - return Api.graphql({ - provider, - query, - signal, - variables: { - username: owner, - }, - }).then((res) => { - const callingFn = 'useCodecovAIInstalledRepos' - const parsedRes = ResponseSchema.safeParse(res?.data) - - if (!parsedRes.success) { - return rejectNetworkError({ - errorName: 'Parsing Error', - errorDetails: { callingFn, error: parsedRes.error }, - }) - } - - return { - aiEnabledRepos: parsedRes.data.owner?.aiEnabledRepos, - } - }) - }, - }) -} diff --git a/src/services/navigation/useNavLinks.ts b/src/services/navigation/useNavLinks.ts index 8e917a1a96..476c7f655e 100644 --- a/src/services/navigation/useNavLinks.ts +++ b/src/services/navigation/useNavLinks.ts @@ -93,11 +93,6 @@ export function useNavLinks() { `/analytics/${provider}/${owner}`, isExternalLink: false, }, - codecovAI: { - path: ({ provider = p, owner = o } = { provider: p, owner: o }) => - `/codecovai/${provider}/${owner}`, - isExternalLink: false, - }, repo: { path: ( { provider = p, owner = o, repo = r } = { diff --git a/src/services/navigation/useStaticNavLinks.ts b/src/services/navigation/useStaticNavLinks.ts index cadb207d56..49b3da814e 100644 --- a/src/services/navigation/useStaticNavLinks.ts +++ b/src/services/navigation/useStaticNavLinks.ts @@ -116,13 +116,6 @@ export function useStaticNavLinks() { isExternalLink: true, openNewTab: true, }, - codecovAIAppInstallation: { - text: 'Install the Codecov AI app for an org', - path: () => - `https://github.com/apps/${config.GH_APP_AI}/installations/new`, - isExternalLink: true, - openNewTab: true, - }, userAppManagePage: { text: 'User App Manage/Access Page', path: () => @@ -510,12 +503,6 @@ export function useStaticNavLinks() { isExternalLink: true, openNewTab: true, }, - codecovAIDocs: { - text: 'Codecov AI Documentation', - path: () => `https://docs.codecov.com/docs/beta-codecov-ai`, - isExternalLink: true, - openNewTab: true, - }, yamlValidatorDocs: { text: 'YAML validator', path: () =>