Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit ebef38f

Browse files
committed
fix(plg): Cody Pro routes conditions & add links to the user nav dropdown (#63378)
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|<img width="1605" alt="Screenshot 2024-06-20 at 12 57 13" src="https://github.com/sourcegraph/sourcegraph/assets/25318659/e7b995fd-4322-4f7f-86bb-c7027cacd644"><img width="1605" alt="Screenshot 2024-06-20 at 13 10 28" src="https://github.com/sourcegraph/sourcegraph/assets/25318659/84be111c-76e2-4b14-9b73-ebdd5f8cbb9a">| |Member|<img width="1605" alt="Screenshot 2024-06-20 at 12 56 50" src="https://github.com/sourcegraph/sourcegraph/assets/25318659/aefd8b3b-29fe-4957-87ee-b493eb489922"><img width="1605" alt="Screenshot 2024-06-20 at 13 09 51" src="https://github.com/sourcegraph/sourcegraph/assets/25318659/8209e7a0-4deb-4681-9f6b-3cfde8110842">| <!-- 💡 To write a useful PR description, make sure that your description covers: - WHAT this PR is changing: - How was it PREVIOUSLY. - How it will be from NOW on. - WHY this PR is needed. - CONTEXT, i.e. to which initiative, project or RFC it belongs. The structure of the description doesn't matter as much as covering these points, so use your best judgement based on your context. Learn how to write good pull request description: https://www.notion.so/sourcegraph/Write-a-good-pull-request-description-610a7fd3e613496eb76f450db5a49b6e?pvs=4 --> ## 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 <!-- All pull requests REQUIRE a test plan: https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles --> ## Changelog <!-- 1. Ensure your pull request title is formatted as: $type($domain): $what 2. Add bullet list items for each additional detail you want to cover (see example below) 3. You can edit this after the pull request was merged, as long as release shipping it hasn't been promoted to the public. 4. For more information, please see this how-to https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c? Audience: TS/CSE > Customers > Teammates (in that order). Cheat sheet: $type = chore|fix|feat $domain: source|search|ci|release|plg|cody|local|... --> <!-- Example: Title: fix(search): parse quotes with the appropriate context Changelog section: ## Changelog - When a quote is used with regexp pattern type, then ... - Refactored underlying code. -->
1 parent b47c376 commit ebef38f

File tree

10 files changed

+106
-44
lines changed

10 files changed

+106
-44
lines changed

client/web/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ ts_project(
240240
"src/cody/management/UseCodyInEditorSection.tsx",
241241
"src/cody/management/api/client.ts",
242242
"src/cody/management/api/components/CodyProApiClient.ts",
243-
"src/cody/management/api/react-query/QueryClientProvider.tsx",
243+
"src/cody/management/api/react-query/CodyProApiProvider.tsx",
244244
"src/cody/management/api/react-query/callCodyProApi.ts",
245245
"src/cody/management/api/react-query/invites.ts",
246246
"src/cody/management/api/react-query/queryKeys.ts",

client/web/src/LegacySourcegraphWebApp.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard'
4141
import { authenticatedUser as authenticatedUserSubject, type AuthenticatedUser, authenticatedUserValue } from './auth'
4242
import { getWebGraphQLClient } from './backend/graphql'
4343
import { isBatchChangesExecutionEnabled } from './batches'
44+
import { CodyProApiProvider } from './cody/management/api/react-query/CodyProApiProvider'
4445
import { ComponentsComposer } from './components/ComponentsComposer'
4546
import { ErrorBoundary } from './components/ErrorBoundary'
4647
import { FeatureFlagsLocalOverrideAgent } from './featureFlags/FeatureFlagsProvider'
@@ -260,6 +261,7 @@ export class LegacySourcegraphWebApp extends React.Component<StaticAppConfig, Le
260261
<SearchResultsCacheProvider />,
261262
<SearchQueryStateStoreProvider useSearchQueryState={useNavbarQueryState} />,
262263
<LegacyRouteContextProvider context={legacyContext} />,
264+
<CodyProApiProvider isSourcegraphDotCom={window.context.sourcegraphDotComMode} />,
263265
/* eslint-enable react/no-children-prop, react/jsx-key */
264266
]}
265267
>

client/web/src/SourcegraphWebApp.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
2929
import { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard'
3030

3131
import { authenticatedUser as authenticatedUserSubject, type AuthenticatedUser, authenticatedUserValue } from './auth'
32+
import { CodyProApiProvider } from './cody/management/api/react-query/CodyProApiProvider'
3233
import { ComponentsComposer } from './components/ComponentsComposer'
3334
import { ErrorBoundary, RouteError } from './components/ErrorBoundary'
3435
import { FeatureFlagsLocalOverrideAgent } from './featureFlags/FeatureFlagsProvider'
@@ -283,6 +284,7 @@ export const SourcegraphWebApp: FC<SourcegraphWebAppProps> = props => {
283284
...props,
284285
}}
285286
/>,
287+
<CodyProApiProvider isSourcegraphDotCom={props.isSourcegraphDotCom} />,
286288
/* eslint-enable react/no-children-prop, react/jsx-key */
287289
]}
288290
>

