From ebef38ff565627da7412e7371c752109b12e0f4c Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Thu, 20 Jun 2024 14:12:19 +0300 Subject: [PATCH 1/6] fix(plg): Cody Pro routes conditions & add links to the user nav dropdown (#63378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/sourcegraph/sourcegraph/issues/63264 1. Adds Cody Pro section to the user nav dropdown on dotcom: - team admins have links to "cody/subscription/manage" and "cody/team/manage" pages - team members have only link only to the "cody/team/manage" page 2. Makes subscription management page only available for team admins. If team members try to navigate this page by changing the URL, they will be redirected to the "/cody/manage" page. - Makes "Manage subscription" link on the "/cody/manage" available only for the team admins (previously - any Pro user) 3. Lifts `QueryClientProvider` (wrapper around react-query `ClientProvider` higher in the component tree so that Cody Pro API query and mutations can be used not only on the Cody Pro routes. [Design](https://www.figma.com/design/FMSdn1oKccJRHQPgf7053o/Cody-PLG-GA?node-id=5351-19126&t=LfNQR7vUCJhlNvyT-4) | Role | Screenshot | |--|--| |Admin|Screenshot 2024-06-20 at 12 57 13Screenshot 2024-06-20 at 13 10 28| |Member|Screenshot 2024-06-20 at 12 56 50Screenshot 2024-06-20 at 13 09 51| ## Test plan - Start Sourcegraph instance in dotcom mode - sign in as a Cody Pro team admin - ensure that "Manage subscription" and "Manage team" links are present in the user nav dropdown - ensure "/cody/manage" page has "Manage subscription" link - sign in as a Cody Pro team member (not admin) - ensure "Manage team" link is present in the user nav dropdown - navigate to "/cody/subscription/manage/ page by changing the URL - ensure you've been redirected to the "/cody/manage" page - ensure "/cody/manage" page doesn't have "Manage subscription" link - start Sourcegraph in the enterprise mode - ensure the mentioned links are not rendered ## Changelog --- client/web/BUILD.bazel | 2 +- client/web/src/LegacySourcegraphWebApp.tsx | 2 ++ client/web/src/SourcegraphWebApp.tsx | 2 ++ client/web/src/cody/codyProRoutes.tsx | 7 +--- .../cody/management/CodyManagementPage.tsx | 2 +- .../api/react-query/CodyProApiProvider.tsx | 32 +++++++++++++++++ .../api/react-query/QueryClientProvider.tsx | 20 ----------- .../manage/CodySubscriptionManagePage.tsx | 19 ++++++++-- client/web/src/nav/UserNavItem.test.tsx | 36 +++++++++++-------- client/web/src/nav/UserNavItem.tsx | 28 +++++++++++++++ 10 files changed, 106 insertions(+), 44 deletions(-) create mode 100644 client/web/src/cody/management/api/react-query/CodyProApiProvider.tsx delete mode 100644 client/web/src/cody/management/api/react-query/QueryClientProvider.tsx diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index d0262208d868..382bea2945c9 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -240,7 +240,7 @@ ts_project( "src/cody/management/UseCodyInEditorSection.tsx", "src/cody/management/api/client.ts", "src/cody/management/api/components/CodyProApiClient.ts", - "src/cody/management/api/react-query/QueryClientProvider.tsx", + "src/cody/management/api/react-query/CodyProApiProvider.tsx", "src/cody/management/api/react-query/callCodyProApi.ts", "src/cody/management/api/react-query/invites.ts", "src/cody/management/api/react-query/queryKeys.ts", diff --git a/client/web/src/LegacySourcegraphWebApp.tsx b/client/web/src/LegacySourcegraphWebApp.tsx index a1611be57460..d1223508cda0 100644 --- a/client/web/src/LegacySourcegraphWebApp.tsx +++ b/client/web/src/LegacySourcegraphWebApp.tsx @@ -41,6 +41,7 @@ import { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard' import { authenticatedUser as authenticatedUserSubject, type AuthenticatedUser, authenticatedUserValue } from './auth' import { getWebGraphQLClient } from './backend/graphql' import { isBatchChangesExecutionEnabled } from './batches' +import { CodyProApiProvider } from './cody/management/api/react-query/CodyProApiProvider' import { ComponentsComposer } from './components/ComponentsComposer' import { ErrorBoundary } from './components/ErrorBoundary' import { FeatureFlagsLocalOverrideAgent } from './featureFlags/FeatureFlagsProvider' @@ -260,6 +261,7 @@ export class LegacySourcegraphWebApp extends React.Component, , , + , /* eslint-enable react/no-children-prop, react/jsx-key */ ]} > diff --git a/client/web/src/SourcegraphWebApp.tsx b/client/web/src/SourcegraphWebApp.tsx index 11795aa0794f..3758b833b548 100644 --- a/client/web/src/SourcegraphWebApp.tsx +++ b/client/web/src/SourcegraphWebApp.tsx @@ -29,6 +29,7 @@ import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard' import { authenticatedUser as authenticatedUserSubject, type AuthenticatedUser, authenticatedUserValue } from './auth' +import { CodyProApiProvider } from './cody/management/api/react-query/CodyProApiProvider' import { ComponentsComposer } from './components/ComponentsComposer' import { ErrorBoundary, RouteError } from './components/ErrorBoundary' import { FeatureFlagsLocalOverrideAgent } from './featureFlags/FeatureFlagsProvider' @@ -283,6 +284,7 @@ export const SourcegraphWebApp: FC = props => { ...props, }} />, + , /* eslint-enable react/no-children-prop, react/jsx-key */ ]} > diff --git a/client/web/src/cody/codyProRoutes.tsx b/client/web/src/cody/codyProRoutes.tsx index e390e2ead64e..c9a3ebfadf1f 100644 --- a/client/web/src/cody/codyProRoutes.tsx +++ b/client/web/src/cody/codyProRoutes.tsx @@ -4,7 +4,6 @@ import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' import { type LegacyLayoutRouteContext, LegacyRoute } from '../LegacyRouteContext' -import { QueryClientProvider } from './management/api/react-query/QueryClientProvider' import { isEmbeddedCodyProUIEnabled } from './util' export enum CodyProRoutes { @@ -78,9 +77,5 @@ interface CodyProPageProps extends Pick = props => { const Component = routeComponents[props.path] - return ( - - - - ) + return } diff --git a/client/web/src/cody/management/CodyManagementPage.tsx b/client/web/src/cody/management/CodyManagementPage.tsx index 6db526e69b24..311f5ee64fba 100644 --- a/client/web/src/cody/management/CodyManagementPage.tsx +++ b/client/web/src/cody/management/CodyManagementPage.tsx @@ -175,7 +175,7 @@ export const CodyManagementPage: React.FunctionComponent - {isUserOnProTier && ( + {isAdmin && (
> = ({ + isSourcegraphDotCom, + children, +}) => { + if (!isSourcegraphDotCom) { + return <>{children} + } + + return {children} +} diff --git a/client/web/src/cody/management/api/react-query/QueryClientProvider.tsx b/client/web/src/cody/management/api/react-query/QueryClientProvider.tsx deleted file mode 100644 index 98f0d38386a1..000000000000 --- a/client/web/src/cody/management/api/react-query/QueryClientProvider.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { QueryClient, QueryClientProvider as ReactQueryClientProvider } from '@tanstack/react-query' - -// Tweak the default queries and mutations behavior. -// See defaults here: https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - // If query failed, it's not likely that refetching it will succeed, so don't retry. - retry: false, - }, - mutations: { - // If query failed, it's not likely that refetching it will succeed, so don't retry. - retry: false, - }, - }, -}) - -export const QueryClientProvider: React.FC> = ({ children }) => ( - {children} -) diff --git a/client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx b/client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx index 357bf0db8ef5..9310665f0f74 100644 --- a/client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx +++ b/client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx @@ -20,7 +20,7 @@ import type { LegacyLayoutRouteContext } from '../../../../LegacyRouteContext' import { CodyProRoutes } from '../../../codyProRoutes' import { PageHeaderIcon } from '../../../components/PageHeaderIcon' import { USER_CODY_PLAN } from '../../../subscription/queries' -import { useCurrentSubscription } from '../../api/react-query/subscriptions' +import { useCurrentSubscription, useSubscriptionSummary } from '../../api/react-query/subscriptions' import { InvoiceHistory } from './InvoiceHistory' import { PaymentDetails } from './PaymentDetails' @@ -38,6 +38,7 @@ const AuthenticatedCodySubscriptionManagePage: React.FC = ({ telemetryRec error: useCodyPlanError, data: userCodyPlanData, } = useQuery(USER_CODY_PLAN, {}) + const subscriptionSummaryQuery = useSubscriptionSummary() useEffect( function recordViewEvent() { @@ -46,7 +47,7 @@ const AuthenticatedCodySubscriptionManagePage: React.FC = ({ telemetryRec [telemetryRecorder] ) - if (userCodyPlanLoading) { + if (userCodyPlanLoading || subscriptionSummaryQuery.isLoading) { return } @@ -55,18 +56,32 @@ const AuthenticatedCodySubscriptionManagePage: React.FC = ({ telemetryRec return null } + if (subscriptionSummaryQuery.isError) { + logger.error('Failed to fetch Cody subscription summary', subscriptionSummaryQuery.error) + return null + } + const subscriptionData = userCodyPlanData?.currentUser?.codySubscription if (!subscriptionData) { logger.error('Cody subscription data is not available.') return null } + if (!subscriptionSummaryQuery.data) { + logger.error('Cody subscription summary is not available.') + return null + } + // This page only applies to users who have a Cody Pro subscription to manage. // Otherwise, direct them to the ./new page to sign up. if (subscriptionData.plan !== CodySubscriptionPlan.PRO) { return } + if (subscriptionSummaryQuery.data.userRole !== 'admin') { + return + } + return ( diff --git a/client/web/src/nav/UserNavItem.test.tsx b/client/web/src/nav/UserNavItem.test.tsx index 26b183273cbb..d8321b33a6e1 100644 --- a/client/web/src/nav/UserNavItem.test.tsx +++ b/client/web/src/nav/UserNavItem.test.tsx @@ -9,6 +9,8 @@ import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo' import { AnchorLink, RouterLink, setLinkComponent } from '@sourcegraph/wildcard' import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing' +import { CodyProApiProvider } from '../cody/management/api/react-query/CodyProApiProvider' + import { UserNavItem, type UserNavItemProps } from './UserNavItem' vi.mock('../util/license', () => ({ @@ -56,18 +58,22 @@ describe('UserNavItem', () => { emails: [], } + const isSourcegraphDotCom = true + test('simple', () => { expect( render( - undefined} - authenticatedUser={USER} - isSourcegraphDotCom={true} - showFeedbackModal={() => undefined} - telemetryService={NOOP_TELEMETRY_SERVICE} - /> + + undefined} + authenticatedUser={USER} + isSourcegraphDotCom={true} + showFeedbackModal={() => undefined} + telemetryService={NOOP_TELEMETRY_SERVICE} + /> + ).asFragment() @@ -77,13 +83,15 @@ describe('UserNavItem', () => { test('logout click triggers page refresh instead of performing client-side only navigation', async () => { const result = renderWithBrandedContext( - undefined} - authenticatedUser={USER} - isSourcegraphDotCom={true} - showFeedbackModal={() => undefined} - telemetryService={NOOP_TELEMETRY_SERVICE} - /> + + undefined} + authenticatedUser={USER} + isSourcegraphDotCom={isSourcegraphDotCom} + showFeedbackModal={() => undefined} + telemetryService={NOOP_TELEMETRY_SERVICE} + /> + ) diff --git a/client/web/src/nav/UserNavItem.tsx b/client/web/src/nav/UserNavItem.tsx index 64d301b9c54b..7046ec1be20d 100644 --- a/client/web/src/nav/UserNavItem.tsx +++ b/client/web/src/nav/UserNavItem.tsx @@ -26,6 +26,8 @@ import { } from '@sourcegraph/wildcard' import type { AuthenticatedUser } from '../auth' +import { CodyProRoutes } from '../cody/codyProRoutes' +import { useSubscriptionSummary } from '../cody/management/api/react-query/subscriptions' import { enableDevSettings, isSourcegraphDev, useDeveloperSettings } from '../stores' import { useNewSearchNavigation } from './new-global-navigation' @@ -130,6 +132,7 @@ export const UserNavItem: FC = props => { Signed in as @{authenticatedUser.username} + {isSourcegraphDotCom && } Settings @@ -241,3 +244,28 @@ export const UserNavItem: FC = props => { ) } + +const CodyProSection: React.FC = () => { + const { data } = useSubscriptionSummary() + + if (!data) { + return null + } + + return ( + <> + + Cody PRO + + Manage team + + + {/* only team admins can manage subscription */} + {data.userRole === 'admin' && ( + + Manage subscription + + )} + + ) +} From 3424ebbaf27c58a2b4b095364a804f199ea47fcc Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Tue, 25 Jun 2024 11:03:49 +0300 Subject: [PATCH 2/6] feat(plg): add Cody Pro routes to the user nav dropdown --- client/web/src/cody/useCodyProNavLinks.ts | 22 ++++++ client/web/src/nav/UserNavItem.test.tsx | 96 +++++++++++++++++------ client/web/src/nav/UserNavItem.tsx | 19 ++--- 3 files changed, 103 insertions(+), 34 deletions(-) create mode 100644 client/web/src/cody/useCodyProNavLinks.ts diff --git a/client/web/src/cody/useCodyProNavLinks.ts b/client/web/src/cody/useCodyProNavLinks.ts new file mode 100644 index 000000000000..660848086035 --- /dev/null +++ b/client/web/src/cody/useCodyProNavLinks.ts @@ -0,0 +1,22 @@ +import { useMemo } from 'react' + +import { CodyProRoutes } from './codyProRoutes' +import { useSubscriptionSummary } from './management/api/react-query/subscriptions' +import { getManageSubscriptionPageURL, isEmbeddedCodyProUIEnabled } from './util' + +export const useCodyProNavLinks = (): { to: string; label: string }[] => { + const { data } = useSubscriptionSummary() + + return useMemo(() => { + if (!data || data.userRole !== 'admin') { + return [] + } + const items = [{ to: getManageSubscriptionPageURL(), label: 'Manage subscription' }] + + if (isEmbeddedCodyProUIEnabled()) { + items.push({ to: CodyProRoutes.ManageTeam, label: 'Manage team' }) + } + + return items + }, [data]) +} diff --git a/client/web/src/nav/UserNavItem.test.tsx b/client/web/src/nav/UserNavItem.test.tsx index d8321b33a6e1..79e4ac590ad9 100644 --- a/client/web/src/nav/UserNavItem.test.tsx +++ b/client/web/src/nav/UserNavItem.test.tsx @@ -2,14 +2,14 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { MemoryRouter } from 'react-router-dom' import sinon from 'sinon' -import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest' +import { afterAll, beforeAll, afterEach, describe, expect, test, vi, beforeEach } from 'vitest' import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo' import { AnchorLink, RouterLink, setLinkComponent } from '@sourcegraph/wildcard' import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing' -import { CodyProApiProvider } from '../cody/management/api/react-query/CodyProApiProvider' +import * as codyProHooks from '../cody/useCodyProNavLinks' import { UserNavItem, type UserNavItemProps } from './UserNavItem' @@ -28,6 +28,14 @@ describe('UserNavItem', () => { setLinkComponent(AnchorLink) }) + const useCodyProNavLinksMock = vi.spyOn(codyProHooks, 'useCodyProNavLinks') + beforeEach(() => { + useCodyProNavLinksMock.mockReturnValue([]) + }) + afterEach(() => { + useCodyProNavLinksMock.mockReset() + }) + const USER: UserNavItemProps['authenticatedUser'] = { username: 'alice', displayName: 'alice doe', @@ -58,22 +66,18 @@ describe('UserNavItem', () => { emails: [], } - const isSourcegraphDotCom = true - test('simple', () => { expect( render( - - undefined} - authenticatedUser={USER} - isSourcegraphDotCom={true} - showFeedbackModal={() => undefined} - telemetryService={NOOP_TELEMETRY_SERVICE} - /> - + undefined} + authenticatedUser={USER} + isSourcegraphDotCom={true} + showFeedbackModal={() => undefined} + telemetryService={NOOP_TELEMETRY_SERVICE} + /> ).asFragment() @@ -83,15 +87,13 @@ describe('UserNavItem', () => { test('logout click triggers page refresh instead of performing client-side only navigation', async () => { const result = renderWithBrandedContext( - - undefined} - authenticatedUser={USER} - isSourcegraphDotCom={isSourcegraphDotCom} - showFeedbackModal={() => undefined} - telemetryService={NOOP_TELEMETRY_SERVICE} - /> - + undefined} + authenticatedUser={USER} + isSourcegraphDotCom={true} + showFeedbackModal={() => undefined} + telemetryService={NOOP_TELEMETRY_SERVICE} + /> ) @@ -104,4 +106,54 @@ describe('UserNavItem', () => { expect(result.locationRef.entries.length).toBe(1) expect(result.locationRef.entries.find(({ pathname }) => pathname.includes('sign-out'))).toBe(undefined) }) + + describe('Cody Pro section', () => { + const setup = (isSourcegraphDotCom: boolean) => { + renderWithBrandedContext( + + undefined} + authenticatedUser={USER} + isSourcegraphDotCom={isSourcegraphDotCom} + showFeedbackModal={() => undefined} + telemetryService={NOOP_TELEMETRY_SERVICE} + /> + + ) + userEvent.click(screen.getByRole('button')) + } + + describe('dotcom', () => { + test('renders provided links', () => { + const links = [ + { to: '/foo', label: 'Foo' }, + { to: '/bar', label: 'Bar' }, + ] + useCodyProNavLinksMock.mockReturnValue(links) + setup(true) + + for (const link of links) { + const el = screen.getByText(link.label) + expect(el).toHaveAttribute('href', link.to) + } + }) + + test('is not rendered if no links provided', () => { + useCodyProNavLinksMock.mockReturnValue([]) + setup(true) + + expect(useCodyProNavLinksMock).toHaveBeenCalled() + expect(screen.queryByText('Cody Pro')).not.toBeInTheDocument() + }) + }) + + describe('enterprise', () => { + test('is not rendered', () => { + setup(false) + + // Cody Pro section is not rendered thus useCodyProNavLinks hook is not called + expect(useCodyProNavLinksMock).not.toHaveBeenCalled() + }) + }) + }) }) diff --git a/client/web/src/nav/UserNavItem.tsx b/client/web/src/nav/UserNavItem.tsx index 7046ec1be20d..5244d78661bc 100644 --- a/client/web/src/nav/UserNavItem.tsx +++ b/client/web/src/nav/UserNavItem.tsx @@ -26,8 +26,7 @@ import { } from '@sourcegraph/wildcard' import type { AuthenticatedUser } from '../auth' -import { CodyProRoutes } from '../cody/codyProRoutes' -import { useSubscriptionSummary } from '../cody/management/api/react-query/subscriptions' +import { useCodyProNavLinks } from '../cody/useCodyProNavLinks' import { enableDevSettings, isSourcegraphDev, useDeveloperSettings } from '../stores' import { useNewSearchNavigation } from './new-global-navigation' @@ -246,9 +245,9 @@ export const UserNavItem: FC = props => { } const CodyProSection: React.FC = () => { - const { data } = useSubscriptionSummary() + const links = useCodyProNavLinks() - if (!data) { + if (!links.length) { return null } @@ -256,16 +255,12 @@ const CodyProSection: React.FC = () => { <> Cody PRO - - Manage team - - {/* only team admins can manage subscription */} - {data.userRole === 'admin' && ( - - Manage subscription + {links.map(({ to, label }) => ( + + {label} - )} + ))} ) } From 889ecc90db5ca570509f58230720e29b88f93eaa Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Tue, 25 Jun 2024 14:39:15 +0300 Subject: [PATCH 3/6] add test --- .../web/src/cody/useCodyProNavLinks.test.tsx | 85 +++++++++++++++++++ client/web/src/cody/useCodyProNavLinks.ts | 1 + 2 files changed, 86 insertions(+) create mode 100644 client/web/src/cody/useCodyProNavLinks.test.tsx diff --git a/client/web/src/cody/useCodyProNavLinks.test.tsx b/client/web/src/cody/useCodyProNavLinks.test.tsx new file mode 100644 index 000000000000..0efc4213ba45 --- /dev/null +++ b/client/web/src/cody/useCodyProNavLinks.test.tsx @@ -0,0 +1,85 @@ +import { renderHook } from '@testing-library/react' +import { vi, describe, afterEach, test, expect, beforeEach } from 'vitest' + +import { CodyProRoutes } from './codyProRoutes' +import * as subscriptionQueries from './management/api/react-query/subscriptions' +import type { SubscriptionSummary } from './management/api/teamSubscriptions' +import { useCodyProNavLinks } from './useCodyProNavLinks' + +describe('useCodyProNavLinks', () => { + const useSubscriptionSummaryMock = vi.spyOn(subscriptionQueries, 'useSubscriptionSummary') + + afterEach(() => { + useSubscriptionSummaryMock.mockReset() + }) + + test('returns empty array if subscription summary is undefined', () => { + useSubscriptionSummaryMock.mockReturnValue({ data: undefined } as ReturnType< + typeof subscriptionQueries.useSubscriptionSummary + >) + const { result } = renderHook(() => useCodyProNavLinks()) + expect(result.current).toHaveLength(0) + }) + + test('returns empty array user is not admin', () => { + const summary: SubscriptionSummary = { + teamId: '018ff1b3-118c-7789-82e4-ab9106eed204', + userRole: 'member', + teamCurrentMembers: 2, + teamMaxMembers: 6, + subscriptionStatus: 'active', + cancelAtPeriodEnd: false, + } + useSubscriptionSummaryMock.mockReturnValue({ data: summary } as ReturnType< + typeof subscriptionQueries.useSubscriptionSummary + >) + const { result } = renderHook(() => useCodyProNavLinks()) + expect(result.current).toHaveLength(0) + }) + + describe('user is admin', () => { + const summary: SubscriptionSummary = { + teamId: '018ff1b3-118c-7789-82e4-ab9106eed204', + userRole: 'admin', + teamCurrentMembers: 2, + teamMaxMembers: 6, + subscriptionStatus: 'active', + cancelAtPeriodEnd: false, + } + + const setUseEmbeddedUI = (useEmbeddedUI: boolean) => { + vi.stubGlobal('context', { + frontendCodyProConfig: { + stripePublishableKey: 'pk_test_123', + sscBaseUrl: '', + useEmbeddedUI, + }, + }) + } + + beforeEach(() => { + vi.stubGlobal('context', {}) + useSubscriptionSummaryMock.mockReturnValue({ data: summary } as ReturnType< + typeof subscriptionQueries.useSubscriptionSummary + >) + }) + + test.skip('returns links to subscription and team management pages if embedded UI is enabled', () => { + setUseEmbeddedUI(true) + const { result } = renderHook(() => useCodyProNavLinks()) + expect(result.current).toHaveLength(2) + expect(result.current[0].label).toBe('Manage subscription') + expect(result.current[0].to).toBe(CodyProRoutes.SubscriptionManage) + expect(result.current[1].label).toBe('Manage team') + expect(result.current[1].to).toBe(CodyProRoutes.ManageTeam) + }) + + test('returns link to subscription management page if embedded UI is disabled', () => { + setUseEmbeddedUI(false) + const { result } = renderHook(() => useCodyProNavLinks()) + expect(result.current).toHaveLength(1) + expect(result.current[0].label).toBe('Manage subscription') + expect(result.current[0].to).toBe('https://accounts.sourcegraph.com/cody/subscription') + }) + }) +}) diff --git a/client/web/src/cody/useCodyProNavLinks.ts b/client/web/src/cody/useCodyProNavLinks.ts index 660848086035..fe79e853a3e2 100644 --- a/client/web/src/cody/useCodyProNavLinks.ts +++ b/client/web/src/cody/useCodyProNavLinks.ts @@ -11,6 +11,7 @@ export const useCodyProNavLinks = (): { to: string; label: string }[] => { if (!data || data.userRole !== 'admin') { return [] } + const items = [{ to: getManageSubscriptionPageURL(), label: 'Manage subscription' }] if (isEmbeddedCodyProUIEnabled()) { From 75803fe0d274421eac63c151dbba2beb5ed197dc Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Tue, 25 Jun 2024 15:10:50 +0300 Subject: [PATCH 4/6] refactor test --- client/web/src/cody/useCodyProNavLinks.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/web/src/cody/useCodyProNavLinks.test.tsx b/client/web/src/cody/useCodyProNavLinks.test.tsx index 0efc4213ba45..f7d27a589b61 100644 --- a/client/web/src/cody/useCodyProNavLinks.test.tsx +++ b/client/web/src/cody/useCodyProNavLinks.test.tsx @@ -13,10 +13,14 @@ describe('useCodyProNavLinks', () => { useSubscriptionSummaryMock.mockReset() }) - test('returns empty array if subscription summary is undefined', () => { - useSubscriptionSummaryMock.mockReturnValue({ data: undefined } as ReturnType< + const mockSubscriptionSummary = (summary?: SubscriptionSummary): void => { + useSubscriptionSummaryMock.mockReturnValue({ data: summary } as ReturnType< typeof subscriptionQueries.useSubscriptionSummary >) + } + + test('returns empty array if subscription summary is undefined', () => { + mockSubscriptionSummary() const { result } = renderHook(() => useCodyProNavLinks()) expect(result.current).toHaveLength(0) }) @@ -30,9 +34,7 @@ describe('useCodyProNavLinks', () => { subscriptionStatus: 'active', cancelAtPeriodEnd: false, } - useSubscriptionSummaryMock.mockReturnValue({ data: summary } as ReturnType< - typeof subscriptionQueries.useSubscriptionSummary - >) + mockSubscriptionSummary(summary) const { result } = renderHook(() => useCodyProNavLinks()) expect(result.current).toHaveLength(0) }) @@ -59,9 +61,7 @@ describe('useCodyProNavLinks', () => { beforeEach(() => { vi.stubGlobal('context', {}) - useSubscriptionSummaryMock.mockReturnValue({ data: summary } as ReturnType< - typeof subscriptionQueries.useSubscriptionSummary - >) + mockSubscriptionSummary(summary) }) test.skip('returns links to subscription and team management pages if embedded UI is enabled', () => { From 45bf8f5dc8cb9e63e02285c2ef645e7d1c9c8a06 Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Tue, 25 Jun 2024 15:30:15 +0300 Subject: [PATCH 5/6] bazel configure --- client/web/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index 75a5b0f3ac53..9be9332fcb8e 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -281,6 +281,7 @@ ts_project( "src/cody/upsell/vs-code.tsx", "src/cody/useCodyChat.tsx", "src/cody/useCodyIgnore.tsx", + "src/cody/useCodyProNavLinks.ts", "src/cody/util.ts", "src/cody/widgets/CodyRecipesWidget.tsx", "src/cody/widgets/components/Recipe.tsx", @@ -1897,6 +1898,7 @@ ts_project( "src/codeintel/ReferencesPanel.test.tsx", "src/cody/team/TeamMemberList.test.ts", "src/cody/useCodyIgnore.test.ts", + "src/cody/useCodyProNavLinks.test.tsx", "src/components/ErrorBoundary.test.tsx", "src/components/FilteredConnection/FilteredConnection.test.tsx", "src/components/FilteredConnection/hooks/usePageSwitcherPagination.test.tsx", From f1ad57229521b8c7600d9a0adc11722c4c253400 Mon Sep 17 00:00:00 2001 From: Taras Yemets Date: Wed, 26 Jun 2024 11:03:45 +0300 Subject: [PATCH 6/6] remove unused props --- client/web/src/LegacySourcegraphWebApp.tsx | 2 +- client/web/src/SourcegraphWebApp.tsx | 2 +- .../api/react-query/CodyProApiProvider.tsx | 14 +++----------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/client/web/src/LegacySourcegraphWebApp.tsx b/client/web/src/LegacySourcegraphWebApp.tsx index d1223508cda0..17743d5b758d 100644 --- a/client/web/src/LegacySourcegraphWebApp.tsx +++ b/client/web/src/LegacySourcegraphWebApp.tsx @@ -261,7 +261,7 @@ export class LegacySourcegraphWebApp extends React.Component, , , - , + , /* eslint-enable react/no-children-prop, react/jsx-key */ ]} > diff --git a/client/web/src/SourcegraphWebApp.tsx b/client/web/src/SourcegraphWebApp.tsx index 3758b833b548..1d9fc2bc5d6a 100644 --- a/client/web/src/SourcegraphWebApp.tsx +++ b/client/web/src/SourcegraphWebApp.tsx @@ -284,7 +284,7 @@ export const SourcegraphWebApp: FC = props => { ...props, }} />, - , + , /* eslint-enable react/no-children-prop, react/jsx-key */ ]} > diff --git a/client/web/src/cody/management/api/react-query/CodyProApiProvider.tsx b/client/web/src/cody/management/api/react-query/CodyProApiProvider.tsx index d40a01be60e6..18e4192ce838 100644 --- a/client/web/src/cody/management/api/react-query/CodyProApiProvider.tsx +++ b/client/web/src/cody/management/api/react-query/CodyProApiProvider.tsx @@ -18,15 +18,7 @@ const queryClient = new QueryClient({ /** * CodyProApiProvider wraps its children with the react-query QueryClientProvider. * It is used to access the Cody Pro API and is only utilized on dotcom. - * In enterprise mode, it simply passes through the children. */ -export const CodyProApiProvider: React.FC> = ({ - isSourcegraphDotCom, - children, -}) => { - if (!isSourcegraphDotCom) { - return <>{children} - } - - return {children} -} +export const CodyProApiProvider: React.FC> = ({ children }) => ( + {children} +)