From 0021f95d6c14611ec489b618a3a6fedd9d82bf4c Mon Sep 17 00:00:00 2001 From: Quinn Slack Date: Wed, 26 Jun 2024 22:29:54 -0700 Subject: [PATCH] =?UTF-8?q?remove=20Cody=20"upsells"=E2=80=94and=20all=20C?= =?UTF-8?q?ody=20links=20if=20Cody=20is=20disabled=20(#63430)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At a high level, we don't want to show annoying ads/upsells for Cody that are not useful. And if Cody is disabled, we don't want to show *any* links to Cody. ### Detailed desired behavior - Dotcom - Navbar - Unauthed: "Cody" single link to /cody (marketing page) - Authed: "Cody" dropdown with "Dashboard" (/cody/manage) and "Chat" (/cody/chat) - Routes - /cody: always the marketing page - /cody/manage: requires sign-in, shows Cody PLG subscription status for the user (Free plan is auto-opted-into by default) - /cody/chat: requires sign-in - Enterprise with Cody enabled on instance - Navbar - Cody NOT enabled for current user: "Cody" single link to /cody/dashboard - Cody enabled for current user: "Cody" dropdown with "Dashboard" (/cody/manage) and "Chat" (/cody/chat) - Routes - /cody: this link should not be present anywhere, but redirect to /cody/dashboard - /cody/manage: informational page, with editor/web links for Cody-enabled users and a "contact admin to get access" message for Cody-disabled users - /cody/chat: chat for Cody-enabled users, redirect to /cody/manage for Cody-disabled users - Enterprise with Cody NOT enabled on instance (`"cody.enabled": false` in site config) - Navbar: no Cody link or dropdown - Routes: all Cody routes 404 - All - Do not show a Cody upsell on the /search page This is an example of what we will KEEP for users on instances with Cody enabled but who do not themselves yet have access to Cody. This is useful because it informs users how to get access to Cody, and presumably their site admin wants people to request it who want to use it. ![image](https://github.com/sourcegraph/sourcegraph/assets/1976/c2adb086-44ec-4240-ad44-95981763fb72) Fixes https://linear.app/sourcegraph/issue/SRCH-529/hide-cody-ai-tab-and-cody-upsell-if-cody-is-not-enabled ### Unexpected code changes needed This ended up being a much bigger change than I expected because I found error-prone code that needed cleaning up: - Improve how we determine if Cody is enabled in the frontend code. Previously, we checked the license features in some places, `cody.enabled` site config in others, and the user's current RBAC permissions for Cody in yet others. The most error-prone was checking the license features, since a license may entitle the instance to Cody but the site admin may still choose to disable it. There were no places in the frontend code where checking the license's entitlements was actually correct, so I changed everything to checking either `window.context.codyEnabledOnInstance` or `window.context.codyEnabledForCurrentUser`. - Did the same for `window.context.codeSearchEnabledOnInstance` for symmetry. - Removed "helper" functions that just checked 1 or 2 boolean values on `window.context` related to this, in favor of accessing `window.context` directly. Globals aren't great, and we should use React context or something similar, but now that the JSContext has the right fields (i.e., enabled instead of licensed), it's simpler and there is no need for helper functions. - Removed prop drilling of the `licenseFeatures` that was unnecessary since these values are available in globals and were being set from globals at some arbitrary point in the React component hierarchy anyway. - Updated the GlobalNavbar test snapshots. ## Test plan Run in 3 modes: (1) dotcom mode, (2) `"cody.enabled": false` in site config, (3) normal `sg start`. ## Changelog - When Cody is disabled in site config (with `"cody.enabled": false`), all links and UI elements about Cody are hidden from all users. Previously, when Cody was disabled, users would see some links informing them about Cody. --- .../src/routes/search/+page.svelte | 2 +- .../src/routes/search/SearchHome.svelte | 10 +- .../cody-upsell/CodyUpsellBanner.svelte | 59 -- .../cody-upsell/MultiLineCompletion.svelte | 229 ----- client/web/BUILD.bazel | 18 +- client/web/dev/utils/create-js-context.ts | 3 +- client/web/src/Index.tsx | 15 +- client/web/src/LegacyRouteContext.tsx | 25 +- client/web/src/LegacySourcegraphWebApp.tsx | 26 +- client/web/src/cody/chat/CodyChatPage.tsx | 16 +- .../cody/chat/new-chat/NewCodyChatPage.tsx | 29 +- .../src/cody/chat/old-chat/CodyChatPage.tsx | 182 +--- client/web/src/cody/codyProRoutes.tsx | 6 +- client/web/src/cody/codyRoutes.tsx | 72 ++ .../web/src/cody/components/ChatUI/ChatUi.tsx | 28 +- .../CodyMarketingPage.module.scss | 198 ----- .../CodyMarketingPage.story.tsx | 57 -- .../CodyMarketingPage/CodyMarketingPage.tsx | 338 -------- .../components/CodyMarketingPage/index.ts | 1 - .../dashboard/CodyDashboardPage.module.scss | 2 +- .../src/cody/dashboard/CodyDashboardPage.tsx | 74 +- client/web/src/cody/isCodyEnabled.tsx | 12 - .../cody/management/CodyManagementPage.tsx | 3 +- .../subscription/CodySubscriptionPage.tsx | 11 +- client/web/src/cody/upsell/ChatBrandIcon.tsx | 27 - .../cody/upsell/CodyUpsellPage.module.scss | 340 -------- client/web/src/cody/upsell/CodyUpsellPage.tsx | 219 ----- .../src/cody/upsell/CompletionsBrandIcon.tsx | 42 - client/web/src/cody/upsell/ContextDiagram.tsx | 805 ------------------ client/web/src/cody/upsell/ContextExample.tsx | 359 -------- .../src/cody/upsell/MultilineCompletion.tsx | 222 ----- client/web/src/cody/useCodyChat.tsx | 14 +- client/web/src/cody/useCodyIgnore.tsx | 10 +- client/web/src/cody/util.ts | 7 + .../legacyLayoutRouteContext.mock.ts | 4 - .../site-admin/SiteAdminSidebar.story.tsx | 9 - client/web/src/global/Notices.tsx | 8 +- client/web/src/integration/jscontext.ts | 7 +- client/web/src/jscontext.ts | 24 +- client/web/src/namespaces/NamespaceArea.tsx | 8 +- client/web/src/namespaces/routes.tsx | 6 +- client/web/src/nav/GlobalNavbar.story.tsx | 47 +- client/web/src/nav/GlobalNavbar.test.tsx | 152 +++- client/web/src/nav/GlobalNavbar.tsx | 42 +- client/web/src/nav/NavBar/NavBar.tsx | 15 +- client/web/src/nav/NavBar/NavDropdown.tsx | 11 +- client/web/src/nav/UserNavItem.test.tsx | 8 +- .../__snapshots__/GlobalNavbar.test.tsx.snap | 439 ---------- .../NewGlobalNavigationBar.story.tsx | 4 + .../NewGlobalNavigationBar.test.tsx | 155 ++++ .../NewGlobalNavigationBar.tsx | 33 +- client/web/src/org/area/OrgArea.tsx | 25 +- client/web/src/org/area/OrgHeader.tsx | 9 +- client/web/src/org/area/navitems.ts | 3 +- .../web/src/org/settings/OrgSettingsArea.tsx | 9 +- client/web/src/org/settings/routes.tsx | 3 +- client/web/src/repo/RepoContainer.tsx | 18 +- client/web/src/repo/blob/BlobPage.tsx | 16 +- client/web/src/repo/blob/CodeMirrorBlob.tsx | 6 +- .../TryCodyWidget/TryCodyWidget.module.scss | 129 --- .../TryCodyWidget/TryCodyWidget.tsx | 242 ------ .../components/TryCodyWidget/WidgetIcons.tsx | 115 --- client/web/src/repo/tree/TreePage.test.tsx | 32 +- client/web/src/repo/tree/TreePage.tsx | 13 - client/web/src/routes.constants.ts | 2 + client/web/src/routes.tsx | 98 +-- client/web/src/site-admin/SiteAdminArea.tsx | 16 +- .../web/src/site-admin/SiteAdminSidebar.tsx | 5 - .../AnalyticsOverviewPage/DevTimeSaved.tsx | 9 +- client/web/src/site-admin/routes.tsx | 12 +- client/web/src/site-admin/sidebaritems.ts | 12 +- .../pages/SearchPage/CodyUpsell.module.scss | 51 -- .../src/storm/pages/SearchPage/CodyUpsell.tsx | 34 - .../pages/SearchPage/MultilineCompletion.tsx | 256 ------ .../pages/SearchPage/SearchPageContent.tsx | 4 +- client/web/src/user/area/UserArea.tsx | 14 +- client/web/src/user/area/UserAreaHeader.tsx | 5 - client/web/src/user/area/navitems.ts | 3 +- client/web/src/util/license.test.ts | 55 -- client/web/src/util/license.ts | 30 - .../internal/app/jscontext/jscontext.go | 50 +- cmd/frontend/internal/app/ui/handlers.go | 3 +- vitest.shared.ts | 4 +- 83 files changed, 760 insertions(+), 4956 deletions(-) delete mode 100644 client/web-sveltekit/src/routes/search/cody-upsell/CodyUpsellBanner.svelte delete mode 100644 client/web-sveltekit/src/routes/search/cody-upsell/MultiLineCompletion.svelte create mode 100644 client/web/src/cody/codyRoutes.tsx delete mode 100644 client/web/src/cody/components/CodyMarketingPage/CodyMarketingPage.module.scss delete mode 100644 client/web/src/cody/components/CodyMarketingPage/CodyMarketingPage.story.tsx delete mode 100644 client/web/src/cody/components/CodyMarketingPage/CodyMarketingPage.tsx delete mode 100644 client/web/src/cody/components/CodyMarketingPage/index.ts delete mode 100644 client/web/src/cody/isCodyEnabled.tsx delete mode 100644 client/web/src/cody/upsell/ChatBrandIcon.tsx delete mode 100644 client/web/src/cody/upsell/CodyUpsellPage.module.scss delete mode 100644 client/web/src/cody/upsell/CodyUpsellPage.tsx delete mode 100644 client/web/src/cody/upsell/CompletionsBrandIcon.tsx delete mode 100644 client/web/src/cody/upsell/ContextDiagram.tsx delete mode 100644 client/web/src/cody/upsell/ContextExample.tsx delete mode 100644 client/web/src/cody/upsell/MultilineCompletion.tsx delete mode 100644 client/web/src/nav/__snapshots__/GlobalNavbar.test.tsx.snap create mode 100644 client/web/src/nav/new-global-navigation/NewGlobalNavigationBar.test.tsx delete mode 100644 client/web/src/repo/components/TryCodyWidget/TryCodyWidget.module.scss delete mode 100644 client/web/src/repo/components/TryCodyWidget/TryCodyWidget.tsx delete mode 100644 client/web/src/repo/components/TryCodyWidget/WidgetIcons.tsx delete mode 100644 client/web/src/storm/pages/SearchPage/CodyUpsell.module.scss delete mode 100644 client/web/src/storm/pages/SearchPage/CodyUpsell.tsx delete mode 100644 client/web/src/storm/pages/SearchPage/MultilineCompletion.tsx delete mode 100644 client/web/src/util/license.test.ts delete mode 100644 client/web/src/util/license.ts diff --git a/client/web-sveltekit/src/routes/search/+page.svelte b/client/web-sveltekit/src/routes/search/+page.svelte index 1d5d2e38f5f..4c0b2827b46 100644 --- a/client/web-sveltekit/src/routes/search/+page.svelte +++ b/client/web-sveltekit/src/routes/search/+page.svelte @@ -41,5 +41,5 @@ selectedFilters={data.queryFilters} /> {:else} - + {/if} diff --git a/client/web-sveltekit/src/routes/search/SearchHome.svelte b/client/web-sveltekit/src/routes/search/SearchHome.svelte index 6ed3a1a309e..05b2734e8a9 100644 --- a/client/web-sveltekit/src/routes/search/SearchHome.svelte +++ b/client/web-sveltekit/src/routes/search/SearchHome.svelte @@ -1,7 +1,7 @@ - -
-
- -

Introducing Cody: your new AI coding assistant.

-

- Cody autocompletes single lines, or entire code blocks, in any programming language, keeping all of your - company’s codebase in mind. -

- Explore Cody -
-
-
- - diff --git a/client/web-sveltekit/src/routes/search/cody-upsell/MultiLineCompletion.svelte b/client/web-sveltekit/src/routes/search/cody-upsell/MultiLineCompletion.svelte deleted file mode 100644 index 8448500c5b1..00000000000 --- a/client/web-sveltekit/src/routes/search/cody-upsell/MultiLineCompletion.svelte +++ /dev/null @@ -1,229 +0,0 @@ - - - - Cody generating a multi-line completion - {#if $isLightTheme} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {:else} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/if} - diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index 750edf8498f..536b5d2258b 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -210,6 +210,7 @@ ts_project( "src/cody/chat/new-chat/components/skeleton/Skeleton.tsx", "src/cody/chat/old-chat/CodyChatPage.tsx", "src/cody/codyProRoutes.tsx", + "src/cody/codyRoutes.tsx", "src/cody/components/ChatUI/ChatUi.tsx", "src/cody/components/ChatUI/index.tsx", "src/cody/components/CodeMirrorEditor.ts", @@ -217,8 +218,6 @@ ts_project( "src/cody/components/CodyContainer.tsx", "src/cody/components/CodyIcon.tsx", "src/cody/components/CodyLogo.tsx", - "src/cody/components/CodyMarketingPage/CodyMarketingPage.tsx", - "src/cody/components/CodyMarketingPage/index.ts", "src/cody/components/CodyProBadgeDeck.tsx", "src/cody/components/FileContentEditor.ts", "src/cody/components/GettingStarted.tsx", @@ -237,7 +236,6 @@ ts_project( "src/cody/invites/InviteUsers.tsx", "src/cody/invites/useInviteParams.ts", "src/cody/invites/useInviteState.ts", - "src/cody/isCodyEnabled.tsx", "src/cody/management/CodyManagementPage.tsx", "src/cody/management/SubscriptionStats.tsx", "src/cody/management/UseCodyInEditorSection.tsx", @@ -279,13 +277,7 @@ ts_project( "src/cody/switch-account/CodySwitchAccountPage.tsx", "src/cody/team/CodyManageTeamPage.tsx", "src/cody/team/TeamMemberList.tsx", - "src/cody/upsell/ChatBrandIcon.tsx", - "src/cody/upsell/CodyUpsellPage.tsx", - "src/cody/upsell/CompletionsBrandIcon.tsx", - "src/cody/upsell/ContextDiagram.tsx", - "src/cody/upsell/ContextExample.tsx", "src/cody/upsell/IntelliJ.tsx", - "src/cody/upsell/MultilineCompletion.tsx", "src/cody/upsell/vs-code.tsx", "src/cody/useCodyChat.tsx", "src/cody/useCodyIgnore.tsx", @@ -1367,8 +1359,6 @@ ts_project( "src/repo/components/RepoHeaderActions/index.ts", "src/repo/components/RepoRevision/RepoRevision.tsx", "src/repo/components/RepoRevision/index.ts", - "src/repo/components/TryCodyWidget/TryCodyWidget.tsx", - "src/repo/components/TryCodyWidget/WidgetIcons.tsx", "src/repo/constants.ts", "src/repo/icon-utils.ts", "src/repo/linkifiy/Linkified.tsx", @@ -1632,11 +1622,9 @@ ts_project( "src/storm/pages/LayoutPage/LayoutPage.tsx", "src/storm/pages/SearchPage/AddCodeHostWidget.tsx", "src/storm/pages/SearchPage/CodeSearchSimpleSearch.tsx", - "src/storm/pages/SearchPage/CodyUpsell.tsx", "src/storm/pages/SearchPage/FindChangesSimpleSearch.tsx", "src/storm/pages/SearchPage/KeywordSearchCtaSection.tsx", "src/storm/pages/SearchPage/KeywordSearchStarsIcon.tsx", - "src/storm/pages/SearchPage/MultilineCompletion.tsx", "src/storm/pages/SearchPage/RepoSearchSimpleSearch.tsx", "src/storm/pages/SearchPage/SearchPage.loader.ts", "src/storm/pages/SearchPage/SearchPage.tsx", @@ -1741,7 +1729,6 @@ ts_project( "src/util/dom.ts", "src/util/getReactElements.ts", "src/util/index.tsx", - "src/util/license.ts", "src/util/permission.ts", "src/util/prettyBytesBigint.ts", "src/util/rbac.ts", @@ -1977,6 +1964,7 @@ ts_project( "src/nav/StatusMessagesNavItem.mocks.ts", "src/nav/StatusMessagesNavItem.test.tsx", "src/nav/UserNavItem.test.tsx", + "src/nav/new-global-navigation/NewGlobalNavigationBar.test.tsx", "src/notebooks/serialize/convertMarkdownToBlocks.test.ts", "src/notebooks/serialize/index.test.ts", "src/onboarding/OnboardingChecklist.mocks.ts", @@ -2029,7 +2017,6 @@ ts_project( "src/util/checkRequestAccessAllowed.test.ts", "src/util/codeStatsUtils.test.ts", "src/util/getReactElements.test.tsx", - "src/util/license.test.ts", "src/util/prettyBytesBigint.test.ts", "src/util/size.test.ts", "src/util/time.test.ts", @@ -2221,7 +2208,6 @@ ts_project( "src/auth/VsCodeSignUpPage.story.tsx", "src/batches/RepoBatchChangesButton.story.tsx", "src/codeintel/ReferencesPanel.story.tsx", - "src/cody/components/CodyMarketingPage/CodyMarketingPage.story.tsx", "src/communitySearchContexts/CommunitySearchContextPage.story.tsx", "src/components/Breadcrumbs.story.tsx", "src/components/Byline/CreatedByAndUpdatedByInfoByline.story.tsx", diff --git a/client/web/dev/utils/create-js-context.ts b/client/web/dev/utils/create-js-context.ts index 808df75c5a6..d364ee8576d 100644 --- a/client/web/dev/utils/create-js-context.ts +++ b/client/web/dev/utils/create-js-context.ts @@ -38,9 +38,10 @@ export const createJsContext = ({ sourcegraphBaseUrl }: { sourcegraphBaseUrl: st batchChangesDisableWebhooksWarning: false, batchChangesWebhookLogsEnabled: true, executorsEnabled: false, - codyEnabled: true, + codyEnabledOnInstance: true, codyEnabledForCurrentUser: true, codyRequiresVerifiedEmail: false, + codeSearchEnabledOnInstance: true, codeIntelAutoIndexingEnabled: false, codeIntelAutoIndexingAllowGlobalPolicies: false, codeIntelligenceEnabled: true, diff --git a/client/web/src/Index.tsx b/client/web/src/Index.tsx index 11fc8fe46fe..f3ad7f10376 100644 --- a/client/web/src/Index.tsx +++ b/client/web/src/Index.tsx @@ -3,13 +3,10 @@ import type { FC } from 'react' import { Navigate } from 'react-router-dom' import { PageRoutes } from './routes.constants' -import { isCodyOnlyLicense } from './util/license' -export const IndexPage: FC = () => { - let redirectRoute = PageRoutes.Search - if (isCodyOnlyLicense()) { - redirectRoute = PageRoutes.Cody - } - - return -} +export const IndexPage: FC = () => ( + +) diff --git a/client/web/src/LegacyRouteContext.tsx b/client/web/src/LegacyRouteContext.tsx index 9082f2d4914..92fa0cb14ef 100644 --- a/client/web/src/LegacyRouteContext.tsx +++ b/client/web/src/LegacyRouteContext.tsx @@ -1,19 +1,19 @@ -import { type FC, type PropsWithChildren, createContext, useContext, useCallback } from 'react' +import { createContext, useCallback, useContext, type FC, type PropsWithChildren } from 'react' import type { Observable } from 'rxjs' import { isMacPlatform } from '@sourcegraph/common' -import { type FetchFileParameters, fetchHighlightedFileLineRanges } from '@sourcegraph/shared/src/backend/file' +import { fetchHighlightedFileLineRanges, type FetchFileParameters } from '@sourcegraph/shared/src/backend/file' import type { PlatformContext } from '@sourcegraph/shared/src/platform/context' import { + createSearchContext, + deleteSearchContext, + fetchSearchContext, fetchSearchContextBySpec, fetchSearchContexts, - fetchSearchContext, getUserSearchContextNamespaces, - createSearchContext, - updateSearchContext, - deleteSearchContext, isSearchContextSpecAvailable, + updateSearchContext, type SearchContextProps, } from '@sourcegraph/shared/src/search' import { aggregateStreamingSearch } from '@sourcegraph/shared/src/search/stream' @@ -25,9 +25,8 @@ import { isBatchChangesExecutionEnabled } from './batches' import { useBreadcrumbs, type BreadcrumbSetters, type BreadcrumbsProps } from './components/Breadcrumbs' import { NotFoundPage } from './components/HeroPage' import type { SearchStreamingProps } from './search' -import type { StaticSourcegraphWebAppContext, DynamicSourcegraphWebAppContext } from './SourcegraphWebApp' +import type { DynamicSourcegraphWebAppContext, StaticSourcegraphWebAppContext } from './SourcegraphWebApp' import type { StaticAppConfig } from './staticAppConfig' -import { getLicenseFeatures } from './util/license' export interface StaticLegacyRouteContext extends LegacyRouteComputedContext, LegacyRouteStaticInjections {} @@ -88,12 +87,7 @@ export interface LegacyLayoutRouteContext extends StaticAppConfig, StaticSourcegraphWebAppContext, DynamicSourcegraphWebAppContext, - StaticLegacyRouteContext { - licenseFeatures: { - isCodeSearchEnabled: boolean - isCodyEnabled: boolean - } -} + StaticLegacyRouteContext {} interface LegacyRouteProps { render: (props: LegacyLayoutRouteContext) => JSX.Element @@ -168,7 +162,6 @@ export const LegacyRouteContextProvider: FC{children} @@ -179,7 +172,6 @@ export const LegacyRouteContext = createContext /** * DO NOT USE OUTSIDE OF STORM ROUTES! * A convenience hook to return the LegacyRouteContext. - * * @deprecated This can be used only in components migrated under Storm routes. * Please use Apollo instead to make GraphQL requests and `useSettings` to access settings. */ @@ -193,7 +185,6 @@ export const useLegacyContext_onlyInStormRoutes = (): LegacyLayoutRouteContext = /** * A convenience hook to return the platform context. - * * @deprecated This should not be used for new code anymore, please use Apollo instead to make * GraphQL requests and `useSettings` to access settings. */ diff --git a/client/web/src/LegacySourcegraphWebApp.tsx b/client/web/src/LegacySourcegraphWebApp.tsx index a1611be5746..030638f9bea 100644 --- a/client/web/src/LegacySourcegraphWebApp.tsx +++ b/client/web/src/LegacySourcegraphWebApp.tsx @@ -3,34 +3,34 @@ import 'focus-visible' import * as React from 'react' import { ApolloProvider } from '@apollo/client' -import { RouterProvider, createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom' -import { combineLatest, from, Subscription, fromEvent, type Observable } from 'rxjs' +import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider } from 'react-router-dom' +import { combineLatest, from, fromEvent, Subscription, type Observable } from 'rxjs' import { logger } from '@sourcegraph/common' -import { type GraphQLClient, HTTPStatusError } from '@sourcegraph/http-client' +import { HTTPStatusError, type GraphQLClient } from '@sourcegraph/http-client' import { SharedSpanName, TraceSpanProvider } from '@sourcegraph/observability-client' -import { type FetchFileParameters, fetchHighlightedFileLineRanges } from '@sourcegraph/shared/src/backend/file' +import { fetchHighlightedFileLineRanges, type FetchFileParameters } from '@sourcegraph/shared/src/backend/file' import type { PlatformContext } from '@sourcegraph/shared/src/platform/context' import { ShortcutProvider } from '@sourcegraph/shared/src/react-shortcuts' import { - getUserSearchContextNamespaces, - fetchSearchContexts, + createSearchContext, + deleteSearchContext, fetchSearchContext, fetchSearchContextBySpec, - createSearchContext, - updateSearchContext, - deleteSearchContext, + fetchSearchContexts, + getDefaultSearchContextSpec, + getUserSearchContextNamespaces, isSearchContextSpecAvailable, SearchQueryStateStoreProvider, - getDefaultSearchContextSpec, + updateSearchContext, } from '@sourcegraph/shared/src/search' import { FilterType } from '@sourcegraph/shared/src/search/query/filters' import { filterExists } from '@sourcegraph/shared/src/search/query/validate' import { aggregateStreamingSearch } from '@sourcegraph/shared/src/search/stream' import { EMPTY_SETTINGS_CASCADE, - type SettingsCascadeProps, SettingsProvider, + type SettingsCascadeProps, } from '@sourcegraph/shared/src/settings/settings' import { TemporarySettingsProvider } from '@sourcegraph/shared/src/settings/temporary/TemporarySettingsProvider' import { TemporarySettingsStorage } from '@sourcegraph/shared/src/settings/temporary/TemporarySettingsStorage' @@ -38,7 +38,7 @@ import { NoOpTelemetryRecorderProvider } from '@sourcegraph/shared/src/telemetry import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger' import { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard' -import { authenticatedUser as authenticatedUserSubject, type AuthenticatedUser, authenticatedUserValue } from './auth' +import { authenticatedUser as authenticatedUserSubject, authenticatedUserValue, type AuthenticatedUser } from './auth' import { getWebGraphQLClient } from './backend/graphql' import { isBatchChangesExecutionEnabled } from './batches' import { ComponentsComposer } from './components/ComponentsComposer' @@ -55,7 +55,6 @@ import type { StaticAppConfig } from './staticAppConfig' import { setQueryStateFromSettings, useDeveloperSettings, useNavbarQueryState } from './stores' import { TelemetryRecorderProvider } from './telemetry' import { UserSessionStores } from './UserSessionStores' -import { getLicenseFeatures } from './util/license' import { siteSubjectNoAdmin, viewerSubjectFromSettings } from './util/settings' interface LegacySourcegraphWebAppState extends SettingsCascadeProps { @@ -237,7 +236,6 @@ export class LegacySourcegraphWebApp extends React.Component } /> diff --git a/client/web/src/cody/chat/CodyChatPage.tsx b/client/web/src/cody/chat/CodyChatPage.tsx index 3dc6067dfcb..62ce6470dd1 100644 --- a/client/web/src/cody/chat/CodyChatPage.tsx +++ b/client/web/src/cody/chat/CodyChatPage.tsx @@ -5,6 +5,7 @@ import { useExperimentalFeatures } from '@sourcegraph/shared/src/settings/settin import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' +import { withAuthenticatedUser } from '../../auth/withAuthenticatedUser' import type { SourcegraphContext } from '../../jscontext' import { CodyChatPage as OldCodyChatPage } from './old-chat/CodyChatPage' @@ -15,17 +16,20 @@ const LazyNewCodyChatPage = lazyComponent(() => import('./new-chat/NewCodyChatPa interface CodyChatPageProps extends TelemetryV2Props { isSourcegraphDotCom: boolean - authenticatedUser: AuthenticatedUser | null + authenticatedUser: AuthenticatedUser context: Pick } -export const CodyChatPage: FC = props => { - const { isSourcegraphDotCom, authenticatedUser, context, telemetryRecorder } = props - +const AuthenticatedCodyChatPage: FC = ({ + isSourcegraphDotCom, + authenticatedUser, + context, + telemetryRecorder, +}) => { // We have two different version of Cody Web, first was created as original // Cody Web chat, second version (NewCodyChatPage) is a port from VSCode // cody extension. - const newCodyWeb = useExperimentalFeatures(features => features.newCodyWeb) + const newCodyWeb = !useExperimentalFeatures(features => features.newCodyWeb) // Load new cody web only for authorized users, fallback on old cody web // for better non-logged-in user experience. @@ -40,3 +44,5 @@ export const CodyChatPage: FC = props => { /> ) } + +export const CodyChatPage = withAuthenticatedUser(AuthenticatedCodyChatPage) diff --git a/client/web/src/cody/chat/new-chat/NewCodyChatPage.tsx b/client/web/src/cody/chat/new-chat/NewCodyChatPage.tsx index c32f159ba16..728eaeb3bae 100644 --- a/client/web/src/cody/chat/new-chat/NewCodyChatPage.tsx +++ b/client/web/src/cody/chat/new-chat/NewCodyChatPage.tsx @@ -1,11 +1,14 @@ import type { FC } from 'react' -import { CodyWebChatProvider, ChatHistory } from 'cody-web-experimental' +import { ChatHistory, CodyWebChatProvider } from 'cody-web-experimental' +import { Navigate } from 'react-router-dom' -import { Badge, Link, PageHeader, Text } from '@sourcegraph/wildcard' +import { Badge, ButtonLink, PageHeader, Text } from '@sourcegraph/wildcard' import { Page } from '../../../components/Page' import { PageTitle } from '../../../components/PageTitle' +import { PageRoutes } from '../../../routes.constants' +import { CodyProRoutes } from '../../codyProRoutes' import { CodyColorIcon } from '../CodyPageIcon' import { ChatHistoryList } from './components/chat-history-list/ChatHistoryList' @@ -72,12 +75,25 @@ interface CodyPageHeaderProps { const CodyPageHeader: FC = props => { const { isSourcegraphDotCom, className } = props - const codyDashboardLink = isSourcegraphDotCom ? '/cody/manage' : '/cody' + const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard + + if (!window.context?.codyEnabledForCurrentUser) { + return + } return ( + + Editor extensions + + + Dashboard + + + } > @@ -86,11 +102,6 @@ const CodyPageHeader: FC = props => { Experimental - - - Manage - - diff --git a/client/web/src/cody/chat/old-chat/CodyChatPage.tsx b/client/web/src/cody/chat/old-chat/CodyChatPage.tsx index cde22698902..ba84b6d797c 100644 --- a/client/web/src/cody/chat/old-chat/CodyChatPage.tsx +++ b/client/web/src/cody/chat/old-chat/CodyChatPage.tsx @@ -1,27 +1,16 @@ import React, { useEffect, useState } from 'react' -import { - mdiChevronRight, - mdiClose, - mdiCogOutline, - mdiDelete, - mdiDotsVertical, - mdiFormatListBulleted, - mdiOpenInNew, - mdiPlus, -} from '@mdi/js' +import { mdiCogOutline, mdiDelete, mdiDotsVertical, mdiFormatListBulleted, mdiOpenInNew, mdiPlus } from '@mdi/js' import classNames from 'classnames' -import { useLocation, useNavigate } from 'react-router-dom' +import { Navigate, useLocation, useNavigate } from 'react-router-dom' import { CodyLogo } from '@sourcegraph/cody-ui' -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary' +import { type AuthenticatedUser } from '@sourcegraph/shared/src/auth' import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import { Badge, Button, ButtonLink, - H3, H4, Icon, Link, @@ -32,21 +21,18 @@ import { MenuLink, MenuList, PageHeader, - Text, Tooltip, } from '@sourcegraph/wildcard' -import { MarketingBlock } from '../../../components/MarketingBlock' import { Page } from '../../../components/Page' import { PageTitle } from '../../../components/PageTitle' import { useFeatureFlag } from '../../../featureFlags/useFeatureFlag' import type { SourcegraphContext } from '../../../jscontext' +import { PageRoutes } from '../../../routes.constants' import { EventName } from '../../../util/constants' import { CodyProRoutes } from '../../codyProRoutes' import { ChatUI } from '../../components/ChatUI' -import { CodyMarketingPage } from '../../components/CodyMarketingPage' import { HistoryList } from '../../components/HistoryList' -import { isCodyEnabled } from '../../isCodyEnabled' import { useCodyChat, type CodyChatStore } from '../../useCodyChat' import { CodyColorIcon } from '../CodyPageIcon' @@ -54,7 +40,7 @@ import styles from './CodyChatPage.module.scss' interface CodyChatPageProps extends TelemetryV2Props { isSourcegraphDotCom: boolean - authenticatedUser: AuthenticatedUser | null + authenticatedUser: AuthenticatedUser context: Pick } @@ -91,7 +77,6 @@ const onTranscriptHistoryLoad = ( export const CodyChatPage: React.FunctionComponent = ({ authenticatedUser, - context, isSourcegraphDotCom, telemetryRecorder, }) => { @@ -117,9 +102,6 @@ export const CodyChatPage: React.FunctionComponent = ({ deleteHistoryItem, logTranscriptEvent, } = codyChatStore - const [isCTADismissed = true, setIsCTADismissed] = useTemporarySetting('cody.chatPageCta.dismissed', false) - const onCTADismiss = (): void => setIsCTADismissed(true) - useEffect(() => { logTranscriptEvent(EventName.CODY_CHAT_PAGE_VIEWED, 'cody.chat', 'view') }, [logTranscriptEvent]) @@ -127,7 +109,7 @@ export const CodyChatPage: React.FunctionComponent = ({ const transcriptId = transcript?.id useEffect(() => { - if (!loaded || !transcriptId || !authenticatedUser || !isCodyEnabled()) { + if (!loaded || !transcriptId || !authenticatedUser || !window.context?.codyEnabledForCurrentUser) { return } const idFromUrl = transcriptIdFromUrl(pathname) @@ -149,85 +131,35 @@ export const CodyChatPage: React.FunctionComponent = ({ return null } - if (!authenticatedUser || !isCodyEnabled()) { - return ( - - ) + if (!window.context?.codyEnabledForCurrentUser) { + return } - const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : '/cody' + const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard return ( - {!isSourcegraphDotCom && !isCTADismissed && ( - -
- -
-

Cody is more powerful in your editor

- - Cody adds powerful AI assistant functionality like inline completions and assist, and - powerful recipes to help you understand codebases and generate and fix code more - accurately. - - - View editor extensions → - -
-
- -
- )} +
+ + Editor extensions + + + Dashboard +
} - description={ - <> - Cody answers code questions and writes code for you using your entire codebase and the code - graph. - {!isSourcegraphDotCom && isCTADismissed && ( - <> - {' '} - Get Cody in your editor. - - )} - - } className={styles.pageHeader} > -
- Cody Chat - - Experimental - - - - Manage - - -
+
Cody Chat
@@ -272,51 +204,6 @@ export const CodyChatPage: React.FunctionComponent = ({ deleteHistoryItem={deleteHistoryItem} /> - {isSourcegraphDotCom && !isCTADismissed && ( - -

Use Cody in your editor

- - Autocomplete, test generation, refactors, code Q&A, and more—with the context of - your code. - -
- - logTranscriptEvent( - EventName.CODY_CHAT_GET_EDITOR_EXTENSION, - 'cody.chat.getEditorExtensionCTA', - 'click' - ) - } - > - Get Cody in your editor - - -
- Try Cody VS Code Extension - -
- )}
@@ -383,38 +270,3 @@ export const CodyChatPage: React.FunctionComponent = ({ ) } - -const CodyCTAIcon: React.FunctionComponent<{ className?: string }> = ({ className }) => ( - - - - - - - -) diff --git a/client/web/src/cody/codyProRoutes.tsx b/client/web/src/cody/codyProRoutes.tsx index e390e2ead64..5d7f6005e5a 100644 --- a/client/web/src/cody/codyProRoutes.tsx +++ b/client/web/src/cody/codyProRoutes.tsx @@ -2,7 +2,7 @@ import type { RouteObject } from 'react-router-dom' import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' -import { type LegacyLayoutRouteContext, LegacyRoute } from '../LegacyRouteContext' +import { LegacyRoute, type LegacyLayoutRouteContext } from '../LegacyRouteContext' import { QueryClientProvider } from './management/api/react-query/QueryClientProvider' import { isEmbeddedCodyProUIEnabled } from './util' @@ -42,8 +42,8 @@ export const codyProRoutes: RouteObject[] = Object.values(CodyProRoutes).map(pat telemetryRecorder={props.platformContext.telemetryRecorder} /> )} - condition={({ isSourcegraphDotCom, licenseFeatures }) => - isSourcegraphDotCom && licenseFeatures.isCodyEnabled && isRouteEnabled(path) + condition={({ isSourcegraphDotCom }) => + isSourcegraphDotCom && window.context?.codyEnabledOnInstance && isRouteEnabled(path) } /> ), diff --git a/client/web/src/cody/codyRoutes.tsx b/client/web/src/cody/codyRoutes.tsx new file mode 100644 index 00000000000..6062f037991 --- /dev/null +++ b/client/web/src/cody/codyRoutes.tsx @@ -0,0 +1,72 @@ +import { Navigate, type RouteObject } from 'react-router-dom' + +import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' + +import { LegacyRoute } from '../LegacyRouteContext' +import { PageRoutes } from '../routes.constants' + +import { CodyIgnoreProvider } from './useCodyIgnore' + +const CodyChatPage = lazyComponent(() => import('./chat/CodyChatPage'), 'CodyChatPage') +const CodySwitchAccountPage = lazyComponent( + () => import('./switch-account/CodySwitchAccountPage'), + 'CodySwitchAccountPage' +) +const CodyDashboardPage = lazyComponent(() => import('./dashboard/CodyDashboardPage'), 'CodyDashboardPage') + +/** + * Use {@link codyProRoutes} for Cody PLG routes. + */ +export const codyRoutes: RouteObject[] = [ + { + path: PageRoutes.CodyRedirectToMarketingOrDashboard, + element: ( + ( + + )} + condition={() => window.context?.codyEnabledOnInstance} + /> + ), + }, + { + path: PageRoutes.CodySwitchAccount, + element: ( + ( + + )} + condition={() => window.context?.codyEnabledOnInstance} + /> + ), + }, + { + path: `${PageRoutes.CodyChat}/*`, + element: ( + ( + + + + )} + condition={() => window.context?.codyEnabledOnInstance} + /> + ), + }, + { + path: PageRoutes.CodyDashboard, + element: ( + } + condition={() => window.context?.codyEnabledOnInstance} + /> + ), + }, +] diff --git a/client/web/src/cody/components/ChatUI/ChatUi.tsx b/client/web/src/cody/components/ChatUI/ChatUi.tsx index 8b465b9e06a..7d5832bdcc5 100644 --- a/client/web/src/cody/components/ChatUI/ChatUi.tsx +++ b/client/web/src/cody/components/ChatUI/ChatUi.tsx @@ -1,14 +1,14 @@ -import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { - mdiClose, - mdiSend, mdiArrowDown, - mdiPencil, - mdiThumbUp, - mdiThumbDown, mdiCheck, + mdiClose, + mdiPencil, + mdiSend, mdiStopCircleOutline, + mdiThumbDown, + mdiThumbUp, } from '@mdi/js' import classNames from 'classnames' import { useLocation } from 'react-router-dom' @@ -25,12 +25,12 @@ import { import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger' -import { Button, Icon, TextArea, Link, Tooltip, Alert, Text, H2 } from '@sourcegraph/wildcard' +import { Alert, Button, H2, Icon, Link, Text, TextArea, Tooltip } from '@sourcegraph/wildcard' import { CodyPageIcon } from '../../chat/CodyPageIcon' -import { isCodyEnabled, isEmailVerificationNeededForCody, isSignInRequiredForCody } from '../../isCodyEnabled' import { useCodySidebar } from '../../sidebar/Provider' import type { CodyChatStore } from '../../useCodyChat' +import { currentUserRequiresEmailVerificationForCody } from '../../util' import { GettingStarted } from '../GettingStarted' import { ScopeSelector } from '../ScopeSelector' import type { ScopeSelectorProps } from '../ScopeSelector/ScopeSelector' @@ -173,7 +173,7 @@ export const ChatUI: React.FC = ({ transcriptActionClassName={styles.transcriptAction} FeedbackButtonsContainer={FeedbackButtons} feedbackButtonsOnSubmit={onFeedbackSubmit} - needsEmailVerification={isEmailVerificationNeededForCody()} + needsEmailVerification={currentUserRequiresEmailVerificationForCody()} needsEmailVerificationNotice={NeedsEmailVerificationNotice} codyNotEnabledNotice={CodyNotEnabledNotice} contextStatusComponent={ScopeSelector} @@ -182,7 +182,7 @@ export const ChatUI: React.FC = ({ gettingStartedComponentProps={gettingStartedComponentProps} abortMessageInProgressComponent={AbortMessageInProgress} onAbortMessageInProgress={abortMessageInProgress} - isCodyEnabled={isCodyEnabled()} + isCodyEnabled={window.context?.codyEnabledForCurrentUser} /> ) @@ -360,9 +360,9 @@ export const AutoResizableTextArea: React.FC = React return ( = React