client/web/src/cody/codyProRoutes.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent'
44

55
import { type LegacyLayoutRouteContext, LegacyRoute } from '../LegacyRouteContext'
66

7-
import { QueryClientProvider } from './management/api/react-query/QueryClientProvider'
87
import { isEmbeddedCodyProUIEnabled } from './util'
98

109
export enum CodyProRoutes {
@@ -78,9 +77,5 @@ interface CodyProPageProps extends Pick<LegacyLayoutRouteContext, 'authenticated
7877
*/
7978
const CodyProPage: React.FC<CodyProPageProps> = props => {
8079
const Component = routeComponents[props.path]
81-
return (
82-
<QueryClientProvider>
83-
<Component authenticatedUser={props.authenticatedUser} telemetryRecorder={props.telemetryRecorder} />
84-
</QueryClientProvider>
85-
)
80+
return <Component authenticatedUser={props.authenticatedUser} telemetryRecorder={props.telemetryRecorder} />
8681
}

client/web/src/cody/management/CodyManagementPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export const CodyManagementPage: React.FunctionComponent<CodyManagementPageProps
175175
)}
176176
</Text>
177177
</div>
178-
{isUserOnProTier && (
178+
{isAdmin && (
179179
<div>
180180
<ButtonLink
181181
variant="primary"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2+
3+
// Tweak the default queries and mutations behavior.
4+
// See defaults here: https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults
5+
const queryClient = new QueryClient({
6+
defaultOptions: {
7+
queries: {
8+
// If query failed, it's not likely that refetching it will succeed, so don't retry.
9+
retry: false,
10+
},
11+
mutations: {
12+
// If query failed, it's not likely that refetching it will succeed, so don't retry.
13+
retry: false,
14+
},
15+
},
16+
})
17+
18+
/**
19+
* CodyProApiProvider wraps its children with the react-query QueryClientProvider.
20+
* It is used to access the Cody Pro API and is only utilized on dotcom.
21+
* In enterprise mode, it simply passes through the children.
22+
*/
23+
export const CodyProApiProvider: React.FC<React.PropsWithChildren<{ isSourcegraphDotCom: boolean }>> = ({
24+
isSourcegraphDotCom,
25+
children,
26+
}) => {
27+
if (!isSourcegraphDotCom) {
28+
return <>{children}</>
29+
}
30+
31+
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
32+
}

client/web/src/cody/management/api/react-query/QueryClientProvider.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type { LegacyLayoutRouteContext } from '../../../../LegacyRouteContext'
2020
import { CodyProRoutes } from '../../../codyProRoutes'
2121
import { PageHeaderIcon } from '../../../components/PageHeaderIcon'
2222
import { USER_CODY_PLAN } from '../../../subscription/queries'
23-
import { useCurrentSubscription } from '../../api/react-query/subscriptions'
23+
import { useCurrentSubscription, useSubscriptionSummary } from '../../api/react-query/subscriptions'
2424

2525
import { InvoiceHistory } from './InvoiceHistory'
2626
import { PaymentDetails } from './PaymentDetails'
@@ -38,6 +38,7 @@ const AuthenticatedCodySubscriptionManagePage: React.FC<Props> = ({ telemetryRec
3838
error: useCodyPlanError,
3939
data: userCodyPlanData,
4040
} = useQuery<UserCodyPlanResult, UserCodyPlanVariables>(USER_CODY_PLAN, {})
41+
const subscriptionSummaryQuery = useSubscriptionSummary()
4142

4243
useEffect(
4344
function recordViewEvent() {
@@ -46,7 +47,7 @@ const AuthenticatedCodySubscriptionManagePage: React.FC<Props> = ({ telemetryRec
4647
[telemetryRecorder]
4748
)
4849

49-
if (userCodyPlanLoading) {
50+
if (userCodyPlanLoading || subscriptionSummaryQuery.isLoading) {
5051
return <LoadingSpinner />
5152
}
5253

@@ -55,18 +56,32 @@ const AuthenticatedCodySubscriptionManagePage: React.FC<Props> = ({ telemetryRec
5556
return null
5657
}
5758

59+
if (subscriptionSummaryQuery.isError) {
60+
logger.error('Failed to fetch Cody subscription summary', subscriptionSummaryQuery.error)
61+
return null
62+
}
63+
5864
const subscriptionData = userCodyPlanData?.currentUser?.codySubscription
5965
if (!subscriptionData) {
6066
logger.error('Cody subscription data is not available.')
6167
return null
6268
}
6369

70+
if (!subscriptionSummaryQuery.data) {
71+
logger.error('Cody subscription summary is not available.')
72+
return null
73+
}
74+
6475
// This page only applies to users who have a Cody Pro subscription to manage.
6576
// Otherwise, direct them to the ./new page to sign up.
6677
if (subscriptionData.plan !== CodySubscriptionPlan.PRO) {
6778
return <Navigate to={CodyProRoutes.NewProSubscription} replace={true} />
6879
}
6980

81+
if (subscriptionSummaryQuery.data.userRole !== 'admin') {
82+
return <Navigate to={CodyProRoutes.Manage} replace={true} />
83+
}
84+
7085
return (
7186
<Page className="d-flex flex-column">
7287
<PageTitle title="Manage subscription" />

client/web/src/nav/UserNavItem.test.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'
99
import { AnchorLink, RouterLink, setLinkComponent } from '@sourcegraph/wildcard'
1010
import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'
1111

12+
import { CodyProApiProvider } from '../cody/management/api/react-query/CodyProApiProvider'
13+
1214
import { UserNavItem, type UserNavItemProps } from './UserNavItem'
1315

1416
vi.mock('../util/license', () => ({
@@ -56,18 +58,22 @@ describe('UserNavItem', () => {
5658
emails: [],
5759
}
5860

61+
const isSourcegraphDotCom = true
62+
5963
test('simple', () => {
6064
expect(
6165
render(
6266
<MemoryRouter>
6367
<MockedTestProvider>
64-
<UserNavItem
65-
showKeyboardShortcutsHelp={() => undefined}
66-
authenticatedUser={USER}
67-
isSourcegraphDotCom={true}
68-
showFeedbackModal={() => undefined}
69-
telemetryService={NOOP_TELEMETRY_SERVICE}
70-
/>
68+
<CodyProApiProvider isSourcegraphDotCom={isSourcegraphDotCom}>
69+
<UserNavItem
70+
showKeyboardShortcutsHelp={() => undefined}
71+
authenticatedUser={USER}
72+
isSourcegraphDotCom={true}
73+
showFeedbackModal={() => undefined}
74+
telemetryService={NOOP_TELEMETRY_SERVICE}
75+
/>
76+
</CodyProApiProvider>
7177
</MockedTestProvider>
7278
</MemoryRouter>
7379
).asFragment()
@@ -77,13 +83,15 @@ describe('UserNavItem', () => {
7783
test('logout click triggers page refresh instead of performing client-side only navigation', async () => {
7884
const result = renderWithBrandedContext(
7985
<MockedTestProvider>
80-
<UserNavItem
81-
showKeyboardShortcutsHelp={() => undefined}
82-
authenticatedUser={USER}
83-
isSourcegraphDotCom={true}
84-
showFeedbackModal={() => undefined}
85-
telemetryService={NOOP_TELEMETRY_SERVICE}
86-
/>
86+
<CodyProApiProvider isSourcegraphDotCom={isSourcegraphDotCom}>
87+
<UserNavItem
88+
showKeyboardShortcutsHelp={() => undefined}
89+
authenticatedUser={USER}
90+
isSourcegraphDotCom={isSourcegraphDotCom}
91+
showFeedbackModal={() => undefined}
92+
telemetryService={NOOP_TELEMETRY_SERVICE}
93+
/>
94+
</CodyProApiProvider>
8795
</MockedTestProvider>
8896
)
8997

client/web/src/nav/UserNavItem.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
} from '@sourcegraph/wildcard'
2727

2828
import type { AuthenticatedUser } from '../auth'
29+
import { CodyProRoutes } from '../cody/codyProRoutes'
30+
import { useSubscriptionSummary } from '../cody/management/api/react-query/subscriptions'
2931
import { enableDevSettings, isSourcegraphDev, useDeveloperSettings } from '../stores'
3032

3133
import { useNewSearchNavigation } from './new-global-navigation'
@@ -130,6 +132,7 @@ export const UserNavItem: FC<UserNavItemProps> = props => {
130132
<MenuHeader className={styles.dropdownHeader}>
131133
Signed in as <strong>@{authenticatedUser.username}</strong>
132134
</MenuHeader>
135+
{isSourcegraphDotCom && <CodyProSection />}
133136
<MenuDivider className={styles.dropdownDivider} />
134137
<MenuLink as={Link} to={authenticatedUser.settingsURL!}>
135138
Settings
@@ -241,3 +244,28 @@ export const UserNavItem: FC<UserNavItemProps> = props => {
241244
</>
242245
)
243246
}
247+
248+
const CodyProSection: React.FC = () => {
249+
const { data } = useSubscriptionSummary()
250+
251+
if (!data) {
252+
return null
253+
}
254+
255+
return (
256+
<>
257+
<MenuDivider className={styles.dropdownDivider} />
258+
<MenuHeader className={styles.dropdownHeader}>Cody PRO</MenuHeader>
259+
<MenuLink as={Link} to={CodyProRoutes.ManageTeam}>
260+
Manage team
261+
</MenuLink>
262+
263+
{/* only team admins can manage subscription */}
264+
{data.userRole === 'admin' && (
265+
<MenuLink as={Link} to={CodyProRoutes.SubscriptionManage}>
266+
Manage subscription
267+
</MenuLink>
268+
)}
269+
</>
270+
)
271+
}

0 commit comments

Comments
 (0)