-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix(plg): add links to the user nav dropdown #63462
base: main
Are you sure you want to change the base?
Changes from 7 commits
ebef38f
3424ebb
7ec61a0
889ecc9
75803fe
45bf8f5
9c551f7
19d5eeb
f1ad572
d884491
9e63554
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { QueryClient, QueryClientProvider } 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, | ||
}, | ||
}, | ||
}) | ||
|
||
/** | ||
* 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<React.PropsWithChildren<{ isSourcegraphDotCom: boolean }>> = ({ | ||
isSourcegraphDotCom, | ||
children, | ||
}) => { | ||
if (!isSourcegraphDotCom) { | ||
return <>{children}</> | ||
} | ||
|
||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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<Props> = ({ telemetryRec | |||||
error: useCodyPlanError, | ||||||
data: userCodyPlanData, | ||||||
} = useQuery<UserCodyPlanResult, UserCodyPlanVariables>(USER_CODY_PLAN, {}) | ||||||
const subscriptionSummaryQuery = useSubscriptionSummary() | ||||||
|
||||||
useEffect( | ||||||
function recordViewEvent() { | ||||||
|
@@ -46,7 +47,7 @@ const AuthenticatedCodySubscriptionManagePage: React.FC<Props> = ({ telemetryRec | |||||
[telemetryRecorder] | ||||||
) | ||||||
|
||||||
if (userCodyPlanLoading) { | ||||||
if (userCodyPlanLoading || subscriptionSummaryQuery.isLoading) { | ||||||
return <LoadingSpinner /> | ||||||
} | ||||||
|
||||||
|
@@ -55,18 +56,32 @@ const AuthenticatedCodySubscriptionManagePage: React.FC<Props> = ({ telemetryRec | |||||
return null | ||||||
} | ||||||
|
||||||
if (subscriptionSummaryQuery.isError) { | ||||||
logger.error('Failed to fetch Cody subscription summary', subscriptionSummaryQuery.error) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is just logging an error and returning It just seems like this could lead to a situation where the user has a blank page or some awkward looking UI, which is a little worse than having a clear "error" state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chrsmith, WDYT about throwing an error that is propagated to the closest There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's fine, then we may want to handle the other failed API calls inside components in a similar way (search). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc: @rrhyne There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that error message is fine for now but it would be better to update the text to be more end user friendly by removing “your site admin” and linking “Sourcegraph support” to the proper channel on discord, or the best place to resolve the error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This SGTM. The "fall back to the default error handler" seems super reasonable. And I also like Rob's suggestions. (Which I'm guessing would just be within the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replacing |
||||||
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 <Navigate to={CodyProRoutes.NewProSubscription} replace={true} /> | ||||||
} | ||||||
|
||||||
if (subscriptionSummaryQuery.data.userRole !== 'admin') { | ||||||
return <Navigate to={CodyProRoutes.Manage} replace={true} /> | ||||||
} | ||||||
|
||||||
return ( | ||||||
<Page className="d-flex flex-column"> | ||||||
<PageTitle title="Manage subscription" /> | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', () => { | ||
taras-yemets marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const useSubscriptionSummaryMock = vi.spyOn(subscriptionQueries, 'useSubscriptionSummary') | ||
|
||
afterEach(() => { | ||
useSubscriptionSummaryMock.mockReset() | ||
}) | ||
|
||
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) | ||
}) | ||
|
||
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, | ||
} | ||
mockSubscriptionSummary(summary) | ||
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', {}) | ||
mockSubscriptionSummary(summary) | ||
}) | ||
|
||
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') | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
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]) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.