mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 12:51:55 +00:00
remove Cody "upsells"—and all Cody links if Cody is disabled (#63430)
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.

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.
This commit is contained in:
parent
13b315c7c5
commit
0021f95d6c
@ -41,5 +41,5 @@
|
||||
selectedFilters={data.queryFilters}
|
||||
/>
|
||||
{:else}
|
||||
<SearchHome {queryState} codyHref={data.codyHref} showDotcomFooterLinks={data.showDotcomFooterLinks} />
|
||||
<SearchHome {queryState} codyHref={data.codyHref} showDotcomStuff={data.showDotcomFooterLinks} />
|
||||
{/if}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { setContext, onMount } from 'svelte'
|
||||
import { onMount, setContext } from 'svelte'
|
||||
|
||||
import { logoLight, logoDark } from '$lib/images'
|
||||
import { logoDark, logoLight } from '$lib/images'
|
||||
import SearchInput from '$lib/search/input/SearchInput.svelte'
|
||||
import type { QueryStateStore } from '$lib/search/state'
|
||||
import type { SearchPageContext } from '$lib/search/utils'
|
||||
@ -9,13 +9,12 @@
|
||||
import { isLightTheme } from '$lib/stores'
|
||||
import { TELEMETRY_RECORDER } from '$lib/telemetry'
|
||||
|
||||
import CodyUpsellBanner from './cody-upsell/CodyUpsellBanner.svelte'
|
||||
import DotcomFooterLinks from './DotcomFooterLinks.svelte'
|
||||
import SearchHomeNotifications from './SearchHomeNotifications.svelte'
|
||||
|
||||
export let queryState: QueryStateStore
|
||||
export let codyHref: string = '/cody'
|
||||
export let showDotcomFooterLinks: boolean = false
|
||||
export let showDotcomStuff: boolean = false
|
||||
|
||||
setContext<SearchPageContext>('search-context', {
|
||||
setQuery(newQuery) {
|
||||
@ -41,8 +40,7 @@
|
||||
<SearchInput {queryState} autoFocus onSubmit={handleSubmit} />
|
||||
<SearchHomeNotifications />
|
||||
</div>
|
||||
<CodyUpsellBanner {codyHref} />
|
||||
{#if showDotcomFooterLinks}
|
||||
{#if showDotcomStuff}
|
||||
<DotcomFooterLinks />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/Icon.svelte'
|
||||
|
||||
import MultiLineCompletion from './MultiLineCompletion.svelte'
|
||||
|
||||
export let codyHref = '/cody'
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="meta">
|
||||
<div class="logo"><Icon icon={ISgCody} aria-label="Cody logo" /></div>
|
||||
<h2 class="title">Introducing Cody: your new AI coding assistant.</h2>
|
||||
<p class="description">
|
||||
Cody autocompletes single lines, or entire code blocks, in any programming language, keeping all of your
|
||||
company’s codebase in mind.
|
||||
</p>
|
||||
<a href={codyHref}>Explore Cody</a>
|
||||
</div>
|
||||
<div class="image"><MultiLineCompletion /></div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
section {
|
||||
isolation: isolate;
|
||||
padding: 1.75rem 2.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: 1rem;
|
||||
|
||||
@media (--sm-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.meta {
|
||||
align-self: center;
|
||||
|
||||
.logo {
|
||||
--icon-color: initial;
|
||||
--icon-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
filter: drop-shadow(-7px -16px 32px #a112ff24);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 <Navigate replace={true} to={redirectRoute} />
|
||||
}
|
||||
export const IndexPage: FC = () => (
|
||||
<Navigate
|
||||
replace={true}
|
||||
to={window.context?.codeSearchEnabledOnInstance ? PageRoutes.Search : PageRoutes.CodyDashboard}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -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<PropsWithChildren<LegacyRouteContext
|
||||
...injections,
|
||||
...computedContextFields,
|
||||
...context,
|
||||
licenseFeatures: getLicenseFeatures(),
|
||||
} satisfies LegacyLayoutRouteContext
|
||||
|
||||
return <LegacyRouteContext.Provider value={legacyContext}>{children}</LegacyRouteContext.Provider>
|
||||
@ -179,7 +172,6 @@ export const LegacyRouteContext = createContext<LegacyLayoutRouteContext | null>
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@ -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<StaticAppConfig, Le
|
||||
updateSearchContext={updateSearchContext}
|
||||
deleteSearchContext={deleteSearchContext}
|
||||
streamSearch={aggregateStreamingSearch}
|
||||
licenseFeatures={getLicenseFeatures()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -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<SourcegraphContext, 'externalURL'>
|
||||
}
|
||||
|
||||
export const CodyChatPage: FC<CodyChatPageProps> = props => {
|
||||
const { isSourcegraphDotCom, authenticatedUser, context, telemetryRecorder } = props
|
||||
|
||||
const AuthenticatedCodyChatPage: FC<CodyChatPageProps> = ({
|
||||
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<CodyChatPageProps> = props => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const CodyChatPage = withAuthenticatedUser(AuthenticatedCodyChatPage)
|
||||
|
||||
@ -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<CodyPageHeaderProps> = props => {
|
||||
const { isSourcegraphDotCom, className } = props
|
||||
|
||||
const codyDashboardLink = isSourcegraphDotCom ? '/cody/manage' : '/cody'
|
||||
const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard
|
||||
|
||||
if (!window.context?.codyEnabledForCurrentUser) {
|
||||
return <Navigate to={PageRoutes.CodyDashboard} />
|
||||
}
|
||||
|
||||
return (
|
||||
<PageHeader
|
||||
className={className}
|
||||
description="Cody answers code questions and writes code for you using your entire codebase and the code graph."
|
||||
actions={
|
||||
<div className="d-flex flex-gap-1">
|
||||
<ButtonLink variant="link" to={codyDashboardLink}>
|
||||
Editor extensions
|
||||
</ButtonLink>
|
||||
<ButtonLink variant="secondary" to={codyDashboardLink}>
|
||||
Dashboard
|
||||
</ButtonLink>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<PageHeader.Heading as="h2" styleAs="h1">
|
||||
<PageHeader.Breadcrumb icon={CodyColorIcon}>
|
||||
@ -86,11 +102,6 @@ const CodyPageHeader: FC<CodyPageHeaderProps> = props => {
|
||||
<Badge variant="info" className="ml-2">
|
||||
Experimental
|
||||
</Badge>
|
||||
<Link to={codyDashboardLink}>
|
||||
<Text className="mb-0 ml-2" size="small">
|
||||
Manage
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
</PageHeader.Breadcrumb>
|
||||
</PageHeader.Heading>
|
||||
|
||||
@ -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<SourcegraphContext, 'externalURL'>
|
||||
}
|
||||
|
||||
@ -91,7 +77,6 @@ const onTranscriptHistoryLoad = (
|
||||
|
||||
export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
authenticatedUser,
|
||||
context,
|
||||
isSourcegraphDotCom,
|
||||
telemetryRecorder,
|
||||
}) => {
|
||||
@ -117,9 +102,6 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
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<CodyChatPageProps> = ({
|
||||
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<CodyChatPageProps> = ({
|
||||
return null
|
||||
}
|
||||
|
||||
if (!authenticatedUser || !isCodyEnabled()) {
|
||||
return (
|
||||
<CodyMarketingPage
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
authenticatedUser={authenticatedUser}
|
||||
context={context}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
/>
|
||||
)
|
||||
if (!window.context?.codyEnabledForCurrentUser) {
|
||||
return <Navigate to={PageRoutes.CodyDashboard} />
|
||||
}
|
||||
|
||||
const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : '/cody'
|
||||
const codyDashboardLink = isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard
|
||||
|
||||
return (
|
||||
<Page className={classNames('d-flex flex-column', styles.page)}>
|
||||
<PageTitle title="Cody chat" />
|
||||
{!isSourcegraphDotCom && !isCTADismissed && (
|
||||
<MarketingBlock
|
||||
wrapperClassName="mb-5"
|
||||
contentClassName={classNames(styles.ctaWrapper, styles.ctaContent)}
|
||||
>
|
||||
<div className="d-flex">
|
||||
<CodyCTAIcon className="flex-shrink-0" />
|
||||
<div className="ml-3">
|
||||
<H3>Cody is more powerful in your editor</H3>
|
||||
<Text>
|
||||
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.
|
||||
</Text>
|
||||
<ButtonLink variant="primary" to="/help/cody">
|
||||
View editor extensions →
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
<Icon
|
||||
svgPath={mdiClose}
|
||||
aria-label="Close Cody editor extensions CTA"
|
||||
className={classNames(styles.closeButton, 'position-absolute m-0')}
|
||||
onClick={onCTADismiss}
|
||||
/>
|
||||
</MarketingBlock>
|
||||
)}
|
||||
<PageHeader
|
||||
actions={
|
||||
<div className="d-flex">
|
||||
<div className="d-flex flex-gap-1">
|
||||
<ButtonLink variant="link" to={codyDashboardLink}>
|
||||
Editor extensions
|
||||
</ButtonLink>
|
||||
<ButtonLink variant="secondary" to={codyDashboardLink}>
|
||||
Dashboard
|
||||
</ButtonLink>
|
||||
<Button variant="primary" onClick={initializeNewChat}>
|
||||
<Icon aria-hidden={true} svgPath={mdiPlus} />
|
||||
New chat
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
description={
|
||||
<>
|
||||
Cody answers code questions and writes code for you using your entire codebase and the code
|
||||
graph.
|
||||
{!isSourcegraphDotCom && isCTADismissed && (
|
||||
<>
|
||||
{' '}
|
||||
<Link to="/help/cody">Get Cody in your editor.</Link>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
className={styles.pageHeader}
|
||||
>
|
||||
<PageHeader.Heading as="h2" styleAs="h1">
|
||||
<PageHeader.Breadcrumb icon={CodyColorIcon}>
|
||||
<div className="d-inline-flex align-items-center">
|
||||
Cody Chat
|
||||
<Badge variant="info" className="ml-2">
|
||||
Experimental
|
||||
</Badge>
|
||||
<Link to={codyDashboardLink}>
|
||||
<Text className="mb-0 ml-2" size="small">
|
||||
Manage
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="d-inline-flex align-items-center">Cody Chat</div>
|
||||
</PageHeader.Breadcrumb>
|
||||
</PageHeader.Heading>
|
||||
</PageHeader>
|
||||
@ -272,51 +204,6 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
deleteHistoryItem={deleteHistoryItem}
|
||||
/>
|
||||
</div>
|
||||
{isSourcegraphDotCom && !isCTADismissed && (
|
||||
<MarketingBlock
|
||||
wrapperClassName="d-flex"
|
||||
contentClassName={classNames(
|
||||
'flex-grow-1 d-flex flex-column justify-content-between',
|
||||
styles.ctaWrapper
|
||||
)}
|
||||
>
|
||||
<H3 className="d-flex align-items-center mb-4">Use Cody in your editor</H3>
|
||||
<Text>
|
||||
Autocomplete, test generation, refactors, code Q&A, and more—with the context of
|
||||
your code.
|
||||
</Text>
|
||||
<div className="mb-2">
|
||||
<Link
|
||||
to={CodyProRoutes.Manage}
|
||||
className={classNames(
|
||||
'd-inline-flex align-items-center text-merged',
|
||||
styles.ctaLink
|
||||
)}
|
||||
onClick={() =>
|
||||
logTranscriptEvent(
|
||||
EventName.CODY_CHAT_GET_EDITOR_EXTENSION,
|
||||
'cody.chat.getEditorExtensionCTA',
|
||||
'click'
|
||||
)
|
||||
}
|
||||
>
|
||||
Get Cody in your editor
|
||||
<Icon svgPath={mdiChevronRight} aria-hidden={true} />
|
||||
</Link>
|
||||
</div>
|
||||
<img
|
||||
src="https://storage.googleapis.com/sourcegraph-assets/TryCodyVSCodeExtension.png"
|
||||
alt="Try Cody VS Code Extension"
|
||||
width={666}
|
||||
/>
|
||||
<Icon
|
||||
svgPath={mdiClose}
|
||||
aria-label="Close try Cody widget"
|
||||
className={classNames(styles.closeButton, 'position-absolute m-0')}
|
||||
onClick={onCTADismiss}
|
||||
/>
|
||||
</MarketingBlock>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classNames('col-md-9 h-100', styles.chatMainWrapper)}>
|
||||
@ -383,38 +270,3 @@ export const CodyChatPage: React.FunctionComponent<CodyChatPageProps> = ({
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
const CodyCTAIcon: React.FunctionComponent<{ className?: string }> = ({ className }) => (
|
||||
<svg
|
||||
width="146"
|
||||
height="112"
|
||||
viewBox="0 0 146 112"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<rect x="24" y="24" width="98" height="64" rx="6" fill="#E8D1FF" />
|
||||
<path
|
||||
d="M56.25 65.3333C56.25 65.687 56.3817 66.0261 56.6161 66.2761C56.8505 66.5262 57.1685 66.6667 57.5 66.6667H60V69.3333H56.875C56.1875 69.3333 55 68.7333 55 68C55 68.7333 53.8125 69.3333 53.125 69.3333H50V66.6667H52.5C52.8315 66.6667 53.1495 66.5262 53.3839 66.2761C53.6183 66.0261 53.75 65.687 53.75 65.3333V46.6667C53.75 46.313 53.6183 45.9739 53.3839 45.7239C53.1495 45.4738 52.8315 45.3333 52.5 45.3333H50V42.6667H53.125C53.8125 42.6667 55 43.2667 55 44C55 43.2667 56.1875 42.6667 56.875 42.6667H60V45.3333H57.5C57.1685 45.3333 56.8505 45.4738 56.6161 45.7239C56.3817 45.9739 56.25 46.313 56.25 46.6667V65.3333Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M88.9095 45C90.3781 45 91.5686 46.1789 91.5686 47.6331V52.314C91.5686 53.7682 90.3781 54.9471 88.9095 54.9471C87.4409 54.9471 86.2504 53.7682 86.2504 52.314V47.6331C86.2504 46.1789 87.4409 45 88.9095 45Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M72.068 51.1437C72.068 49.6895 73.2585 48.5106 74.7271 48.5106H79.4544C80.923 48.5106 82.1135 49.6895 82.1135 51.1437C82.1135 52.5978 80.923 53.7767 79.4544 53.7767H74.7271C73.2585 53.7767 72.068 52.5978 72.068 51.1437Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M95.2643 58.8091C96.2107 59.6994 96.2491 61.1808 95.35 62.1179L94.5134 62.99C87.9666 69.8138 76.9295 69.6438 70.6002 62.6216C69.731 61.6572 69.8159 60.1777 70.7898 59.317C71.7637 58.4563 73.2579 58.5403 74.1271 59.5047C78.6157 64.4848 86.4432 64.6053 91.0861 59.7659L91.9227 58.8939C92.8218 57.9568 94.3179 57.9188 95.2643 58.8091Z"
|
||||
fill="#A305E1"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
/>
|
||||
),
|
||||
|
||||
72
client/web/src/cody/codyRoutes.tsx
Normal file
72
client/web/src/cody/codyRoutes.tsx
Normal file
@ -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: (
|
||||
<LegacyRoute
|
||||
render={({ isSourcegraphDotCom }) => (
|
||||
<Navigate
|
||||
to={isSourcegraphDotCom ? 'https://sourcegraph.com/cody' : PageRoutes.CodyDashboard}
|
||||
replace={true}
|
||||
/>
|
||||
)}
|
||||
condition={() => window.context?.codyEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: PageRoutes.CodySwitchAccount,
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => (
|
||||
<CodySwitchAccountPage {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
)}
|
||||
condition={() => window.context?.codyEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: `${PageRoutes.CodyChat}/*`,
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => (
|
||||
<CodyIgnoreProvider isSourcegraphDotCom={props.isSourcegraphDotCom}>
|
||||
<CodyChatPage
|
||||
{...props}
|
||||
context={window.context}
|
||||
telemetryRecorder={props.platformContext.telemetryRecorder}
|
||||
/>
|
||||
</CodyIgnoreProvider>
|
||||
)}
|
||||
condition={() => window.context?.codyEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: PageRoutes.CodyDashboard,
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <CodyDashboardPage {...props} />}
|
||||
condition={() => window.context?.codyEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
@ -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<IChatUIProps> = ({
|
||||
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<IChatUIProps> = ({
|
||||
gettingStartedComponentProps={gettingStartedComponentProps}
|
||||
abortMessageInProgressComponent={AbortMessageInProgress}
|
||||
onAbortMessageInProgress={abortMessageInProgress}
|
||||
isCodyEnabled={isCodyEnabled()}
|
||||
isCodyEnabled={window.context?.codyEnabledForCurrentUser}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
@ -360,9 +360,9 @@ export const AutoResizableTextArea: React.FC<AutoResizableTextAreaProps> = React
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
isSignInRequiredForCody()
|
||||
!window.context.isAuthenticatedUser
|
||||
? 'Sign in to get access to Cody.'
|
||||
: isEmailVerificationNeededForCody()
|
||||
: currentUserRequiresEmailVerificationForCody()
|
||||
? 'Verify your email to use Cody.'
|
||||
: ''
|
||||
}
|
||||
@ -370,7 +370,7 @@ export const AutoResizableTextArea: React.FC<AutoResizableTextAreaProps> = React
|
||||
<TextArea
|
||||
ref={textAreaRef}
|
||||
className={className}
|
||||
value={isSignInRequiredForCody() ? 'Sign in to get access to use Cody' : value}
|
||||
value={!window.context.isAuthenticatedUser ? 'Sign in to get access to use Cody' : value}
|
||||
onChange={handleChange}
|
||||
rows={1}
|
||||
autoFocus={false}
|
||||
@ -417,7 +417,7 @@ const CodyNotEnabledNotice: React.FunctionComponent = React.memo(function CodyNo
|
||||
<div className="d-flex align-items-start">
|
||||
<CodyNotEnabledIcon className="flex-shrink-0" />
|
||||
<Text className="ml-2">
|
||||
{isSignInRequiredForCody() ? (
|
||||
{!window.context?.isAuthenticatedUser ? (
|
||||
<>
|
||||
<Link to={`/sign-in?returnTo=${location.pathname}`}>Sign in</Link> to get access to Cody.
|
||||
You can learn more about Cody{' '}
|
||||
|
||||
@ -1,198 +0,0 @@
|
||||
@import 'wildcard/src/global-styles/breakpoints';
|
||||
|
||||
:root {
|
||||
--platform-icon-color: #4d52f4;
|
||||
}
|
||||
|
||||
:global(.theme-light) {
|
||||
--marketing-block-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
:global(.theme-dark) {
|
||||
--marketing-block-shadow: 0 4px 16px -6px rgba(46, 34, 119, 0.1);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
:global(.theme-dark) & {
|
||||
color: var(--gray-05);
|
||||
|
||||
.page-header-breadcrumb {
|
||||
color: var(--gray-04);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-light) & {
|
||||
color: var(--gray-07);
|
||||
|
||||
.page-header-breadcrumb {
|
||||
color: var(--gray-08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-section {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 4rem;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cody-conversation {
|
||||
padding: 0.25rem 1rem;
|
||||
border: 2px solid var(--gray-04);
|
||||
border-radius: 0.25rem 0.25rem 0.25rem 0;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.cody-sign-up-panel {
|
||||
background-color: var(--color-bg-1);
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 0.5rem 1rem -6px rgba(46, 34, 119, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.cody-sign-up-panel-wrapper {
|
||||
box-shadow: var(--marketing-block-shadow);
|
||||
height: fit-content;
|
||||
max-width: 598px;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.26rem;
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
text-align: center;
|
||||
background-color: var(--white);
|
||||
color: var(--black);
|
||||
padding: 0.375rem 2.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
width: fit-content;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
margin-right: 0.125 !important;
|
||||
}
|
||||
|
||||
.terms-privacy-link {
|
||||
text-decoration: underline;
|
||||
|
||||
:global(.theme-dark) & {
|
||||
color: var(--gray-04);
|
||||
}
|
||||
|
||||
:global(.theme-light) & {
|
||||
color: var(--gray-08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.learn-more-section {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.starting-point-wrapper {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.learn-more-items-wrapper {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
padding-bottom: 4rem;
|
||||
|
||||
.learn-more-items {
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
border: 1px solid var(--gray-03);
|
||||
border-radius: 3px;
|
||||
|
||||
:global(.theme-dark) & {
|
||||
background-color: var(--gray-10);
|
||||
border: 1px solid var(--gray-09);
|
||||
}
|
||||
|
||||
:global(.theme-light) & {
|
||||
background-color: var(--gray-03);
|
||||
}
|
||||
.learn-more-items-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 2.125rem 0.625rem 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1.5rem;
|
||||
|
||||
:global(.theme-dark) & {
|
||||
border-top: 1px solid var(--gray-09);
|
||||
}
|
||||
|
||||
:global(.theme-light) & {
|
||||
border-top: 1px solid var(--gray-03);
|
||||
}
|
||||
|
||||
.footer-description {
|
||||
margin-bottom: 1.3125rem;
|
||||
font-size: 1.125rem;
|
||||
max-width: 852px;
|
||||
}
|
||||
|
||||
.footer-cta-link {
|
||||
color: var(--violet-09) !important;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cody-platform-card-wrapper {
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--color-bg-1);
|
||||
|
||||
:global(.theme-dark) & {
|
||||
border: 1px solid var(--gray-09);
|
||||
}
|
||||
|
||||
:global(.theme-light) & {
|
||||
border: 1px solid var(--gray-03);
|
||||
}
|
||||
|
||||
.cody-platform-card-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--platform-icon-color);
|
||||
}
|
||||
|
||||
.cody-platform-card-description {
|
||||
max-width: 326px;
|
||||
}
|
||||
|
||||
.cody-platform-card-image {
|
||||
max-width: 326px;
|
||||
|
||||
@media (--xs-breakpoint-down) {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import type { Meta, StoryFn } from '@storybook/react'
|
||||
|
||||
import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
|
||||
|
||||
import type { AuthenticatedUser } from '../../../auth'
|
||||
import { WebStory } from '../../../components/WebStory'
|
||||
import type { SourcegraphContext } from '../../../jscontext'
|
||||
|
||||
import { CodyMarketingPage } from './CodyMarketingPage'
|
||||
|
||||
const config: Meta = {
|
||||
title: 'web/src/cody/CodyMarketingPage',
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
const context: Pick<SourcegraphContext, 'externalURL'> = {
|
||||
externalURL: 'https://sourcegraph.test:3443',
|
||||
}
|
||||
|
||||
export const SourcegraphDotCom: StoryFn = () => (
|
||||
<WebStory>
|
||||
{() => (
|
||||
<CodyMarketingPage
|
||||
context={context}
|
||||
isSourcegraphDotCom={true}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
)
|
||||
export const Enterprise: StoryFn = () => (
|
||||
<WebStory>
|
||||
{() => (
|
||||
<CodyMarketingPage
|
||||
context={context}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={null}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
)
|
||||
|
||||
export const EnterpriseSiteAdmin: StoryFn = () => (
|
||||
<WebStory>
|
||||
{() => (
|
||||
<CodyMarketingPage
|
||||
context={context}
|
||||
isSourcegraphDotCom={false}
|
||||
authenticatedUser={{ siteAdmin: true } as AuthenticatedUser}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
/>
|
||||
)}
|
||||
</WebStory>
|
||||
)
|
||||
@ -1,338 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { mdiChevronRight, mdiCodeBracesBox, mdiGit } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger'
|
||||
import { Theme, useTheme } from '@sourcegraph/shared/src/theme'
|
||||
import { Badge, H1, H2, H3, H4, Icon, Link, PageHeader, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../../auth'
|
||||
import { ExternalsAuth } from '../../../auth/components/ExternalsAuth'
|
||||
import { MarketingBlock } from '../../../components/MarketingBlock'
|
||||
import { Page } from '../../../components/Page'
|
||||
import { PageTitle } from '../../../components/PageTitle'
|
||||
import type { SourcegraphContext } from '../../../jscontext'
|
||||
import { MeetCodySVG } from '../../../repo/components/TryCodyWidget/WidgetIcons'
|
||||
import { EventName } from '../../../util/constants'
|
||||
import { CodyColorIcon, CodyHelpIcon, CodyWorkIcon } from '../../chat/CodyPageIcon'
|
||||
|
||||
import styles from './CodyMarketingPage.module.scss'
|
||||
|
||||
interface CodyPlatformCardProps {
|
||||
icon: string | JSX.Element
|
||||
title: string | JSX.Element
|
||||
description: string | JSX.Element
|
||||
illustration: string
|
||||
}
|
||||
|
||||
const onSpeakToAnEngineer = (): void => EVENT_LOGGER.log(EventName.SPEAK_TO_AN_ENGINEER_CTA)
|
||||
|
||||
const IDEIcon: React.FunctionComponent<{}> = () => (
|
||||
<svg viewBox="-4 -4 31 31" fill="none" xmlns="http://www.w3.org/2000/svg" className={styles.codyPlatformCardIcon}>
|
||||
<rect x="0.811523" y="0.366669" width="25" height="25" rx="3" fill="#4D52F4" />
|
||||
<path
|
||||
d="M13.8115 20.1583C13.8115 20.4346 13.9169 20.6996 14.1044 20.8949C14.292 21.0903 14.5463 21.2 14.8115 21.2H16.8115V23.2833H14.3115C13.7615 23.2833 12.8115 22.8146 12.8115 22.2417C12.8115 22.8146 11.8615 23.2833 11.3115 23.2833H8.81152V21.2H10.8115C11.0767 21.2 11.3311 21.0903 11.5186 20.8949C11.7062 20.6996 11.8115 20.4346 11.8115 20.1583V5.57501C11.8115 5.29874 11.7062 5.03379 11.5186 4.83844C11.3311 4.64309 11.0767 4.53335 10.8115 4.53335H8.81152V2.45001H11.3115C11.8615 2.45001 12.8115 2.91876 12.8115 3.49168C12.8115 2.91876 13.7615 2.45001 14.3115 2.45001H16.8115V4.53335H14.8115C14.5463 4.53335 14.292 4.64309 14.1044 4.83844C13.9169 5.03379 13.8115 5.29874 13.8115 5.57501V20.1583Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const codyPlatformCardItems = (
|
||||
isSourcegraphDotCom: boolean
|
||||
): {
|
||||
title: string | JSX.Element
|
||||
description: string | JSX.Element
|
||||
icon: string | JSX.Element
|
||||
illustration: { dark: string; light: string }
|
||||
}[] => [
|
||||
{
|
||||
title: 'Knows your code',
|
||||
description:
|
||||
'Cody knows about your codebase and can use that knowledge to explain, generate, and improve your code.',
|
||||
icon: mdiCodeBracesBox,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-knows-your-code-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-knows-your-code-illustration-light.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Cody in your editor',
|
||||
description: (
|
||||
<>
|
||||
The extensions combine an LLM with the context of your code to help you generate and fix code more
|
||||
accurately. <Link to="/help/cody">View supported editors.</Link>
|
||||
</>
|
||||
),
|
||||
icon: <IDEIcon />,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-vs-code-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-vs-code-illustration-light.png',
|
||||
},
|
||||
},
|
||||
...(isSourcegraphDotCom
|
||||
? [
|
||||
{
|
||||
title: (
|
||||
<>
|
||||
Try it on sourcegraph.com{' '}
|
||||
<Badge variant="info" className="d-inline">
|
||||
Experimental
|
||||
</Badge>
|
||||
</>
|
||||
),
|
||||
description:
|
||||
'Cody explains, generates, convert code, and more within the context of public repositories.',
|
||||
icon: mdiGit,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-light.png',
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Recipes accelerate your flow',
|
||||
description:
|
||||
'Cody explains, generates, convert code, and more within the context of your repositories.',
|
||||
icon: mdiGit,
|
||||
illustration: {
|
||||
dark: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-dark.png',
|
||||
light: 'https://storage.googleapis.com/sourcegraph-assets/app-images/cody-com-illustration-light.png',
|
||||
},
|
||||
},
|
||||
]),
|
||||
]
|
||||
|
||||
export interface CodyMarketingPageProps extends TelemetryV2Props {
|
||||
isSourcegraphDotCom: boolean
|
||||
context: Pick<SourcegraphContext, 'externalURL'>
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
}
|
||||
|
||||
export const CodyMarketingPage: React.FunctionComponent<CodyMarketingPageProps> = ({
|
||||
context,
|
||||
isSourcegraphDotCom,
|
||||
authenticatedUser,
|
||||
telemetryRecorder,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const isDarkTheme = theme === Theme.Dark
|
||||
|
||||
useEffect(() => telemetryRecorder.recordEvent('cody.marketing', 'view'), [telemetryRecorder])
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageTitle title="Cody" />
|
||||
<PageHeader
|
||||
description={
|
||||
<>
|
||||
Cody answers code questions and writes code for you by reading your entire codebase and the code
|
||||
graph.
|
||||
</>
|
||||
}
|
||||
className={classNames('mb-3', styles.pageHeader)}
|
||||
>
|
||||
<PageHeader.Heading as="h2" styleAs="h1">
|
||||
<PageHeader.Breadcrumb icon={CodyColorIcon}>
|
||||
<div className={classNames('d-inline-flex align-items-center', styles.pageHeaderBreadcrumb)}>
|
||||
Cody
|
||||
</div>
|
||||
</PageHeader.Breadcrumb>
|
||||
</PageHeader.Heading>
|
||||
</PageHeader>
|
||||
|
||||
{/* Page content */}
|
||||
<div className={styles.headerSection}>
|
||||
<div>
|
||||
{isSourcegraphDotCom && <H1>Meet Cody, your AI assistant</H1>}
|
||||
<div className="ml-3">
|
||||
<Text className={styles.codyConversation}>AI-powered chat for you code</Text>
|
||||
<Text className={styles.codyConversation}>Autocomplete</Text>
|
||||
<Text className={styles.codyConversation}>Find, fix and explain code</Text>
|
||||
<Text className={styles.codyConversation}>Create documentation</Text>
|
||||
<Text className={styles.codyConversation}>Generate unit tests</Text>
|
||||
<Text className={styles.codyConversation}>Build custom commands</Text>
|
||||
</div>
|
||||
<CodyHelpIcon />
|
||||
</div>
|
||||
|
||||
{isSourcegraphDotCom ? (
|
||||
<MarketingBlock
|
||||
contentClassName={styles.codySignUpPanel}
|
||||
wrapperClassName={styles.codySignUpPanelWrapper}
|
||||
>
|
||||
<H2>Sign up to get free access</H2>
|
||||
<Text className="mt-3">
|
||||
Cody answers technical questions and writes code directly in your IDE, using your code graph
|
||||
for context and accuracy. Sign up with:
|
||||
</Text>
|
||||
<div className={styles.buttonWrapper}>
|
||||
<ExternalsAuth
|
||||
page="cody-marketing-page"
|
||||
context={context}
|
||||
githubLabel="GitHub"
|
||||
gitlabLabel="GitLab"
|
||||
googleLabel="Google"
|
||||
withCenteredText={true}
|
||||
onClick={() => {}}
|
||||
ctaClassName={styles.authButton}
|
||||
iconClassName={styles.buttonIcon}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
telemetryService={EVENT_LOGGER}
|
||||
/>
|
||||
</div>
|
||||
<Text className="mt-3 mb-0">
|
||||
By registering, you agree to our{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://sourcegraph.com/terms"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
className={styles.termsPrivacyLink}
|
||||
to="https://sourcegraph.com/privacy"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
) : (
|
||||
<MarketingBlock
|
||||
contentClassName={styles.codySignUpPanel}
|
||||
wrapperClassName={styles.codySignUpPanelWrapper}
|
||||
>
|
||||
<H2>Meet Cody, your AI assistant</H2>
|
||||
<Text className="mt-3">
|
||||
Cody is an AI assistant that leverages the code graph to know more about your code. Use it
|
||||
to:
|
||||
</Text>
|
||||
<ul>
|
||||
<li>Onboard to new codebases</li>
|
||||
<li>Evaluate and fix code</li>
|
||||
<li>Write code faster</li>
|
||||
</ul>
|
||||
<Text className="mb-0">
|
||||
<Link to="https://sourcegraph.com/cody">Learn more about Cody →</Link>
|
||||
{authenticatedUser?.siteAdmin && (
|
||||
<>
|
||||
{' '}
|
||||
or <Link to="/help/cody/explanations/enabling_cody_enterprise">enable it now</Link>.
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
</MarketingBlock>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.learnMoreSection}>
|
||||
<H2>Enhancing productivity with Cody</H2>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'd-flex flex-row flex-wrap mt-3 justify-content-center',
|
||||
styles.startingPointWrapper
|
||||
)}
|
||||
>
|
||||
{codyPlatformCardItems(isSourcegraphDotCom).map((item, index) => (
|
||||
<CodyPlatformCard
|
||||
key={index}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
illustration={isDarkTheme ? item.illustration.dark : item.illustration.light}
|
||||
icon={item.icon}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.learnMoreItemsWrapper}>
|
||||
{isSourcegraphDotCom ? (
|
||||
<div className={styles.learnMoreItems}>
|
||||
<H4 className={styles.learnMoreItemsTitle}>Overview</H4>
|
||||
<Text className="mb-0">
|
||||
Visit the{' '}
|
||||
<Link to="https://sourcegraph.com/cody" target="_blank" rel="noopener">
|
||||
product page
|
||||
</Link>{' '}
|
||||
and see what devs are building with Cody.
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex align-items-center">
|
||||
<div>
|
||||
<MeetCodySVG />
|
||||
</div>
|
||||
<Text className="ml-3">
|
||||
<Link to="https://sourcegraph.com/cody">Learn about Cody</Link>, Sourcegraph's AI coding
|
||||
assistant.
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.learnMoreItems}>
|
||||
<H4 className={styles.learnMoreItemsTitle}>Documentation</H4>
|
||||
<Text className="mb-0">
|
||||
Learn about Cody’s use cases, commands, and FAQs on the{' '}
|
||||
<Link to="/help/cody" target="_blank" rel="noopener">
|
||||
documentation page
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isSourcegraphDotCom && (
|
||||
<div className={styles.footer}>
|
||||
<CodyWorkIcon />
|
||||
<div>
|
||||
<H1 className="mb-2">Get Cody for work</H1>
|
||||
<Text className={styles.footerDescription}>
|
||||
Cody for Sourcegraph Enterprise utilizes Sourcegraph's code graph to deliver context-aware
|
||||
answers based on your private codebase, enabling enhanced code comprehension and
|
||||
productivity.
|
||||
</Text>
|
||||
<div className="mb-2">
|
||||
<Link
|
||||
to="https://sourcegraph.com/demo"
|
||||
className={classNames('d-inline-flex align-items-center', styles.footerCtaLink)}
|
||||
onClick={onSpeakToAnEngineer}
|
||||
>
|
||||
Speak to an engineer
|
||||
<Icon svgPath={mdiChevronRight} aria-hidden={true} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
const CodyPlatformCard: React.FunctionComponent<CodyPlatformCardProps> = ({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
illustration,
|
||||
}) => (
|
||||
<div className={styles.codyPlatformCardWrapper}>
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
{typeof icon === 'string' ? (
|
||||
<Icon svgPath={icon} aria-hidden={true} className={styles.codyPlatformCardIcon} />
|
||||
) : (
|
||||
<>{icon}</>
|
||||
)}
|
||||
<H3 className="ml-2 mb-0">{title}</H3>
|
||||
</div>
|
||||
<Text className={classNames('mt-2', styles.codyPlatformCardDescription)}>{description}</Text>
|
||||
<img src={illustration} alt="Cody platform card" className={styles.codyPlatformCardImage} />
|
||||
</div>
|
||||
)
|
||||
@ -1 +0,0 @@
|
||||
export * from './CodyMarketingPage'
|
||||
@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
&-header {
|
||||
font-size: 2.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import {
|
||||
ButtonLink,
|
||||
H1,
|
||||
H2,
|
||||
Icon,
|
||||
Link,
|
||||
Menu,
|
||||
@ -34,7 +35,7 @@ const setupOptions: SetupOption[] = [
|
||||
{
|
||||
icon: <VSCodeIcon className={styles.linkSelectorIcon} />,
|
||||
maker: 'Microsoft',
|
||||
name: 'VSCode',
|
||||
name: 'VS Code',
|
||||
setupLink: 'https://sourcegraph.com/docs/cody/clients/install-vscode',
|
||||
},
|
||||
{
|
||||
@ -53,36 +54,63 @@ export const CodyDashboardPage: FC<CodyDashboardPageProps> = ({ telemetryRecorde
|
||||
}, [telemetryRecorder])
|
||||
|
||||
const codySetupLink = 'https://sourcegraph.com/docs/cody'
|
||||
return (
|
||||
return !window.context?.codyEnabledOnInstance ? (
|
||||
// This page should not be linked from anywhere if Cody is disabled on the instance, but add
|
||||
// a check here just in case to avoid confusing users if they find their way here.
|
||||
<section className={styles.dashboardContainer}>
|
||||
<section className={styles.dashboardHero}>
|
||||
<CodyColorIcon className={styles.dashboardCodyIcon} />
|
||||
<H1 className={styles.dashboardHeroHeader}>
|
||||
Get started with <span className={styles.codyGradient}>Cody</span>
|
||||
</H1>
|
||||
<H1 className={styles.dashboardHeroHeader}>Cody is not enabled</H1>
|
||||
<Text className={styles.dashboardHeroTagline}>
|
||||
Hey! 👋 Let’s get started with Cody, your AI coding assistant.
|
||||
Contact your Sourcegraph admin if this is unexpected.
|
||||
</Text>
|
||||
</section>
|
||||
|
||||
<section className={styles.dashboardOnboarding}>
|
||||
<section className={styles.dashboardOnboardingIde}>
|
||||
<Text className={styles.dashboardText}>Get Cody in your editor</Text>
|
||||
<LinkSelector options={setupOptions} />
|
||||
<Text className="text-muted">
|
||||
<Link to={codySetupLink} className={styles.dashboardOnboardingIdeInstallationLink}>
|
||||
Explore installation docs
|
||||
</section>
|
||||
) : (
|
||||
<section className={styles.dashboardContainer}>
|
||||
{window.context?.codyEnabledForCurrentUser ? (
|
||||
<>
|
||||
<section className={styles.dashboardHero}>
|
||||
<CodyColorIcon className={styles.dashboardCodyIcon} />
|
||||
<H1 className={styles.dashboardHeroHeader}>
|
||||
Get started with <span className={styles.codyGradient}>Cody</span>
|
||||
</H1>
|
||||
<Text className={styles.dashboardHeroTagline}>
|
||||
Hey! 👋 Let’s get started with Cody — your new AI coding assistant.
|
||||
</Text>
|
||||
</section>
|
||||
<section className={styles.dashboardOnboarding}>
|
||||
<section className={styles.dashboardOnboardingIde}>
|
||||
<Text className={styles.dashboardText}>Use Cody in your editor</Text>
|
||||
<LinkSelector options={setupOptions} />
|
||||
<Text className="text-muted">
|
||||
<Link to={codySetupLink} className={styles.dashboardOnboardingIdeInstallationLink}>
|
||||
Documentation
|
||||
</Link>
|
||||
</Text>
|
||||
</section>
|
||||
<section className={styles.dashboardOnboardingWeb}>
|
||||
<Text className={styles.dashboardText}>... or try it on the web</Text>
|
||||
<ButtonLink to="/cody/chat" outline={true} className={styles.dashboardOnboardingWebLink}>
|
||||
<CodyColorIcon className={styles.dashboardOnboardingCodyIcon} />
|
||||
<span>Cody Web</span>
|
||||
</ButtonLink>
|
||||
</section>
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<section className={styles.dashboardHero}>
|
||||
<CodyColorIcon className={styles.dashboardCodyIcon} />
|
||||
<H2 className={styles.dashboardHeroHeader}>
|
||||
Your user account doesn't have access to <span className={styles.codyGradient}>Cody</span>
|
||||
</H2>
|
||||
<Text className={styles.dashboardHeroTagline}>
|
||||
Ask your Sourcegraph admin to{' '}
|
||||
<Link to="/help/cody/clients/enable-cody-enterprise#enable-cody-only-for-some-users">
|
||||
enable Cody for you
|
||||
</Link>
|
||||
</Text>
|
||||
</section>
|
||||
<section className={styles.dashboardOnboardingWeb}>
|
||||
<Text className={styles.dashboardText}>... or try it on the web</Text>
|
||||
<ButtonLink to="/cody/chat" outline={true} className={styles.dashboardOnboardingWebLink}>
|
||||
<CodyColorIcon className={styles.dashboardOnboardingCodyIcon} />
|
||||
<span>Cody for web</span>
|
||||
</ButtonLink>
|
||||
</section>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
export const isEmailVerificationNeededForCody = (): boolean =>
|
||||
window.context?.codyRequiresVerifiedEmail && !window.context?.currentUser?.hasVerifiedEmail
|
||||
|
||||
export const isCodyEnabled = (): boolean => {
|
||||
if (!window.context?.codyEnabled || !window.context?.codyEnabledForCurrentUser) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export const isSignInRequiredForCody = (): boolean => !window.context.isAuthenticatedUser
|
||||
@ -23,7 +23,6 @@ import { CodyAlert } from '../components/CodyAlert'
|
||||
import { PageHeaderIcon } from '../components/PageHeaderIcon'
|
||||
import { AcceptInviteBanner } from '../invites/AcceptInviteBanner'
|
||||
import { InviteUsers } from '../invites/InviteUsers'
|
||||
import { isCodyEnabled } from '../isCodyEnabled'
|
||||
import { USER_CODY_PLAN, USER_CODY_USAGE } from '../subscription/queries'
|
||||
import { getManageSubscriptionPageURL, isEmbeddedCodyProUIEnabled } from '../util'
|
||||
|
||||
@ -91,7 +90,7 @@ export const CodyManagementPage: React.FunctionComponent<CodyManagementPageProps
|
||||
throw dataError || usageDateError
|
||||
}
|
||||
|
||||
if (!isCodyEnabled() || !subscription) {
|
||||
if (!window.context?.codyEnabledForCurrentUser || !subscription) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { type ReactElement, useEffect, useMemo } from 'react'
|
||||
import React, { useEffect, useMemo, type ReactElement } from 'react'
|
||||
|
||||
import { mdiArrowLeft, mdiInformationOutline, mdiTrendingUp, mdiCreditCardOutline } from '@mdi/js'
|
||||
import { mdiArrowLeft, mdiCreditCardOutline, mdiInformationOutline, mdiTrendingUp } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
@ -10,10 +10,10 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
ButtonLink,
|
||||
Link,
|
||||
H1,
|
||||
H2,
|
||||
Icon,
|
||||
Link,
|
||||
PageHeader,
|
||||
Text,
|
||||
Tooltip,
|
||||
@ -23,12 +23,11 @@ import {
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import { Page } from '../../components/Page'
|
||||
import { PageTitle } from '../../components/PageTitle'
|
||||
import { CodySubscriptionPlan } from '../../graphql-operations'
|
||||
import type { UserCodyPlanResult, UserCodyPlanVariables } from '../../graphql-operations'
|
||||
import { CodySubscriptionPlan } from '../../graphql-operations'
|
||||
import { CodyProRoutes } from '../codyProRoutes'
|
||||
import { ProIcon } from '../components/CodyIcon'
|
||||
import { PageHeaderIcon } from '../components/PageHeaderIcon'
|
||||
import { isCodyEnabled } from '../isCodyEnabled'
|
||||
import { getManageSubscriptionPageURL, isEmbeddedCodyProUIEnabled, manageSubscriptionRedirectURL } from '../util'
|
||||
|
||||
import { USER_CODY_PLAN } from './queries'
|
||||
@ -65,7 +64,7 @@ export const CodySubscriptionPage: React.FunctionComponent<CodySubscriptionPageP
|
||||
throw dataError
|
||||
}
|
||||
|
||||
if (!isCodyEnabled() || !data?.currentUser || !authenticatedUser) {
|
||||
if (!window.context?.codyEnabledForCurrentUser || !data?.currentUser || !authenticatedUser) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import type { FC } from 'react'
|
||||
|
||||
export const ChatBrandIcon: FC = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40">
|
||||
<path
|
||||
fill="url(#paint0_linear_571_94691)"
|
||||
fillRule="evenodd"
|
||||
d="M5.477 1.846h29.046a3.641 3.641 0 013.63 3.63v21.785a3.642 3.642 0 01-3.63 3.631H9.108l-7.262 7.262V5.477a3.641 3.641 0 013.63-3.63zm3.63 25.416h25.416V5.477H5.477v25.415l3.63-3.63z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_571_94691"
|
||||
x1="-3.385"
|
||||
x2="37.634"
|
||||
y1="7.385"
|
||||
y2="38.709"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.121" stopColor="#7048E8" />
|
||||
<stop offset="0.308" stopColor="#00CBEC" />
|
||||
<stop offset="0.642" stopColor="#A112FF" />
|
||||
<stop offset="0.92" stopColor="#FF5543" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
@ -1,340 +0,0 @@
|
||||
@import 'wildcard/src/global-styles/breakpoints';
|
||||
|
||||
.container {
|
||||
--container-margin: 6rem;
|
||||
|
||||
width: 100%;
|
||||
margin: var(--container-margin);
|
||||
height: fit-content;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
margin: 3rem;
|
||||
}
|
||||
|
||||
@media (--sm-breakpoint-down) {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
margin-right: calc(var(--container-margin) * -1);
|
||||
display: grid;
|
||||
grid-template-columns: 0.75fr 1fr;
|
||||
gap: 1.875rem;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&-logo {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
&-completion-image {
|
||||
justify-self: flex-end;
|
||||
filter: drop-shadow(-7px -16px 32px #a112ff24);
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
width: 100%;
|
||||
margin-right: -3rem;
|
||||
}
|
||||
|
||||
@media (--sm-breakpoint-down) {
|
||||
margin: -1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-headline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
&-cody {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
&-enterprise {
|
||||
font-size: 1.125rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
&-text {
|
||||
background: linear-gradient(90.64deg, var(--logo-blue) 3.11%, #c66fff 44.21%, #ff8578 83.64%);
|
||||
background-clip: text !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
font-size: 3rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
&-description {
|
||||
font-size: 1.3125rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--text-body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cody-availability {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ide {
|
||||
&-container {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
&-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
|
||||
@media (--sm-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.625rem;
|
||||
}
|
||||
|
||||
&-logo {
|
||||
width: 2rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
&-maker {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: 2.5rem;
|
||||
margin: 5rem 0;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 5rem;
|
||||
}
|
||||
|
||||
&-grid-one {
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2.5rem;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 0.25rem 2rem 0 #670bdb0a;
|
||||
|
||||
&-header {
|
||||
margin-block: 0.75rem;
|
||||
font-size: 1.625rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-description {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-grid-two {
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2.5rem;
|
||||
font-size: 1rem;
|
||||
height: fit-content;
|
||||
box-shadow: 0 0.25rem 2rem 0 #670bdb0a;
|
||||
|
||||
&-header {
|
||||
margin-block: 0.75rem;
|
||||
font-size: 1.625rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
&-list {
|
||||
list-style-type: disclosure-closed;
|
||||
padding-inline: 2rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: monospace;
|
||||
|
||||
li {
|
||||
padding-inline-start: 0.25rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-grid-three {
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2.5rem;
|
||||
font-size: 1rem;
|
||||
box-shadow: 0 0.25rem 2rem 0 #670bdb0a;
|
||||
|
||||
&-container {
|
||||
margin-top: 2rem;
|
||||
background: linear-gradient(
|
||||
90.64deg,
|
||||
var(--logo-blue) 3.11%,
|
||||
var(--logo-purple) 44.21%,
|
||||
var(--logo-orange) 83.64%
|
||||
);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.0625rem;
|
||||
}
|
||||
|
||||
&-text {
|
||||
background: linear-gradient(
|
||||
90.64deg,
|
||||
var(--logo-blue) 3.11%,
|
||||
var(--logo-purple) 44.21%,
|
||||
var(--logo-orange) 83.64%
|
||||
);
|
||||
background-clip: text !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.testimonial {
|
||||
margin: 0 5rem;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
&-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
text-align: center;
|
||||
margin: 3rem 0;
|
||||
font-weight: 500;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
&-container {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
height: fit-content;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-author {
|
||||
&-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&-username {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
background: var(--color-bg-2);
|
||||
width: 2.5rem;
|
||||
|
||||
&::after {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-meta {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 1.125rem;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.context {
|
||||
margin: 0 6rem;
|
||||
gap: 4rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&-header {
|
||||
margin-top: 1rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
&-description {
|
||||
margin-bottom: 1.75rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
&-diagram {
|
||||
@media (--md-breakpoint-down) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,219 +0,0 @@
|
||||
import type { FC, ComponentType } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { UserAvatar } from '@sourcegraph/shared/src/components/UserAvatar'
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
import { H1, H2, Text, ButtonLink, Icon } from '@sourcegraph/wildcard'
|
||||
|
||||
import { CodyLogo } from '../components/CodyLogo'
|
||||
|
||||
import { ChatBrandIcon } from './ChatBrandIcon'
|
||||
import { CompletionsBrandIcon } from './CompletionsBrandIcon'
|
||||
import { ContextDiagram } from './ContextDiagram'
|
||||
import { ContextExample } from './ContextExample'
|
||||
import { IntelliJIcon } from './IntelliJ'
|
||||
import { MultiLineCompletion } from './MultilineCompletion'
|
||||
import { VSCodeIcon } from './vs-code'
|
||||
|
||||
import styles from './CodyUpsellPage.module.scss'
|
||||
|
||||
interface CodyIDEDetails {
|
||||
maker?: string
|
||||
name: string
|
||||
icon: ComponentType<{ className?: string }>
|
||||
}
|
||||
|
||||
const availableIDEsForCody: CodyIDEDetails[] = [
|
||||
{
|
||||
maker: 'Microsoft',
|
||||
name: 'VSCode',
|
||||
icon: () => <VSCodeIcon className={styles.ideLogo} />,
|
||||
},
|
||||
{
|
||||
maker: 'JetBrains',
|
||||
name: 'IntelliJ',
|
||||
icon: () => <IntelliJIcon className={styles.ideLogo} />,
|
||||
},
|
||||
{
|
||||
name: 'Cody for Web',
|
||||
icon: () => <CodyLogo withColor={true} className={styles.ideLogo} />,
|
||||
},
|
||||
]
|
||||
|
||||
interface CodyTestimonial {
|
||||
author: string
|
||||
username: string
|
||||
comment: string
|
||||
}
|
||||
|
||||
const codyTestimonials: CodyTestimonial[] = [
|
||||
{
|
||||
author: 'Joe Previte',
|
||||
username: '@jsjoeio',
|
||||
comment:
|
||||
"I've started using Cody this week and dude, absolute gamechanger especially with me onboarding to Haskell at my new job literally just gave me the answer, explained it will and it just fixed my error.",
|
||||
},
|
||||
{
|
||||
author: 'Joshua Coetzer',
|
||||
username: 'VS Code marketplace review',
|
||||
comment:
|
||||
"Absolutely loved using Cody in VSCode for the last few months. It's been a game-changer for me. The way it summarises code blocks and fills in gaps in log statements, error messages, and code comments is incredibly smart.",
|
||||
},
|
||||
{
|
||||
author: 'Reza Shabani',
|
||||
username: '@truerezashabani',
|
||||
comment:
|
||||
'Recently I’ve been super impressed with Cody, and am using it constantly. It’s especially good at answering questions about large repos.',
|
||||
},
|
||||
]
|
||||
|
||||
export const CodyUpsellPage: FC = () => {
|
||||
const isLightTheme = useIsLightTheme()
|
||||
const contactSalesLink = 'https://sourcegraph.com/contact/request-info'
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<section className={styles.hero}>
|
||||
<div className={styles.heroHeadline}>
|
||||
<div className={styles.heroHeadlineHeader}>
|
||||
<CodyLogo withColor={true} className={styles.heroLogo} />
|
||||
<Text className={classNames('m-0', styles.heroHeadlineCody)}>Cody</Text>
|
||||
<Text className={classNames('m-0', styles.heroHeadlineEnterprise)}>for enterprise</Text>
|
||||
</div>
|
||||
<H1 className={styles.heroHeadlineText}>Code more, type less.</H1>
|
||||
<Text className={styles.heroHeadlineDescription}>
|
||||
Cody is a coding AI assistant that uses AI and a deep understanding of your organisation’s
|
||||
codebases to help you write and understand code faster.
|
||||
</Text>
|
||||
<ButtonLink to={contactSalesLink} variant="primary" className="py-2 px-5">
|
||||
Contact sales
|
||||
</ButtonLink>
|
||||
<div className={styles.ideContainer}>
|
||||
<Text className={classNames('text-muted', styles.codyAvailability)}>
|
||||
Cody is available for your favourite IDE...
|
||||
</Text>
|
||||
<div className={styles.ideList}>
|
||||
{availableIDEsForCody.map((ide, index) => (
|
||||
<div key={index} className={styles.ideDetail}>
|
||||
<Icon as={ide.icon} aria-hidden={true} />
|
||||
<div>
|
||||
<Text className={classNames('mb-0 text-muted', styles.ideMaker)}>
|
||||
{ide.maker}
|
||||
</Text>
|
||||
<Text className={classNames('mb-0', styles.ideName)}>{ide.name}</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MultiLineCompletion isLightTheme={isLightTheme} className={styles.heroCompletionImage} />
|
||||
</section>
|
||||
|
||||
<section className={styles.about}>
|
||||
<div>
|
||||
<div className={styles.aboutGridOne}>
|
||||
<CompletionsBrandIcon />
|
||||
<Text className={styles.aboutGridOneHeader}>Code faster with AI-assisted autocomplete</Text>
|
||||
<Text className={classNames('text-muted', styles.aboutGridOneDescription)}>
|
||||
Cody autocompletes single lines, or whole functions, in any programming language,
|
||||
configuration file, or documentation.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className={styles.aboutGridThreeContainer}>
|
||||
<div className={styles.aboutGridThree}>
|
||||
<Text className={styles.aboutGridThreeText}>
|
||||
Every day, Cody helps developers write >25,000 lines of code
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.aboutGridTwo}>
|
||||
<ChatBrandIcon />
|
||||
<Text className={styles.aboutGridTwoHeader}>AI-powered chat for your code</Text>
|
||||
<Text className={classNames('text-muted', styles.aboutGridTwoText)}>
|
||||
Cody chat helps unblock you when you’re jumping into new projects, trying to understand legacy
|
||||
code, or taking on tricky problems.
|
||||
</Text>
|
||||
<ul className={classNames('text-muted', styles.aboutGridTwoList)}>
|
||||
<li>How is this repository structured?</li>
|
||||
<li>What does this file do?</li>
|
||||
<li>Where is this component defined?</li>
|
||||
<li>Why isn't this code working?</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.testimonial}>
|
||||
<H1 className={classNames('text-muted', styles.testimonialHeader)}>What people say about Cody...</H1>
|
||||
<section className={styles.testimonialGrid}>
|
||||
{codyTestimonials.map((testimonial, index) => (
|
||||
<Testimonial key={index} testimonial={testimonial} />
|
||||
))}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section className={styles.context}>
|
||||
<div>
|
||||
<SearchIcon />
|
||||
<H2 className={styles.contextHeader}>Sourcegraph powered context</H2>
|
||||
<Text className={styles.contextDescription}>
|
||||
Sourcegraph’s code graph and analysis tools allows Cody to autocomplete, explain, and edit your
|
||||
code with additional context.
|
||||
</Text>
|
||||
<ContextExample isLightTheme={isLightTheme} />
|
||||
</div>
|
||||
<ContextDiagram isLightTheme={isLightTheme} className={styles.contextDiagram} />
|
||||
</section>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
interface TestimonialProps {
|
||||
testimonial: CodyTestimonial
|
||||
}
|
||||
|
||||
const Testimonial: FC<TestimonialProps> = ({ testimonial }) => (
|
||||
<section className={styles.testimonialContainer}>
|
||||
<div className={styles.testimonialMeta}>
|
||||
<UserAvatar
|
||||
className={styles.testimonialAuthorAvatar}
|
||||
capitalizeInitials={true}
|
||||
user={{ displayName: testimonial.author, username: testimonial.username, avatarURL: null }}
|
||||
/>
|
||||
<div className={styles.testimonialAuthorInfo}>
|
||||
<span className={styles.testimonialAuthorName}>{testimonial.author}</span>
|
||||
<span className={classNames('text-muted', styles.testimonialAuthorUsername)}>
|
||||
{testimonial.username}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Text className={styles.testimonialText}>{testimonial.comment}</Text>
|
||||
</section>
|
||||
)
|
||||
|
||||
const SearchIcon: FC = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="37" height="40" fill="none" viewBox="0 0 37 40">
|
||||
<path
|
||||
fill="url(#paint0_linear_571_94711)"
|
||||
fillRule="evenodd"
|
||||
d="M18.067 4.53c-7.441 0-13.5 6.029-13.5 13.5 0 7.47 6.059 13.5 13.5 13.5a2.274 2.274 0 012.284 2.264 2.274 2.274 0 01-2.284 2.265C8.074 36.059 0 27.972 0 18.029 0 8.087 8.074 0 18.067 0c9.994 0 18.068 8.087 18.068 18.03 0 4.964-2.013 9.463-5.268 12.724l5.393 5.386a2.251 2.251 0 01-.011 3.202 2.296 2.296 0 01-3.23-.01l-7.101-7.094a2.254 2.254 0 01.243-3.402 13.476 13.476 0 005.408-10.807c0-7.47-6.06-13.5-13.502-13.5z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_571_94711"
|
||||
x1="0.885"
|
||||
x2="30.949"
|
||||
y1="26.786"
|
||||
y2="27.094"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#FF5543" />
|
||||
<stop offset="1" stopColor="#A112FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
@ -1,42 +0,0 @@
|
||||
import type { FC } from 'react'
|
||||
|
||||
export const CompletionsBrandIcon: FC = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40">
|
||||
<path
|
||||
fill="url(#paint0_linear_646_2840)"
|
||||
d="M14.461 10.166h3.548c.741 0 1.342.6 1.342 1.342v17.138c0 .741-.6 1.342-1.342 1.342h-3.548v1.858h3.548a3.19 3.19 0 002.271-.946 3.19 3.19 0 002.272.946h3.082v-1.858h-3.082c-.741 0-1.342-.601-1.342-1.342V11.508c0-.741.6-1.342 1.342-1.342h3.082V8.308h-3.082a3.19 3.19 0 00-2.271.946 3.19 3.19 0 00-2.272-.946h-3.548v1.858z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#paint1_linear_646_2840)"
|
||||
d="M4.216 1.538h31.709l2.537 2.537v31.709l-2.537 2.678H4.216l-2.678-2.678V4.075l2.678-2.537zm0 34.246h31.709V4.075H4.216v31.709z"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_646_2840"
|
||||
x1="14.292"
|
||||
x2="32.609"
|
||||
y1="8.608"
|
||||
y2="20.721"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.125" stopColor="#7048E8" />
|
||||
<stop offset="0.308" stopColor="#00CBEC" />
|
||||
<stop offset="0.642" stopColor="#A112FF" />
|
||||
<stop offset="1" stopColor="#FF5543" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_646_2840"
|
||||
x1="0.98"
|
||||
x2="30.561"
|
||||
y1="2.009"
|
||||
y2="43.225"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.125" stopColor="#7048E8" />
|
||||
<stop offset="0.308" stopColor="#00CBEC" />
|
||||
<stop offset="0.642" stopColor="#A112FF" />
|
||||
<stop offset="1" stopColor="#FF5543" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
@ -1,805 +0,0 @@
|
||||
import type { FC } from 'react'
|
||||
|
||||
interface ContextDiagramProps {
|
||||
isLightTheme: boolean
|
||||
className: string
|
||||
}
|
||||
|
||||
export const ContextDiagram: FC<ContextDiagramProps> = ({ isLightTheme, className }) =>
|
||||
isLightTheme ? <ContextDiagramLight className={className} /> : <ContextDiagramDark className={className} />
|
||||
|
||||
const ContextDiagramDark: FC<Pick<ContextDiagramProps, 'className'>> = ({ className }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="492"
|
||||
height="329"
|
||||
fill="none"
|
||||
viewBox="0 0 492 329"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_571_94747)">
|
||||
<mask id="mask0_571_94747" width="492" height="281" x="0" y="24" maskUnits="userSpaceOnUse">
|
||||
<path fill="#fff" d="M491.946 24.5H.054v280h491.892v-280z" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_571_94747)">
|
||||
<g filter="url(#filter0_di_571_94747)">
|
||||
<path
|
||||
fill="url(#paint0_linear_571_94747)"
|
||||
stroke="url(#paint1_radial_571_94747)"
|
||||
strokeWidth="1"
|
||||
d="M185.123 251.469L64.656 171.303c-2.9-1.93-7.598-1.922-10.492.018L20.43 193.938c-2.894 1.94-2.888 5.078.012 7.007l120.467 80.167c2.901 1.93 7.598 1.922 10.492-.019l33.735-22.616c2.894-1.94 2.889-5.077-.012-7.008z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M137.496 230.016l-34.325-22.843c-1.791-1.192-4.692-1.187-6.478.011-1.787 1.199-1.784 3.135.007 4.327l34.326 22.843c1.791 1.191 4.691 1.186 6.478-.011 1.787-1.198 1.783-3.136-.008-4.327zM146.629 236.094l21.306 14.178c1.791 1.192 1.795 3.129.008 4.327s-4.687 1.203-6.478.011l-21.306-14.178c-1.791-1.191-1.794-3.129-.007-4.327 1.786-1.197 4.687-1.202 6.477-.011zM93.29 200.598l-1.977-1.316c-1.791-1.191-4.691-1.186-6.478.012-1.786 1.197-1.783 3.135.008 4.327l1.976 1.315c1.79 1.192 4.692 1.186 6.478-.012 1.787-1.197 1.784-3.134-.007-4.326z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
fillOpacity="0.8"
|
||||
d="M120.733 234.085l-18.693-12.439c-1.791-1.192-4.691-1.187-6.478.011-1.787 1.197-1.784 3.135.007 4.327l18.694 12.439c1.79 1.192 4.691 1.188 6.478-.011 1.787-1.198 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M137.299 259.346l-41.187-27.408c-1.791-1.192-4.691-1.187-6.478.011-1.787 1.197-1.784 3.134.007 4.327l41.187 27.408c1.79 1.191 4.691 1.186 6.478-.012 1.787-1.197 1.784-3.135-.007-4.326z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
d="M80.597 221.613l4.3 2.862c1.79 1.191 1.794 3.128.007 4.327-1.787 1.198-4.687 1.202-6.478.011l-4.3-2.862c-1.79-1.191-1.794-3.129-.007-4.327 1.787-1.197 4.687-1.202 6.478-.011z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
fillOpacity="0.4"
|
||||
d="M150.498 253.893l-20.368-13.554c-1.791-1.193-4.691-1.188-6.478.011-1.787 1.198-1.784 3.134.007 4.327l20.369 13.554c1.791 1.191 4.691 1.186 6.478-.011 1.786-1.198 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M71.008 215.233l-2.135-1.42c-1.79-1.193-4.69-1.188-6.478.011-1.786 1.198-1.783 3.134.008 4.327l2.135 1.42c1.79 1.192 4.69 1.187 6.478-.011 1.786-1.198 1.783-3.135-.008-4.327zM92.503 215.298l-12.971-8.631c-1.791-1.192-4.691-1.187-6.478.011s-1.784 3.135.007 4.327l12.97 8.631c1.792 1.193 4.693 1.188 6.48-.011 1.786-1.198 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#CE9CFF"
|
||||
d="M55.67 190.103l3.95 2.629-.008-5.272-3.943 2.643zm-.012-6.71l5.747 3.824.01 5.751-8.6 5.766c-.381.255-.896.399-1.435.399-.539.002-1.055-.14-1.436-.394l-8.62-5.736c-.381-.254-.595-.598-.596-.958 0-.359.213-.705.593-.959l11.467-7.688c.795-.533 2.073-.536 2.87-.005zm-5.002 11.99l1.433-.961-6.465-4.302-1.433.961 6.465 4.302zm5.022-.487l1.434-.962-8.62-5.736-1.434.96 8.62 5.738z"
|
||||
/>
|
||||
<path stroke="#fff" strokeLinecap="round" strokeOpacity="0.2" d="M50.253 212.974l32.554-21.968" />
|
||||
</g>
|
||||
<g filter="url(#filter1_di_571_94747)">
|
||||
<path
|
||||
fill="url(#paint2_linear_571_94747)"
|
||||
stroke="url(#paint3_radial_571_94747)"
|
||||
strokeWidth="1"
|
||||
d="M239.753 212.425l-120.468-80.166c-2.901-1.93-7.598-1.922-10.492.018l-33.735 22.617c-2.894 1.94-2.888 5.077.012 7.008l120.468 80.166c2.901 1.93 7.598 1.922 10.492-.018l33.734-22.617c2.894-1.94 2.889-5.078-.011-7.008z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
fillOpacity="0.8"
|
||||
d="M192.126 190.972L157.8 168.129c-1.791-1.191-4.692-1.186-6.479.012-1.786 1.197-1.783 3.134.008 4.326l34.326 22.843c1.791 1.192 4.691 1.186 6.478-.012 1.787-1.197 1.784-3.134-.007-4.326z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M201.259 197.051l21.306 14.178c1.791 1.191 1.794 3.129.007 4.326-1.786 1.198-4.687 1.203-6.478.012l-21.305-14.179c-1.791-1.192-1.795-3.128-.008-4.327 1.787-1.198 4.687-1.203 6.478-.01z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
fillOpacity="0.4"
|
||||
d="M147.918 161.554l-1.976-1.315c-1.791-1.192-4.691-1.187-6.477.011-1.787 1.198-1.784 3.135.007 4.327l1.976 1.315c1.791 1.192 4.691 1.187 6.478-.011 1.787-1.199 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M175.363 195.042l-18.694-12.44c-1.791-1.192-4.691-1.187-6.478.01-1.787 1.199-1.783 3.136.007 4.327l18.694 12.441c1.791 1.191 4.691 1.186 6.477-.011 1.788-1.198 1.785-3.136-.006-4.327zM191.928 220.302l-41.187-27.408c-1.791-1.192-4.691-1.187-6.478.01-1.786 1.199-1.783 3.136.008 4.327l41.186 27.408c1.791 1.193 4.692 1.188 6.479-.01 1.786-1.199 1.783-3.135-.008-4.327zM135.227 182.57l4.299 2.861c1.791 1.191 1.794 3.129.008 4.327-1.787 1.197-4.687 1.202-6.478.011l-4.3-2.862c-1.791-1.191-1.795-3.128-.008-4.326 1.787-1.199 4.688-1.204 6.479-.011zM205.128 214.85l-20.369-13.556c-1.791-1.191-4.691-1.186-6.478.012-1.787 1.197-1.783 3.135.008 4.327l20.368 13.554c1.791 1.192 4.691 1.187 6.478-.011s1.784-3.135-.007-4.326zM125.637 176.189l-2.135-1.421c-1.79-1.191-4.691-1.186-6.478.012-1.787 1.197-1.783 3.135.008 4.326l2.135 1.421c1.791 1.192 4.691 1.187 6.478-.011 1.786-1.198 1.783-3.135-.008-4.327zM147.131 176.255l-12.97-8.632c-1.791-1.192-4.691-1.187-6.478.012-1.787 1.197-1.784 3.134.007 4.326l12.971 8.632c1.791 1.191 4.691 1.186 6.478-.011 1.787-1.198 1.783-3.136-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#CE9CFF"
|
||||
d="M110.298 151.059l3.952 2.629-.01-5.272-3.942 2.643zm-.011-6.71l5.747 3.824.01 5.752-8.601 5.765c-.38.255-.896.399-1.434.4-.539.001-1.055-.141-1.436-.395l-8.62-5.736c-.381-.254-.596-.598-.596-.958 0-.359.213-.704.593-.959l11.467-7.688c.795-.533 2.073-.535 2.87-.005zm-5.002 11.991l1.433-.961-6.465-4.303-1.433.961 6.465 4.303zm5.022-.488l1.433-.961-8.62-5.736-1.434.96 8.621 5.737z"
|
||||
/>
|
||||
<path stroke="#fff" strokeLinecap="round" strokeOpacity="0.2" d="M104.883 173.93l32.554-21.968" />
|
||||
</g>
|
||||
<g filter="url(#filter2_dd_571_94747)">
|
||||
<path
|
||||
fill="url(#paint4_linear_571_94747)"
|
||||
stroke="url(#paint5_radial_571_94747)"
|
||||
strokeWidth="1.088"
|
||||
d="M306.816 138.911l-70.797-47.112c-1.705-1.135-5.985-.111-9.559 2.285l-41.675 27.94c-3.575 2.396-5.091 5.259-3.387 6.394l70.798 47.113c1.705 1.134 5.984.111 9.56-2.286l41.674-27.94c3.575-2.396 5.092-5.259 3.386-6.394z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M271.145 121.311L234.743 97.1c-1.335-.888-3.497-.884-4.828.008-1.332.893-1.33 2.336.005 3.224l36.401 24.211c1.335.888 3.497.884 4.829-.009 1.332-.892 1.33-2.335-.005-3.223zM256.675 123.029l-13.935-9.268c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l13.935 9.268c1.335.888 3.497.885 4.829-.008 1.332-.892 1.33-2.336-.005-3.224zM259.537 135.54l-24.741-16.455c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.224l24.742 16.455c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224zM286.251 142.7l-22.467-14.942c-1.335-.888-3.497-.884-4.829.008-1.331.893-1.329 2.336.006 3.224l22.466 14.942c1.335.888 3.497.885 4.829-.008 1.332-.892 1.33-2.336-.005-3.224zM227.686 114.356l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.33-2.336-.005-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#005482"
|
||||
d="M206.435 130.13l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M223.863 142.338l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224zM235.63 109.033l-9.669-6.431c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.224l9.67 6.431c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.335-.006-3.223z"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_571_94747)">
|
||||
<path
|
||||
fill="url(#paint6_linear_571_94747)"
|
||||
stroke="url(#paint7_radial_571_94747)"
|
||||
strokeWidth="1.088"
|
||||
d="M221.172 79.291l-76.376-50.825c-1.839-1.223-4.961-1.122-6.972.227l-23.449 15.72c-2.012 1.35-2.152 3.435-.313 4.659l76.376 50.825c1.839 1.223 4.96 1.122 6.972-.227l23.45-15.72c2.011-1.35 2.151-3.435.312-4.659z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M187.645 61.991l-36.401-24.21c-1.335-.889-3.497-.885-4.829.007-1.332.893-1.33 2.336.005 3.224l36.402 24.211c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#1E8DCA"
|
||||
d="M173.175 63.71l-22.524-14.98c-1.335-.889-3.497-.885-4.829.008-1.332.892-1.329 2.335.006 3.223l22.523 14.98c1.335.889 3.497.885 4.829-.007 1.332-.893 1.33-2.336-.005-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M176.588 76.603l-24.741-16.456c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l24.742 16.456c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#005482"
|
||||
d="M202.75 83.381L180.284 68.44c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.006 3.224l22.466 14.942c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M136.559 49.964l-2.043-1.359c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.329 2.335.006 3.223l2.042 1.359c1.335.888 3.497.884 4.829-.008 1.332-.893 1.33-2.336-.005-3.224zM145.003 55.709l-2.042-1.359c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l2.043 1.359c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.336-.006-3.223z"
|
||||
/>
|
||||
<path
|
||||
fill="#005482"
|
||||
d="M143.793 44.169l-1.333-.887c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l1.333.886c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter4_dd_571_94747)">
|
||||
<path
|
||||
fill="url(#paint8_linear_571_94747)"
|
||||
stroke="url(#paint9_radial_571_94747)"
|
||||
strokeWidth="1.088"
|
||||
d="M178.276 104.952l-74.452-49.545c-1.793-1.193-4.877-1.067-6.888.282L73.486 71.41c-2.011 1.349-2.19 3.409-.396 4.602l74.453 49.545c1.791 1.193 4.876 1.067 6.888-.282l23.449-15.721c2.011-1.349 2.189-3.409.396-4.602z"
|
||||
/>
|
||||
<path
|
||||
fill="#005482"
|
||||
d="M142.102 86.838l-25.588-17.02c-1.335-.887-3.497-.883-4.829.01-1.332.892-1.33 2.335.005 3.223l25.588 17.019c1.335.888 3.497.884 4.829-.009 1.332-.892 1.33-2.336-.005-3.223z"
|
||||
/>
|
||||
<path
|
||||
fill="#1E8DCA"
|
||||
d="M148.91 91.366l15.883 10.564c1.335.888 1.337 2.331.005 3.223-1.332.893-3.494.897-4.829.009l-15.882-10.564c-1.335-.888-1.338-2.331-.006-3.224 1.332-.892 3.494-.896 4.829-.008z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M109.148 64.92l-1.473-.98c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.224l1.474.98c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.335-.006-3.224zM129.606 89.87L115.671 80.6c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.329 2.335.006 3.223l13.935 9.269c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.336-.006-3.224zM141.954 108.69l-30.702-20.42c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.223l30.703 20.421c1.335.888 3.497.884 4.829-.008 1.331-.893 1.329-2.336-.006-3.224zM99.687 80.577l3.205 2.132c1.335.888 1.338 2.331.006 3.224-1.332.892-3.494.896-4.829.008l-3.205-2.132c-1.335-.888-1.338-2.33-.006-3.223 1.332-.893 3.494-.897 4.83-.009zM151.793 104.627L136.61 94.528c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.33 2.335.005 3.223l15.184 10.099c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224zM92.54 75.823l-1.592-1.058c-1.335-.888-3.497-.884-4.83.008-1.331.893-1.328 2.336.007 3.224l1.59 1.058c1.336.888 3.498.885 4.83-.008 1.332-.892 1.33-2.336-.006-3.224zM108.561 75.873l-9.669-6.431c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.006 3.224l9.669 6.43c1.335.889 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M165.384 105.477a.195.195 0 00-.21.328l.21-.328zm.631.866a.195.195 0 00.269-.058.195.195 0 00-.059-.269l-.21.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.471 1.405a.194.194 0 00.211-.327l-.211.327zm1.892.75a.194.194 0 10-.21.327l.21-.327zm1.472 1.405a.195.195 0 00.21-.328l-.21.328zm1.892.75a.195.195 0 00-.211.327l.211-.327zm1.471 1.405a.195.195 0 00.21-.328l-.21.328zm1.892.75a.195.195 0 00-.269.058.195.195 0 00.059.269l.21-.327zm1.471 1.404a.194.194 0 10.211-.327l-.211.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.472 1.405a.195.195 0 00.21-.328l-.21.328zm1.892.75a.194.194 0 10-.21.327l.21-.327zm1.471 1.404a.194.194 0 10.21-.327l-.21.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.472 1.405c.09.058.211.031.269-.059a.195.195 0 00-.059-.269l-.21.328zm1.891.75a.194.194 0 10-.21.327l.21-.327zm-26.276-16.371l.841.538.21-.327-.841-.539-.21.328zm2.523 1.616l1.681 1.077.211-.327-1.682-1.078-.21.328zm3.363 2.154l1.682 1.078.21-.328-1.682-1.077-.21.327zm3.363 2.155l1.682 1.078.21-.328-1.681-1.077-.211.327zm3.364 2.155l1.681 1.077.211-.327-1.682-1.077-.21.327zm3.363 2.155l1.682 1.077.21-.328-1.682-1.077-.21.328zm3.364 2.154l1.681 1.077.21-.327-1.681-1.077-.21.327zm3.363 2.155l1.682 1.077.21-.328-1.682-1.077-.21.328zm3.363 2.154l.841.539.21-.328-.841-.538-.21.327zM156.144 59.553a.194.194 0 00-.227-.316l.227.316zm-1.055.278a.193.193 0 00-.044.271.193.193 0 00.27.045l-.226-.316zm-1.428 1.503a.196.196 0 00.044-.27.194.194 0 00-.271-.045l.227.316zm-1.883.872a.195.195 0 00-.045.272.195.195 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm-1.882.872a.194.194 0 00.227.316l-.227-.316zm-1.428 1.504a.194.194 0 00-.227-.316l.227.316zm-1.883.871a.194.194 0 00.227.316l-.227-.316zm-1.428 1.504a.194.194 0 00-.227-.316l.227.316zm-1.882.872a.194.194 0 10.226.316l-.226-.316zm-1.429 1.504a.195.195 0 00.045-.272.195.195 0 00-.272-.044l.227.316zm-1.882.871a.195.195 0 00-.045.272.195.195 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm-1.882.872a.194.194 0 00.226.316l-.226-.316zm-1.428 1.504a.194.194 0 00-.227-.316l.227.316zm-1.883.871a.195.195 0 00-.045.272.196.196 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm25.433-18.726l-.828.594.226.316.829-.594-.227-.316zm-2.483 1.782l-1.656 1.187.227.316 1.656-1.188-.227-.315zm-3.311 2.375l-1.656 1.188.227.316 1.655-1.188-.226-.316zm-3.311 2.376l-1.656 1.187.227.316 1.656-1.188-.227-.315zm-3.311 2.375l-1.655 1.188.226.316 1.656-1.188-.227-.316zm-3.311 2.376l-1.655 1.187.227.316 1.655-1.188-.227-.316zm-3.31 2.375l-1.656 1.188.226.316 1.656-1.188-.226-.316zm-3.311 2.376l-1.656 1.187.227.316 1.656-1.188-.227-.316zm-3.311 2.375l-.828.594.227.316.827-.594-.226-.316z"
|
||||
/>
|
||||
<g filter="url(#filter5_di_571_94747)">
|
||||
<path
|
||||
fill="url(#paint10_linear_571_94747)"
|
||||
stroke="url(#paint11_radial_571_94747)"
|
||||
strokeWidth="1"
|
||||
d="M445.685 235.197l-111.727-74.35c-2.9-1.929-7.598-1.921-10.491.019l-56.671 37.993c-2.894 1.94-2.889 5.078.011 7.008l111.727 74.349c2.901 1.931 7.598 1.923 10.492-.017l56.671-37.994c2.894-1.94 2.888-5.077-.012-7.008z"
|
||||
/>
|
||||
<path
|
||||
fill="#005482"
|
||||
d="M309.137 189.402l-1.776-1.181c-1.676-1.116-4.392-1.112-6.066.01-1.673 1.122-1.669 2.936.008 4.052l1.775 1.181c1.677 1.116 4.393 1.112 6.066-.01 1.673-1.121 1.67-2.936-.007-4.052zM367.484 228.231l-50.466-33.584c-1.677-1.115-4.392-1.111-6.066.011-1.673 1.121-1.67 2.936.007 4.052l50.466 33.583c1.676 1.116 4.392 1.111 6.066-.011 1.673-1.122 1.669-2.936-.007-4.051z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M363.487 194.528l-6.863-4.566c-1.676-1.115-4.392-1.111-6.065.01-1.674 1.122-1.67 2.937.006 4.052l6.862 4.566c1.677 1.117 4.393 1.112 6.066-.01 1.674-1.122 1.67-2.935-.006-4.052zM372.531 200.547l7.124 4.741c1.678 1.116 1.68 2.93.007 4.052-1.673 1.122-4.389 1.126-6.066.01l-7.125-4.741c-1.676-1.116-1.68-2.93-.006-4.051 1.673-1.122 4.389-1.127 6.066-.011zM388.05 224.206l-41.406-27.553c-1.676-1.116-4.392-1.112-6.065.01-1.674 1.122-1.67 2.936.006 4.052l41.406 27.553c1.677 1.116 4.392 1.112 6.066-.01 1.673-1.121 1.67-2.936-.007-4.052zM428.16 237.567l-37.753-25.124c-1.677-1.116-4.393-1.112-6.066.011-1.673 1.122-1.67 2.935.007 4.051l37.753 25.124c1.677 1.115 4.393 1.11 6.066-.011 1.673-1.122 1.67-2.936-.007-4.051zM336.919 190.181l-17.209-11.452c-1.677-1.116-4.393-1.112-6.066.01-1.673 1.121-1.67 2.936.007 4.052l17.209 11.452c1.678 1.116 4.394 1.112 6.066-.01 1.673-1.122 1.671-2.936-.007-4.052zM297.249 204.086l-4.364-2.904c-1.677-1.116-4.393-1.112-6.066.01-1.673 1.121-1.67 2.936.007 4.052l4.365 2.904c1.677 1.117 4.393 1.112 6.066-.01s1.67-2.935-.008-4.052z"
|
||||
/>
|
||||
<path
|
||||
fill="#1E8DCA"
|
||||
d="M323.841 221.923l-17.21-11.452c-1.677-1.116-4.393-1.111-6.066.011-1.674 1.122-1.67 2.936.006 4.051l17.211 11.453c1.676 1.115 4.392 1.111 6.066-.01 1.673-1.122 1.669-2.937-.007-4.053z"
|
||||
/>
|
||||
<path
|
||||
fill="#794AA8"
|
||||
d="M333.862 228.593l24.983 16.625c1.678 1.116 1.68 2.93.007 4.051-1.673 1.122-4.389 1.127-6.066.011l-24.982-16.625c-1.678-1.116-1.681-2.93-.008-4.052 1.673-1.122 4.389-1.126 6.066-.01z"
|
||||
/>
|
||||
<path
|
||||
fill="#FFF388"
|
||||
d="M346.898 183.49l-17.209-11.452c-1.677-1.116-4.393-1.111-6.066.011-1.673 1.122-1.67 2.935.007 4.051l17.209 11.452c1.678 1.116 4.394 1.112 6.067-.01 1.673-1.121 1.67-2.936-.008-4.052z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_di_571_94747"
|
||||
width="200.04"
|
||||
height="143.694"
|
||||
x="6.762"
|
||||
y="164.36"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_571_94747" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.16 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_571_94747" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_di_571_94747"
|
||||
width="200.04"
|
||||
height="143.693"
|
||||
x="61.391"
|
||||
y="125.317"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_571_94747" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.16 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_571_94747" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_dd_571_94747"
|
||||
width="147.33"
|
||||
height="107.019"
|
||||
x="170.442"
|
||||
y="81.08"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4.856" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.16 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="effect1_dropShadow_571_94747" result="effect2_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_571_94747" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_d_571_94747"
|
||||
width="124.658"
|
||||
height="88.103"
|
||||
x="107.138"
|
||||
y="24.755"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_571_94747" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter4_dd_571_94747"
|
||||
width="128.172"
|
||||
height="94.149"
|
||||
x="61.597"
|
||||
y="44.333"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4.856" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.16 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="effect1_dropShadow_571_94747" result="effect2_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_571_94747" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter5_di_571_94747"
|
||||
width="214.235"
|
||||
height="153.254"
|
||||
x="253.129"
|
||||
y="153.905"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_571_94747" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_571_94747" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.16 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_571_94747" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_571_94747"
|
||||
x1="125.627"
|
||||
x2="97.955"
|
||||
y1="211.358"
|
||||
y2="252.942"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint1_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(111.976 -27.866 125.376) scale(37.7222 126.429)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_571_94747"
|
||||
x1="180.256"
|
||||
x2="152.584"
|
||||
y1="172.315"
|
||||
y2="213.899"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint3_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(111.976 12.622 124.287) scale(37.7222 126.429)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_571_94747"
|
||||
x1="272.103"
|
||||
x2="237.918"
|
||||
y1="115.17"
|
||||
y2="166.542"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint5_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(130.436 91.386 110.983) scale(45.9602 75.3358)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_571_94747"
|
||||
x1="183.476"
|
||||
x2="164.24"
|
||||
y1="53.846"
|
||||
y2="82.751"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint7_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(-10.98 23.53919 -73.33289 -34.2066 152.416 46.03)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint8_linear_571_94747"
|
||||
x1="141.536"
|
||||
x2="122.301"
|
||||
y1="80.142"
|
||||
y2="109.048"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint9_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(115.807 32.713 71.158) scale(25.9216 79.0405)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint10_linear_571_94747"
|
||||
x1="390.559"
|
||||
x2="348.755"
|
||||
y1="197.995"
|
||||
y2="260.815"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#fff" stopOpacity="0.17" />
|
||||
<stop offset="1" stopColor="#fff" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint11_radial_571_94747"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(-32.34734 45.5104 -98.19276 -69.79228 338.08 191.004)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0.007" stopColor="#5E6E8C" />
|
||||
<stop offset="0.196" stopColor="#5E6E8C" />
|
||||
<stop offset="1" stopColor="#5E6E8C" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_571_94747">
|
||||
<path fill="#fff" d="M0 0H491.892V280H0z" transform="translate(.054 24.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const ContextDiagramLight: FC<Pick<ContextDiagramProps, 'className'>> = ({ className }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="492"
|
||||
height="329"
|
||||
fill="none"
|
||||
viewBox="0 0 492 329"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_701_6419)">
|
||||
<mask id="mask0_701_6419" width="492" height="281" x="0" y="24" maskUnits="userSpaceOnUse">
|
||||
<path fill="#fff" d="M491.946 24.5H.054v280h491.892v-280z" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_701_6419)">
|
||||
<g filter="url(#filter0_di_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M64.379 171.719h0l120.467 80.166c1.348.897 1.954 2.023 1.956 3.085.002 1.062-.601 2.191-1.946 3.092l-33.734 22.616.278.415-.278-.415c-1.349.904-3.144 1.373-4.966 1.376-1.822.003-3.619-.459-4.971-1.358 0 0 0 0 0 0L20.718 200.529h0c-1.347-.896-1.954-2.023-1.956-3.085-.002-1.062.601-2.19 1.945-3.091h0l33.736-22.617c1.348-.904 3.144-1.372 4.965-1.375 1.822-.004 3.62.459 4.97 1.358z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M137.496 230.016l-34.325-22.843c-1.791-1.192-4.692-1.187-6.478.011-1.787 1.199-1.784 3.135.007 4.327l34.326 22.843c1.791 1.191 4.691 1.186 6.478-.011 1.787-1.198 1.783-3.136-.008-4.327zM146.629 236.094l21.307 14.178c1.791 1.192 1.794 3.129.007 4.327-1.787 1.198-4.687 1.203-6.478.011l-21.306-14.178c-1.791-1.191-1.794-3.129-.007-4.327 1.786-1.197 4.687-1.202 6.477-.011zM93.29 200.598l-1.977-1.316c-1.791-1.191-4.691-1.186-6.478.012-1.786 1.197-1.783 3.135.008 4.327l1.976 1.315c1.79 1.192 4.692 1.186 6.478-.012 1.787-1.197 1.784-3.134-.007-4.326z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M120.733 234.085l-18.693-12.439c-1.791-1.192-4.692-1.187-6.478.011-1.787 1.197-1.784 3.135.007 4.327l18.694 12.439c1.79 1.192 4.691 1.188 6.478-.011 1.787-1.198 1.783-3.135-.008-4.327zM137.299 259.346l-41.187-27.408c-1.791-1.192-4.691-1.187-6.478.011-1.787 1.197-1.784 3.134.007 4.327l41.187 27.408c1.791 1.191 4.691 1.186 6.478-.012 1.787-1.197 1.784-3.135-.007-4.326zM80.597 221.613l4.3 2.862c1.79 1.191 1.794 3.128.007 4.327-1.787 1.198-4.687 1.202-6.478.011l-4.3-2.862c-1.79-1.191-1.794-3.129-.007-4.327 1.787-1.197 4.687-1.202 6.478-.011zM150.498 253.893l-20.368-13.554c-1.791-1.193-4.691-1.188-6.478.011-1.787 1.198-1.784 3.134.007 4.327l20.369 13.554c1.791 1.191 4.691 1.186 6.478-.011 1.786-1.198 1.783-3.135-.008-4.327zM71.008 215.233l-2.135-1.42c-1.79-1.193-4.69-1.188-6.478.011-1.787 1.198-1.783 3.134.008 4.327l2.134 1.42c1.791 1.192 4.692 1.187 6.478-.011 1.787-1.198 1.784-3.135-.007-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M92.503 215.298l-12.971-8.631c-1.791-1.192-4.691-1.187-6.478.011s-1.784 3.135.007 4.327l12.97 8.631c1.792 1.193 4.693 1.188 6.48-.011 1.786-1.198 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#C5C5EC"
|
||||
d="M55.67 190.103l3.95 2.629-.008-5.272-3.943 2.643zm-.012-6.71l5.747 3.824.01 5.751-8.6 5.766c-.381.255-.896.399-1.435.399-.539.002-1.055-.14-1.436-.394l-8.62-5.736c-.381-.254-.595-.598-.596-.958 0-.359.213-.705.593-.959l11.467-7.688c.795-.533 2.073-.536 2.87-.005zm-5.002 11.99l1.433-.961-6.465-4.302-1.433.961 6.465 4.302zm5.022-.487l1.434-.962-8.62-5.736-1.434.96 8.62 5.738z"
|
||||
/>
|
||||
<path stroke="#fff" strokeLinecap="round" strokeOpacity="0.2" d="M50.253 212.974l32.554-21.968" />
|
||||
</g>
|
||||
<g filter="url(#filter1_di_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M75.337 155.31v-.001l33.735-22.616s0 0 0 0c1.348-.904 3.144-1.373 4.966-1.376 1.821-.003 3.619.459 4.97 1.358 0 0 0 0 0 0l120.468 80.167c1.347.896 1.953 2.022 1.955 3.084.002 1.062-.601 2.191-1.945 3.092l.278.415-.278-.415-33.735 22.617c-1.348.903-3.144 1.372-4.966 1.375-1.821.003-3.619-.459-4.97-1.359l-.277.417.277-.417-120.468-80.165v-.001c-1.347-.896-1.954-2.023-1.956-3.084-.001-1.062.602-2.19 1.946-3.091z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M192.126 190.972L157.8 168.129c-1.791-1.191-4.692-1.186-6.479.012-1.787 1.197-1.783 3.134.008 4.326l34.325 22.843c1.791 1.192 4.692 1.186 6.479-.012 1.787-1.197 1.784-3.134-.007-4.326zM201.259 197.051l21.306 14.178c1.791 1.191 1.794 3.129.007 4.326-1.786 1.198-4.687 1.203-6.478.012l-21.305-14.179c-1.791-1.192-1.795-3.128-.008-4.327 1.787-1.198 4.687-1.203 6.478-.01zM147.918 161.554l-1.976-1.315c-1.791-1.192-4.691-1.187-6.477.011-1.787 1.198-1.784 3.135.007 4.327l1.976 1.315c1.791 1.192 4.691 1.187 6.478-.011 1.787-1.199 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M175.363 195.042l-18.694-12.44c-1.791-1.192-4.691-1.187-6.478.01-1.787 1.199-1.783 3.136.007 4.327l18.694 12.441c1.791 1.191 4.691 1.186 6.477-.011 1.788-1.198 1.785-3.136-.006-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M191.928 220.302l-41.187-27.408c-1.791-1.192-4.691-1.187-6.478.01-1.786 1.199-1.783 3.136.008 4.327l41.186 27.408c1.791 1.193 4.692 1.188 6.479-.01 1.786-1.199 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M135.227 182.57l4.299 2.861c1.791 1.191 1.794 3.129.008 4.327-1.787 1.197-4.687 1.202-6.478.011l-4.3-2.862c-1.791-1.191-1.795-3.128-.008-4.326 1.787-1.199 4.688-1.204 6.479-.011z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M205.128 214.85l-20.369-13.556c-1.791-1.191-4.691-1.186-6.478.012-1.787 1.197-1.783 3.135.008 4.327l20.368 13.554c1.791 1.192 4.691 1.187 6.478-.011s1.784-3.135-.007-4.326z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M125.637 176.189l-2.135-1.421c-1.79-1.191-4.691-1.186-6.478.012-1.787 1.197-1.783 3.135.008 4.326l2.135 1.421c1.791 1.192 4.691 1.187 6.478-.011 1.786-1.198 1.783-3.135-.008-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M147.132 176.255l-12.971-8.632c-1.791-1.192-4.691-1.187-6.478.012-1.787 1.197-1.784 3.134.007 4.326l12.971 8.632c1.791 1.191 4.691 1.186 6.478-.011 1.787-1.198 1.784-3.136-.007-4.327z"
|
||||
/>
|
||||
<path
|
||||
fill="#C5C5EC"
|
||||
d="M110.298 151.059l3.952 2.629-.01-5.272-3.942 2.643zm-.011-6.71l5.747 3.824.01 5.752-8.601 5.765c-.38.255-.896.399-1.434.4-.539.001-1.055-.141-1.436-.395l-8.62-5.736c-.381-.254-.596-.598-.596-.958 0-.359.213-.704.593-.959l11.467-7.688c.795-.533 2.073-.535 2.87-.005zm-5.002 11.991l1.433-.961-6.465-4.303-1.433.961 6.465 4.303zm5.022-.488l1.433-.961-8.62-5.736-1.433.96 8.62 5.737z"
|
||||
/>
|
||||
<path stroke="#fff" strokeLinecap="round" strokeOpacity="0.2" d="M104.883 173.93l32.554-21.968" />
|
||||
</g>
|
||||
<g filter="url(#filter2_dd_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M306.539 139.328h0c.302.201.444.457.472.769.03.335-.069.775-.35 1.304-.56 1.058-1.761 2.317-3.509 3.489l-41.675 27.939.279.416-.279-.416c-1.742 1.169-3.651 1.998-5.319 2.397-.834.199-1.593.288-2.233.264-.646-.024-1.131-.162-1.452-.375l-.277.416.277-.416-70.798-47.113s0 0 0 0c-.302-.201-.443-.457-.471-.769-.03-.336.069-.776.35-1.305.56-1.058 1.761-2.316 3.51-3.489h0l41.674-27.94h0c1.742-1.167 3.651-1.996 5.319-2.395.833-.2 1.593-.288 2.233-.264.646.024 1.131.161 1.452.375 0 0 0 0 0 0l70.797 47.113z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M271.145 121.311L234.743 97.1c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l36.401 24.211c1.335.888 3.497.884 4.829-.009 1.332-.892 1.33-2.335-.005-3.223zM256.675 123.029l-13.935-9.268c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l13.934 9.268c1.335.888 3.497.885 4.829-.008 1.332-.892 1.33-2.336-.005-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M259.537 135.54l-24.741-16.455c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.224l24.742 16.455c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224zM286.251 142.7l-22.467-14.942c-1.335-.888-3.497-.884-4.829.008-1.331.893-1.329 2.336.006 3.224l22.466 14.942c1.335.888 3.497.885 4.829-.008 1.332-.892 1.33-2.336-.005-3.224zM227.686 114.356l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.33-2.336-.005-3.224zM206.435 130.13l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M223.863 142.338l-9.669-6.431c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.329 2.336.006 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224zM235.63 109.033l-9.669-6.431c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.329 2.336.006 3.224l9.669 6.431c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.335-.006-3.223z"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M138.103 29.108h0c.915-.614 2.104-.955 3.288-.993 1.187-.039 2.317.228 3.128.768l76.376 50.825c.8.532 1.108 1.201 1.066 1.832-.044.646-.463 1.38-1.38 1.994l-23.45 15.721.279.415-.279-.415c-.916.614-2.104.955-3.288.993-1.187.039-2.316-.228-3.128-.768l-76.376-50.825c-.8-.532-1.108-1.201-1.066-1.832.043-.646.463-1.38 1.38-1.994h0l23.45-15.721z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M187.645 61.991l-36.401-24.21c-1.335-.889-3.497-.885-4.829.007-1.332.893-1.33 2.336.005 3.224l36.402 24.211c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M173.175 63.71l-22.524-14.98c-1.335-.889-3.497-.885-4.829.008-1.332.892-1.329 2.335.006 3.223l22.523 14.98c1.335.889 3.497.885 4.829-.007 1.332-.893 1.33-2.336-.005-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M176.588 76.603l-24.741-16.456c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l24.742 16.456c1.335.888 3.497.884 4.829-.008 1.332-.893 1.329-2.336-.006-3.224zM202.75 83.381L180.284 68.44c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.006 3.224l22.466 14.942c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M136.559 49.964l-2.043-1.359c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.329 2.335.006 3.223l2.042 1.359c1.335.888 3.497.884 4.829-.008 1.332-.893 1.33-2.336-.005-3.224zM145.003 55.709l-2.042-1.359c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.005 3.224l2.043 1.359c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.336-.006-3.223z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M143.793 44.169l-1.333-.887c-1.335-.888-3.497-.884-4.829.008-1.331.893-1.329 2.336.006 3.224l1.333.886c1.335.888 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter4_dd_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M97.214 56.104h0c.918-.616 2.1-.962 3.268-1.01 1.173-.048 2.279.206 3.065.729l74.452 49.545c.772.514 1.061 1.164 1.008 1.784-.056.639-.485 1.37-1.405 1.986l.278.416-.278-.416-23.45 15.721c-.918.616-2.1.962-3.268 1.01-1.173.048-2.279-.205-3.064-.728L73.367 75.595l-.277.417.277-.416c-.773-.514-1.061-1.164-1.008-1.784.056-.64.485-1.37 1.406-1.987l23.45-15.721z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M142.102 86.838l-25.588-17.02c-1.335-.887-3.497-.883-4.829.01-1.332.892-1.33 2.335.005 3.223l25.588 17.019c1.335.888 3.497.884 4.829-.009 1.332-.892 1.33-2.336-.005-3.223zM148.91 91.366l15.883 10.564c1.335.888 1.337 2.331.005 3.223-1.332.893-3.494.897-4.829.009l-15.882-10.564c-1.335-.888-1.338-2.331-.006-3.224 1.332-.892 3.494-.896 4.829-.008z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M109.148 64.92l-1.473-.98c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.224l1.474.98c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.335-.006-3.224zM129.606 89.87L115.671 80.6c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.329 2.335.006 3.223l13.935 9.269c1.335.888 3.497.884 4.829-.009 1.332-.892 1.329-2.336-.006-3.224zM141.954 108.69l-30.702-20.42c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.005 3.223l30.703 20.421c1.335.888 3.497.884 4.829-.008 1.331-.893 1.329-2.336-.006-3.224zM99.687 80.577l3.205 2.132c1.335.888 1.338 2.331.006 3.224-1.332.892-3.494.896-4.829.008l-3.205-2.132c-1.335-.888-1.338-2.33-.006-3.223 1.332-.893 3.494-.897 4.83-.009z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M151.794 104.627L136.61 94.528c-1.335-.888-3.497-.884-4.829.009-1.332.892-1.33 2.335.005 3.223l15.184 10.099c1.335.888 3.497.884 4.829-.008 1.332-.893 1.33-2.336-.005-3.224zM92.54 75.823l-1.592-1.058c-1.335-.888-3.497-.884-4.829.008-1.332.893-1.33 2.336.006 3.224l1.59 1.058c1.336.888 3.498.885 4.83-.008 1.332-.892 1.33-2.336-.006-3.224z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M108.561 75.873l-9.669-6.431c-1.335-.888-3.497-.885-4.829.008-1.332.892-1.33 2.336.006 3.224l9.669 6.43c1.335.889 3.497.885 4.829-.008 1.332-.892 1.329-2.336-.006-3.224z"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M165.384 105.477a.195.195 0 00-.21.328l.21-.328zm.631.866a.195.195 0 00.269-.058.195.195 0 00-.059-.269l-.21.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.471 1.405a.194.194 0 00.211-.327l-.211.327zm1.892.75a.194.194 0 10-.21.327l.21-.327zm1.472 1.405c.09.058.211.031.269-.059a.195.195 0 00-.059-.269l-.21.328zm1.892.75a.195.195 0 00-.211.327l.211-.327zm1.471 1.405a.195.195 0 00.21-.328l-.21.328zm1.892.75a.195.195 0 00-.269.058.195.195 0 00.059.269l.21-.327zm1.471 1.404a.194.194 0 00.211-.327l-.211.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.472 1.405a.195.195 0 00.21-.328l-.21.328zm1.892.75a.194.194 0 00-.211.327l.211-.327zm1.471 1.404a.194.194 0 10.21-.327l-.21.327zm1.892.75a.195.195 0 00-.21.328l.21-.328zm1.472 1.405c.09.058.211.031.269-.059a.195.195 0 00-.059-.269l-.21.328zm1.891.75a.194.194 0 10-.21.327l.21-.327zm-26.276-16.371l.841.538.21-.327-.841-.539-.21.328zm2.523 1.616l1.681 1.077.211-.327-1.682-1.078-.21.328zm3.363 2.154l1.682 1.078.21-.328-1.682-1.077-.21.327zm3.363 2.155l1.682 1.078.21-.328-1.681-1.077-.211.327zm3.364 2.155l1.681 1.077.211-.327-1.682-1.077-.21.327zm3.363 2.155l1.682 1.077.21-.328-1.682-1.077-.21.328zm3.363 2.154l1.682 1.077.21-.327-1.681-1.077-.211.327zm3.364 2.155l1.682 1.077.21-.328-1.682-1.077-.21.328zm3.363 2.154l.841.539.21-.328-.841-.538-.21.327zM156.144 59.553a.194.194 0 00-.227-.316l.227.316zm-1.055.278a.194.194 0 00.226.316l-.226-.316zm-1.428 1.503a.196.196 0 00.044-.27.194.194 0 00-.271-.045l.227.316zm-1.883.872a.195.195 0 00-.045.272.195.195 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm-1.881.872a.194.194 0 10.226.316l-.226-.316zm-1.429 1.504a.195.195 0 00.045-.272.195.195 0 00-.272-.044l.227.316zm-1.883.871a.194.194 0 00.227.316l-.227-.316zm-1.428 1.504a.194.194 0 00-.226-.316l.226.316zm-1.882.872a.194.194 0 10.226.316l-.226-.316zm-1.429 1.504a.195.195 0 00.045-.272.195.195 0 00-.272-.044l.227.316zm-1.882.871a.195.195 0 00-.045.272.195.195 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm-1.882.872a.194.194 0 00.226.316l-.226-.316zm-1.428 1.504a.194.194 0 00-.227-.316l.227.316zm-1.883.871a.195.195 0 00-.045.272.196.196 0 00.272.044l-.227-.316zm-1.429 1.504a.194.194 0 10-.226-.316l.226.316zm25.433-18.726l-.828.594.226.316.829-.594-.227-.316zm-2.483 1.782l-1.656 1.187.227.316 1.656-1.188-.227-.315zm-3.311 2.375l-1.655 1.188.226.316 1.655-1.188-.226-.316zm-3.311 2.376l-1.656 1.187.227.316 1.656-1.188-.227-.315zm-3.31 2.375l-1.656 1.188.226.316 1.656-1.188-.226-.316zm-3.312 2.376l-1.655 1.187.227.316 1.655-1.188-.227-.316zm-3.31 2.375l-1.656 1.188.226.316 1.656-1.188-.226-.316zm-3.311 2.376l-1.656 1.187.227.316 1.656-1.188-.227-.316zm-3.311 2.375l-.828.594.227.316.827-.594-.226-.316z"
|
||||
/>
|
||||
<g filter="url(#filter5_di_701_6419)">
|
||||
<path
|
||||
fill="#fff"
|
||||
stroke="#DBE2F0"
|
||||
d="M267.074 199.275h0l56.671-37.994-.278-.415.278.415c1.348-.904 3.144-1.372 4.966-1.375 1.821-.004 3.619.459 4.97 1.358l111.727 74.349c1.347.897 1.954 2.023 1.956 3.085.002 1.062-.601 2.191-1.945 3.092l-56.671 37.994c-1.349.903-3.145 1.372-4.966 1.375-1.822.003-3.619-.459-4.971-1.359l-.277.416.277-.416-111.727-74.349-.277.416.277-.416c-1.347-.897-1.953-2.023-1.955-3.085-.002-1.062.601-2.19 1.945-3.091z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M309.137 189.402l-1.776-1.181c-1.677-1.116-4.392-1.112-6.066.01-1.673 1.122-1.67 2.936.008 4.052l1.775 1.181c1.676 1.116 4.392 1.112 6.066-.01 1.673-1.121 1.67-2.936-.007-4.052z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M367.484 228.231l-50.466-33.584c-1.677-1.115-4.392-1.111-6.066.011-1.673 1.121-1.67 2.936.007 4.052l50.466 33.583c1.676 1.116 4.392 1.111 6.066-.011 1.673-1.122 1.669-2.936-.007-4.051zM363.486 194.528l-6.862-4.566c-1.676-1.115-4.392-1.111-6.065.01-1.674 1.122-1.67 2.937.006 4.052l6.862 4.566c1.677 1.117 4.393 1.112 6.066-.01s1.67-2.935-.007-4.052zM372.531 200.547l7.124 4.741c1.678 1.116 1.68 2.93.007 4.052-1.673 1.122-4.389 1.126-6.066.01l-7.125-4.741c-1.676-1.116-1.68-2.93-.006-4.051 1.673-1.122 4.389-1.127 6.066-.011z"
|
||||
/>
|
||||
<path
|
||||
fill="#A6B6D9"
|
||||
d="M388.05 224.206l-41.406-27.553c-1.676-1.116-4.392-1.112-6.065.01-1.674 1.122-1.67 2.936.006 4.052l41.406 27.553c1.676 1.116 4.392 1.112 6.066-.01 1.673-1.121 1.67-2.936-.007-4.052zM428.16 237.567l-37.753-25.124c-1.677-1.116-4.393-1.112-6.066.011-1.673 1.122-1.67 2.935.007 4.051l37.753 25.124c1.677 1.115 4.393 1.11 6.066-.011 1.673-1.122 1.67-2.936-.007-4.051zM336.919 190.181l-17.209-11.452c-1.677-1.116-4.393-1.112-6.066.01-1.673 1.121-1.67 2.936.007 4.052l17.209 11.452c1.678 1.116 4.394 1.112 6.066-.01 1.673-1.122 1.671-2.936-.007-4.052zM297.249 204.086l-4.364-2.904c-1.677-1.116-4.393-1.112-6.066.01-1.673 1.121-1.67 2.936.007 4.052l4.365 2.904c1.677 1.117 4.393 1.112 6.066-.01s1.67-2.935-.008-4.052z"
|
||||
/>
|
||||
<path
|
||||
fill="#DBE2F0"
|
||||
d="M323.841 221.923l-17.211-11.452c-1.676-1.116-4.392-1.111-6.065.011-1.674 1.122-1.67 2.936.006 4.051l17.211 11.453c1.676 1.115 4.392 1.111 6.066-.01 1.673-1.122 1.669-2.937-.007-4.053zM333.862 228.593l24.983 16.625c1.678 1.116 1.68 2.93.007 4.051-1.673 1.122-4.389 1.127-6.066.011l-24.982-16.625c-1.678-1.116-1.681-2.93-.008-4.052 1.673-1.122 4.389-1.126 6.066-.01zM346.898 183.49l-17.209-11.452c-1.677-1.116-4.393-1.111-6.066.011-1.673 1.122-1.67 2.935.007 4.051l17.209 11.452c1.678 1.116 4.394 1.112 6.067-.01 1.673-1.121 1.67-2.936-.008-4.052z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_di_701_6419"
|
||||
width="199.04"
|
||||
height="142.693"
|
||||
x="7.262"
|
||||
y="164.861"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_701_6419" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.04 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_701_6419" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_di_701_6419"
|
||||
width="199.04"
|
||||
height="142.693"
|
||||
x="61.892"
|
||||
y="125.817"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_701_6419" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.04 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_701_6419" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_dd_701_6419"
|
||||
width="146.242"
|
||||
height="105.932"
|
||||
x="170.986"
|
||||
y="81.624"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4.856" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.08 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0" />
|
||||
<feBlend in2="effect1_dropShadow_701_6419" result="effect2_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_701_6419" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_d_701_6419"
|
||||
width="123.57"
|
||||
height="87.016"
|
||||
x="107.682"
|
||||
y="25.299"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_701_6419" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter4_dd_701_6419"
|
||||
width="127.084"
|
||||
height="93.061"
|
||||
x="62.141"
|
||||
y="44.876"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4.856" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.08 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="1.85" dy="4.625" />
|
||||
<feGaussianBlur stdDeviation="3.469" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0" />
|
||||
<feBlend in2="effect1_dropShadow_701_6419" result="effect2_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow_701_6419" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter5_di_701_6419"
|
||||
width="213.235"
|
||||
height="152.254"
|
||||
x="253.629"
|
||||
y="154.406"
|
||||
colorInterpolationFilters="sRGB"
|
||||
filterUnits="userSpaceOnUse"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dx="4" dy="10" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_701_6419" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_701_6419" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="10.5" />
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic" />
|
||||
<feColorMatrix values="0 0 0 0 0.631373 0 0 0 0 0.0705882 0 0 0 0 1 0 0 0 0.04 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_701_6419" />
|
||||
</filter>
|
||||
<clipPath id="clip0_701_6419">
|
||||
<path fill="#fff" d="M0 0H491.892V280H0z" transform="translate(.054 24.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,16 +1,16 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import type {
|
||||
TranscriptJSON,
|
||||
TranscriptJSONScope,
|
||||
CodyClient,
|
||||
CodyClientScope,
|
||||
CodyClientConfig,
|
||||
CodyClientEvent,
|
||||
CodyClientScope,
|
||||
TranscriptJSON,
|
||||
TranscriptJSONScope,
|
||||
} from '@sourcegraph/cody-shared'
|
||||
import { Transcript, useClient, NoopEditor } from '@sourcegraph/cody-shared'
|
||||
import { NoopEditor, Transcript, useClient } from '@sourcegraph/cody-shared'
|
||||
import type { Scalars } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger'
|
||||
@ -18,8 +18,8 @@ import { useLocalStorage } from '@sourcegraph/wildcard'
|
||||
|
||||
import { EventName } from '../util/constants'
|
||||
|
||||
import { isEmailVerificationNeededForCody } from './isCodyEnabled'
|
||||
import { useCodyIgnore } from './useCodyIgnore'
|
||||
import { currentUserRequiresEmailVerificationForCody } from './util'
|
||||
|
||||
export interface CodyChatStore
|
||||
extends Pick<
|
||||
@ -179,7 +179,7 @@ export const useCodyChat = ({
|
||||
accessToken: null,
|
||||
customHeaders: window.context.xhrHeaders,
|
||||
debugEnable: false,
|
||||
needsEmailVerification: isEmailVerificationNeededForCody(),
|
||||
needsEmailVerification: currentUserRequiresEmailVerificationForCody(),
|
||||
experimentalLocalSymbols: false,
|
||||
},
|
||||
scope: initialScope,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { createContext, useCallback, useMemo, useContext, useEffect, useState } from 'react'
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { type ApolloClient, type ApolloQueryResult, useApolloClient } from '@apollo/client'
|
||||
import { useApolloClient, type ApolloClient, type ApolloQueryResult } from '@apollo/client'
|
||||
|
||||
import { useQuery, gql, getDocumentNode } from '@sourcegraph/http-client'
|
||||
import { getDocumentNode, gql, useQuery } from '@sourcegraph/http-client'
|
||||
|
||||
import type {
|
||||
CodyIgnoreContentResult,
|
||||
@ -11,8 +11,6 @@ import type {
|
||||
ContextFiltersVariables,
|
||||
} from '../graphql-operations'
|
||||
|
||||
import { isCodyEnabled } from './isCodyEnabled'
|
||||
|
||||
interface CodyIgnoreFns {
|
||||
isRepoIgnored(repoName: string): boolean
|
||||
isFileIgnored(repoName: string, filePath: string): boolean
|
||||
@ -49,7 +47,7 @@ export const CodyIgnoreProvider: React.FC<React.PropsWithChildren<{ isSourcegrap
|
||||
children,
|
||||
}) => {
|
||||
// Cody is not enabled, return default ignore fns.
|
||||
if (!isCodyEnabled()) {
|
||||
if (!window.context?.codyEnabledForCurrentUser) {
|
||||
return <CodyIgnoreContext.Provider value={defaultCodyIgnoreFns}>{children}</CodyIgnoreContext.Provider>
|
||||
}
|
||||
|
||||
|
||||
@ -42,3 +42,10 @@ export function isValidEmailAddress(emailAddress: string): boolean {
|
||||
* and keep in mind that the backend validation has the final say, validation in the web app is only for UX improvement.
|
||||
*/
|
||||
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
|
||||
|
||||
/**
|
||||
* Whether the current user is unable to use Cody because they must verify their email address.
|
||||
*/
|
||||
export function currentUserRequiresEmailVerificationForCody(): boolean {
|
||||
return window.context?.codyRequiresVerifiedEmail && !window.context?.currentUser?.hasVerifiedEmail
|
||||
}
|
||||
|
||||
@ -72,8 +72,4 @@ export const legacyLayoutRouteContextMock = {
|
||||
...dynamicWebAppConfig,
|
||||
...legacyRouteComputedContext,
|
||||
...legacyRouteInjectedContext,
|
||||
licenseFeatures: {
|
||||
isCodeSearchEnabled: true,
|
||||
isCodyEnabled: true,
|
||||
},
|
||||
} satisfies LegacyLayoutRouteContext
|
||||
|
||||
@ -20,11 +20,6 @@ const config: Meta = {
|
||||
|
||||
export default config
|
||||
|
||||
const licenseFeatures = {
|
||||
isCodeSearchEnabled: true,
|
||||
isCodyEnabled: true,
|
||||
}
|
||||
|
||||
// Moved story under enterprise folder to avoid failing ci linting
|
||||
// due to importing enterprise path in oss folders.
|
||||
export const AdminSidebarItems: StoryFn = () => (
|
||||
@ -44,7 +39,6 @@ export const AdminSidebarItems: StoryFn = () => (
|
||||
batchChangesWebhookLogsEnabled={true}
|
||||
codeInsightsEnabled={true}
|
||||
endUserOnboardingEnabled={false}
|
||||
license={licenseFeatures}
|
||||
/>
|
||||
<SiteAdminSidebar
|
||||
{...webProps}
|
||||
@ -55,7 +49,6 @@ export const AdminSidebarItems: StoryFn = () => (
|
||||
batchChangesWebhookLogsEnabled={true}
|
||||
codeInsightsEnabled={true}
|
||||
endUserOnboardingEnabled={false}
|
||||
license={licenseFeatures}
|
||||
/>
|
||||
<SiteAdminSidebar
|
||||
{...webProps}
|
||||
@ -66,7 +59,6 @@ export const AdminSidebarItems: StoryFn = () => (
|
||||
batchChangesWebhookLogsEnabled={false}
|
||||
codeInsightsEnabled={true}
|
||||
endUserOnboardingEnabled={false}
|
||||
license={licenseFeatures}
|
||||
/>
|
||||
<SiteAdminSidebar
|
||||
{...webProps}
|
||||
@ -77,7 +69,6 @@ export const AdminSidebarItems: StoryFn = () => (
|
||||
batchChangesWebhookLogsEnabled={true}
|
||||
codeInsightsEnabled={false}
|
||||
endUserOnboardingEnabled={false}
|
||||
license={licenseFeatures}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
@ -6,10 +6,10 @@ import { renderMarkdown } from '@sourcegraph/common'
|
||||
import type { Notice } from '@sourcegraph/shared/src/schema/settings.schema'
|
||||
import { useSettings } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { Alert, type AlertProps, Markdown } from '@sourcegraph/wildcard'
|
||||
import { Alert, Markdown, type AlertProps } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import { isEmailVerificationNeededForCody } from '../cody/isCodyEnabled'
|
||||
import { currentUserRequiresEmailVerificationForCody } from '../cody/util'
|
||||
import { DismissibleAlert } from '../components/DismissibleAlert'
|
||||
|
||||
import styles from './Notices.module.scss'
|
||||
@ -103,11 +103,11 @@ export const VerifyEmailNotices: React.FunctionComponent<VerifyEmailNoticesProps
|
||||
telemetryRecorder,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (isEmailVerificationNeededForCody() && authenticatedUser) {
|
||||
if (currentUserRequiresEmailVerificationForCody() && authenticatedUser) {
|
||||
telemetryRecorder.recordEvent('alert.verifyEmail', 'view')
|
||||
}
|
||||
}, [telemetryRecorder, authenticatedUser])
|
||||
if (isEmailVerificationNeededForCody() && authenticatedUser) {
|
||||
if (currentUserRequiresEmailVerificationForCody() && authenticatedUser) {
|
||||
return (
|
||||
<div className={classNames(styles.notices, className)}>
|
||||
<NoticeAlert
|
||||
|
||||
@ -31,7 +31,8 @@ export const createJsContext = ({ sourcegraphBaseUrl }: { sourcegraphBaseUrl: st
|
||||
batchChangesWebhookLogsEnabled: true,
|
||||
codeInsightsEnabled: true,
|
||||
executorsEnabled: true,
|
||||
codyEnabled: true,
|
||||
codyEnabledOnInstance: true,
|
||||
codeSearchEnabledOnInstance: true,
|
||||
codeIntelligenceEnabled: true,
|
||||
codeMonitoringEnabled: true,
|
||||
notebooksEnabled: true,
|
||||
@ -57,10 +58,6 @@ export const createJsContext = ({ sourcegraphBaseUrl }: { sourcegraphBaseUrl: st
|
||||
maxNumChangesets: -1,
|
||||
unrestricted: true,
|
||||
},
|
||||
features: {
|
||||
codeSearch: true,
|
||||
cody: true,
|
||||
},
|
||||
},
|
||||
needServerRestart: false,
|
||||
needsSiteInit: false,
|
||||
|
||||
@ -196,15 +196,22 @@ export interface SourcegraphContext extends Pick<Required<SiteConfiguration>, 'e
|
||||
|
||||
batchChangesWebhookLogsEnabled: boolean
|
||||
|
||||
/** Whether cody is enabled site-wide. */
|
||||
codyEnabled: boolean
|
||||
/**
|
||||
* Whether Cody is enabled on this instance. Check
|
||||
* {@link SourcegraphContext.codyEnabledForCurrentUser} to see whether Cody is enabled for the
|
||||
* current user.
|
||||
*/
|
||||
codyEnabledOnInstance: boolean
|
||||
|
||||
/** Whether cody is enabled for the user. */
|
||||
/** Whether Cody is enabled for the user. */
|
||||
codyEnabledForCurrentUser: boolean
|
||||
|
||||
/** Whether the site requires a verified email for cody. */
|
||||
/** Whether the instance requires a verified email for Cody. */
|
||||
codyRequiresVerifiedEmail: boolean
|
||||
|
||||
/** Whether the code search feature is enabled on the instance. */
|
||||
codeSearchEnabledOnInstance: boolean
|
||||
|
||||
/** Whether executors are enabled on the site. */
|
||||
executorsEnabled: boolean
|
||||
|
||||
@ -285,7 +292,6 @@ export interface SourcegraphContext extends Pick<Required<SiteConfiguration>, 'e
|
||||
/** Contains information about the product license. */
|
||||
licenseInfo?: {
|
||||
batchChanges?: BatchChangesLicenseInfo
|
||||
features: LicenseFeatures
|
||||
}
|
||||
|
||||
/** sha256 hashed license key */
|
||||
@ -320,11 +326,3 @@ export interface BrandAssets {
|
||||
/** The URL to the symbol used as the search icon */
|
||||
symbol?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the license features available.
|
||||
*/
|
||||
export interface LicenseFeatures {
|
||||
codeSearch: boolean
|
||||
cody: boolean
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import { type PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import type { BatchChangesProps } from '../batches'
|
||||
import type { UserAreaUserFields, OrgAreaOrganizationFields } from '../graphql-operations'
|
||||
import type { OrgAreaOrganizationFields, UserAreaUserFields } from '../graphql-operations'
|
||||
import type { NavItemWithIconDescriptor, RouteV6Descriptor } from '../util/contributions'
|
||||
|
||||
/**
|
||||
@ -13,10 +13,6 @@ export interface NamespaceAreaContext extends PlatformContextProps {
|
||||
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
isSourcegraphDotCom: boolean
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface NamespaceAreaRoute extends RouteV6Descriptor<NamespaceAreaContext> {}
|
||||
|
||||
@ -17,20 +17,20 @@ export const namespaceAreaRoutes: readonly NamespaceAreaRoute[] = [
|
||||
{
|
||||
path: 'searches',
|
||||
render: props => <SavedSearchListPage {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: 'searches/add',
|
||||
render: props => (
|
||||
<SavedSearchCreateForm {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
),
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: 'searches/:id',
|
||||
render: props => (
|
||||
<SavedSearchUpdateForm {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
),
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
]
|
||||
|
||||
@ -87,7 +87,9 @@ const config: Meta<typeof GlobalNavbar> = {
|
||||
export default config
|
||||
|
||||
export const Default: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.licenseInfo = { features: { codeSearch: true, cody: true } }
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
return (
|
||||
<Grid columnCount={1}>
|
||||
<div>
|
||||
@ -115,8 +117,10 @@ export const Default: StoryFn<GlobalNavbarProps> = props => {
|
||||
)
|
||||
}
|
||||
|
||||
export const CodyOnlyLicense: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.licenseInfo = { features: { codeSearch: false, cody: true } }
|
||||
export const CodyOnly: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.codeSearchEnabledOnInstance = false
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
return (
|
||||
<Grid columnCount={1}>
|
||||
<div>
|
||||
@ -144,8 +148,41 @@ export const CodyOnlyLicense: StoryFn<GlobalNavbarProps> = props => {
|
||||
)
|
||||
}
|
||||
|
||||
export const CodeSearchOnlyLicense: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.licenseInfo = { features: { codeSearch: true, cody: false } }
|
||||
export const UserNotLicensedForCody: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
return (
|
||||
<Grid columnCount={1}>
|
||||
<div>
|
||||
<H3 className="ml-2">Anonymous viewer</H3>
|
||||
<GlobalNavbar {...props} />
|
||||
</div>
|
||||
<div>
|
||||
<H3 className="ml-2">Anonymous viewer with all possible nav items</H3>
|
||||
<GlobalNavbar {...props} {...allNavItemsProps} />
|
||||
</div>
|
||||
<div>
|
||||
<H3 className="ml-2">Authenticated user with all possible nav items</H3>
|
||||
<GlobalNavbar {...props} {...allNavItemsProps} {...allAuthenticatedNavItemsProps} />
|
||||
</div>
|
||||
<div>
|
||||
<H3 className="ml-2">Authenticated user with all possible nav items and search input</H3>
|
||||
<GlobalNavbar
|
||||
{...props}
|
||||
{...allNavItemsProps}
|
||||
{...allAuthenticatedNavItemsProps}
|
||||
showSearchBox={true}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export const CodeSearchOnly: StoryFn<GlobalNavbarProps> = props => {
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = false
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
return (
|
||||
<Grid columnCount={1}>
|
||||
<div>
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import { describe, expect, test, vi, afterAll } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'
|
||||
import {
|
||||
mockFetchSearchContexts,
|
||||
@ -16,7 +17,7 @@ vi.mock('../search/input/SearchNavbarItem', () => ({ SearchNavbarItem: () => 'Se
|
||||
vi.mock('../components/branding/BrandLogo', () => ({ BrandLogo: () => 'BrandLogo' }))
|
||||
|
||||
const PROPS: React.ComponentProps<typeof GlobalNavbar> = {
|
||||
authenticatedUser: null,
|
||||
authenticatedUser: { username: 'alice', organizations: { nodes: [] } } as Partial<AuthenticatedUser> as any,
|
||||
isSourcegraphDotCom: false,
|
||||
platformContext: {} as any,
|
||||
settingsCascade: NOOP_SETTINGS_CASCADE,
|
||||
@ -38,55 +39,146 @@ const PROPS: React.ComponentProps<typeof GlobalNavbar> = {
|
||||
codeMonitoringEnabled: true,
|
||||
ownEnabled: true,
|
||||
showFeedbackModal: () => undefined,
|
||||
__testing__isOpen: true,
|
||||
}
|
||||
|
||||
describe('GlobalNavbar', () => {
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
if (!window.context) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
window.context = {} as any
|
||||
}
|
||||
const origCodeSearchEnabledOnInstance = window.context?.codeSearchEnabledOnInstance ?? true
|
||||
const origCodyEnabledOnInstance = window.context?.codyEnabledOnInstance ?? true
|
||||
const origCodyEnabledForCurrentUser = window.context?.codyEnabledForCurrentUser ?? true
|
||||
const reset = () => {
|
||||
window.context.codeSearchEnabledOnInstance = origCodeSearchEnabledOnInstance
|
||||
window.context.codyEnabledOnInstance = origCodyEnabledOnInstance
|
||||
window.context.codyEnabledForCurrentUser = origCodyEnabledForCurrentUser
|
||||
}
|
||||
beforeEach(reset)
|
||||
afterEach(reset)
|
||||
|
||||
test('default', () => {
|
||||
vi.mock('../util/license', () => ({
|
||||
isCodeSearchOnlyLicense: () => false,
|
||||
isCodeSearchPlusCodyLicense: () => true,
|
||||
isCodyOnlyLicense: () => false,
|
||||
}))
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'dropdown',
|
||||
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('cody only license', () => {
|
||||
vi.mock('../util/license', () => ({
|
||||
isCodeSearchOnlyLicense: () => false,
|
||||
isCodeSearchPlusCodyLicense: () => false,
|
||||
isCodyOnlyLicense: () => true,
|
||||
}))
|
||||
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
test('dotcom unauthed', () => {
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
<GlobalNavbar {...PROPS} isSourcegraphDotCom={true} authenticatedUser={null} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'link',
|
||||
codyItemLink: '/cody',
|
||||
})
|
||||
})
|
||||
|
||||
test('code search only license', () => {
|
||||
vi.mock('../util/license', () => ({
|
||||
isCodeSearchOnlyLicense: () => true,
|
||||
isCodeSearchPlusCodyLicense: () => false,
|
||||
isCodyOnlyLicense: () => false,
|
||||
}))
|
||||
test('dotcom authed', () => {
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} isSourcegraphDotCom={true} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'dropdown',
|
||||
codyDropdownItems: ['Dashboard /cody/manage', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
test('enterprise cody enabled for user', () => {
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'dropdown',
|
||||
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('enterprise cody disabled for user', () => {
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'link',
|
||||
codyItemLink: '/cody/dashboard',
|
||||
})
|
||||
})
|
||||
|
||||
test('code search disabled on instance', () => {
|
||||
window.context.codeSearchEnabledOnInstance = false
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
|
||||
codyItemType: 'dropdown',
|
||||
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('cody disabled on instance', () => {
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = false
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<GlobalNavbar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
expect(baseElement.querySelector('a[href^="/cody"]')).toBeNull()
|
||||
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({ codyItemType: 'none' })
|
||||
})
|
||||
})
|
||||
|
||||
interface NavBarTestDescription {
|
||||
codyItemType: 'none' | 'link' | 'dropdown'
|
||||
codyItemLink?: string
|
||||
codyDropdownItems?: string[]
|
||||
}
|
||||
|
||||
function describeNavBar(baseElement: HTMLElement): NavBarTestDescription {
|
||||
const dropdownButton = baseElement.querySelector('button[aria-label="Show cody menu"]')
|
||||
if (dropdownButton) {
|
||||
const popover = baseElement.querySelector('[data-reach-menu-popover]')!
|
||||
return {
|
||||
codyItemType: 'dropdown',
|
||||
codyDropdownItems: [...popover.querySelectorAll('a')].map(
|
||||
item => `${item.textContent ?? ''} ${item.getAttribute('href') ?? ''}`
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const item = baseElement.querySelector<HTMLAnchorElement>('a[href^="/cody"]')
|
||||
return item
|
||||
? {
|
||||
codyItemType: 'link',
|
||||
codyItemLink: item?.getAttribute('href') ?? '',
|
||||
}
|
||||
: { codyItemType: 'none' }
|
||||
}
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import {
|
||||
type FC,
|
||||
type MutableRefObject,
|
||||
type SetStateAction,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type FC,
|
||||
type MutableRefObject,
|
||||
type SetStateAction,
|
||||
} from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import BarChartIcon from 'mdi-react/BarChartIcon'
|
||||
import MagnifyIcon from 'mdi-react/MagnifyIcon'
|
||||
import { type RouteObject, useLocation } from 'react-router-dom'
|
||||
import { useLocation, type RouteObject } from 'react-router-dom'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
|
||||
import { isMacPlatform } from '@sourcegraph/common'
|
||||
import { isDefined, isMacPlatform } from '@sourcegraph/common'
|
||||
import { shortcutDisplayName } from '@sourcegraph/shared/src/keyboardShortcuts'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { Settings } from '@sourcegraph/shared/src/schema/settings.schema'
|
||||
@ -46,7 +46,6 @@ import { SearchNavbarItem } from '../search/input/SearchNavbarItem'
|
||||
import { AccessRequestsGlobalNavItem } from '../site-admin/AccessRequestsPage/AccessRequestsGlobalNavItem'
|
||||
import { useDeveloperSettings, useNavbarQueryState } from '../stores'
|
||||
import { SvelteKitNavItem } from '../sveltekit/SvelteKitNavItem'
|
||||
import { isCodyOnlyLicense, isCodeSearchOnlyLicense } from '../util/license'
|
||||
|
||||
import { NavAction, NavActions, NavBar, NavGroup, NavItem, NavLink } from '.'
|
||||
import { NavDropdown, type NavDropdownItem } from './NavBar/NavDropdown'
|
||||
@ -77,6 +76,8 @@ export interface GlobalNavbarProps
|
||||
showFeedbackModal: () => void
|
||||
|
||||
setFuzzyFinderIsVisible: React.Dispatch<SetStateAction<boolean>>
|
||||
|
||||
__testing__isOpen?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,6 +134,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
notebooksEnabled,
|
||||
ownEnabled,
|
||||
showFeedbackModal,
|
||||
__testing__isOpen,
|
||||
...props
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
@ -141,7 +143,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
|
||||
const onNavbarQueryChange = useNavbarQueryState(state => state.setQueryState)
|
||||
const isLicensed = !!window.context?.licenseInfo
|
||||
const disableCodeSearchFeatures = isCodyOnlyLicense()
|
||||
const disableCodeSearchFeatures = !window.context?.codeSearchEnabledOnInstance
|
||||
// Search context management is still enabled on .com
|
||||
// but should not show in the navbar. Users can still
|
||||
// access this feature via the context dropdown.
|
||||
@ -196,6 +198,7 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
|
||||
showCodeInsights={codeInsights}
|
||||
routeMatch={routeMatch}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
__testing__isOpen={__testing__isOpen}
|
||||
/>
|
||||
|
||||
<NavActions>
|
||||
@ -287,6 +290,8 @@ export interface InlineNavigationPanelProps {
|
||||
/** A current react router route match */
|
||||
routeMatch?: string
|
||||
className?: string
|
||||
|
||||
__testing__isOpen?: boolean
|
||||
}
|
||||
|
||||
export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
@ -300,12 +305,11 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
isSourcegraphDotCom,
|
||||
routeMatch,
|
||||
className,
|
||||
__testing__isOpen,
|
||||
} = props
|
||||
|
||||
const navbarReference = useRef<HTMLDivElement | null>(null)
|
||||
const navLinkVariant = useCalculatedNavLinkVariant(navbarReference)
|
||||
const disableCodyFeatures = isCodeSearchOnlyLicense()
|
||||
const disableCodeSearchFeatures = isCodyOnlyLicense()
|
||||
|
||||
const searchNavBarItems = useMemo(() => {
|
||||
const items: (NavDropdownItem | false)[] = [
|
||||
@ -350,11 +354,14 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
</NavItem>
|
||||
)
|
||||
|
||||
const CodyLogoWrapper = (): JSX.Element => <CodyLogo withColor={routeMatch === `${PageRoutes.Cody}/*`} />
|
||||
const hideCodyDropdown = disableCodyFeatures || !props.authenticatedUser
|
||||
const codyNavigation = hideCodyDropdown ? (
|
||||
const CodyLogoWrapper = (): JSX.Element => <CodyLogo withColor={routeMatch?.startsWith('/cody/')} />
|
||||
const codyNavigation = !window.context?.codyEnabledOnInstance ? null : !window.context
|
||||
?.codyEnabledForCurrentUser ? (
|
||||
<NavItem icon={() => <CodyLogoWrapper />} key="cody">
|
||||
<NavLink variant={navLinkVariant} to={disableCodyFeatures ? PageRoutes.Cody : PageRoutes.CodyChat}>
|
||||
<NavLink
|
||||
variant={navLinkVariant}
|
||||
to={isSourcegraphDotCom ? PageRoutes.CodyRedirectToMarketingOrDashboard : PageRoutes.CodyDashboard}
|
||||
>
|
||||
Cody
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
@ -362,7 +369,7 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
<NavDropdown
|
||||
key="cody"
|
||||
toggleItem={{
|
||||
path: isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.Cody,
|
||||
path: '/cody/*',
|
||||
icon: () => <CodyLogoWrapper />,
|
||||
content: 'Cody',
|
||||
variant: navLinkVariant,
|
||||
@ -370,7 +377,7 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
routeMatch={routeMatch}
|
||||
items={[
|
||||
{
|
||||
path: isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.Cody,
|
||||
path: isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard,
|
||||
content: 'Dashboard',
|
||||
},
|
||||
{
|
||||
@ -379,12 +386,13 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
|
||||
},
|
||||
]}
|
||||
name="cody"
|
||||
__testing__isOpen={__testing__isOpen}
|
||||
/>
|
||||
)
|
||||
|
||||
let prioritizedLinks: JSX.Element[] = [searchNavigation, codyNavigation]
|
||||
let prioritizedLinks: JSX.Element[] = [searchNavigation, codyNavigation].filter(isDefined)
|
||||
|
||||
if (disableCodeSearchFeatures) {
|
||||
if (!window.context?.codeSearchEnabledOnInstance) {
|
||||
// This should be cheap considering there will only be two items in the array.
|
||||
prioritizedLinks = prioritizedLinks.reverse()
|
||||
}
|
||||
|
||||
@ -2,23 +2,22 @@ import React, { forwardRef, useContext } from 'react'
|
||||
|
||||
import { mdiMenu } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
import { type LinkProps, NavLink as RouterNavLink } from 'react-router-dom'
|
||||
import { NavLink as RouterNavLink, type LinkProps } from 'react-router-dom'
|
||||
|
||||
import {
|
||||
Link,
|
||||
Icon,
|
||||
H1,
|
||||
type ForwardReferenceComponent,
|
||||
VIEWPORT_SM,
|
||||
Icon,
|
||||
Link,
|
||||
Menu,
|
||||
MenuList,
|
||||
MenuButton,
|
||||
MenuLink,
|
||||
MenuList,
|
||||
VIEWPORT_SM,
|
||||
useMatchMedia,
|
||||
type ForwardReferenceComponent,
|
||||
} from '@sourcegraph/wildcard'
|
||||
|
||||
import { PageRoutes } from '../../routes.constants'
|
||||
import { isCodyOnlyLicense } from '../../util/license'
|
||||
|
||||
import navActionStyles from './NavAction.module.scss'
|
||||
import navBarStyles from './NavBar.module.scss'
|
||||
@ -52,7 +51,7 @@ export interface NavLinkProps extends NavItemProps, Pick<LinkProps, 'to'> {
|
||||
}
|
||||
|
||||
export const NavBar = forwardRef(function NavBar({ children, logo }, reference): JSX.Element {
|
||||
const logoUrl = isCodyOnlyLicense() ? PageRoutes.Cody : PageRoutes.Search
|
||||
const logoUrl = window.context?.codeSearchEnabledOnInstance ? PageRoutes.Search : PageRoutes.Cody
|
||||
return (
|
||||
<nav aria-label="Main" className={navBarStyles.navbar} ref={reference}>
|
||||
{logo && (
|
||||
|
||||
@ -4,7 +4,7 @@ import { mdiChevronDown, mdiChevronUp } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { Link, Menu, MenuButton, MenuLink, MenuList, EMPTY_RECTANGLE, Icon } from '@sourcegraph/wildcard'
|
||||
import { EMPTY_RECTANGLE, Icon, Link, Menu, MenuButton, MenuLink, MenuList } from '@sourcegraph/wildcard'
|
||||
|
||||
import { MobileNavGroupContext, NavItem, NavLink, type NavLinkProps } from '.'
|
||||
|
||||
@ -37,6 +37,8 @@ interface NavDropdownProps {
|
||||
routeMatch?: string
|
||||
/** The name of the dropdown to use for accessible labels */
|
||||
name: string
|
||||
|
||||
__testing__isOpen?: boolean
|
||||
}
|
||||
|
||||
export const NavDropdown: React.FunctionComponent<React.PropsWithChildren<NavDropdownProps>> = ({
|
||||
@ -45,6 +47,7 @@ export const NavDropdown: React.FunctionComponent<React.PropsWithChildren<NavDro
|
||||
items,
|
||||
routeMatch,
|
||||
name,
|
||||
__testing__isOpen,
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
const isItemSelected = useMemo(
|
||||
@ -101,7 +104,11 @@ export const NavDropdown: React.FunctionComponent<React.PropsWithChildren<NavDro
|
||||
</MenuButton>
|
||||
</div>
|
||||
|
||||
<MenuList className={styles.menuList} targetPadding={EMPTY_RECTANGLE}>
|
||||
<MenuList
|
||||
className={styles.menuList}
|
||||
targetPadding={EMPTY_RECTANGLE}
|
||||
isOpen={__testing__isOpen}
|
||||
>
|
||||
{homeItem && (
|
||||
<MenuLink as={Link} key={toggleItem.path} to={toggleItem.path}>
|
||||
{homeItem.content}
|
||||
|
||||
@ -2,7 +2,7 @@ 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, describe, expect, test } from 'vitest'
|
||||
|
||||
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'
|
||||
@ -11,12 +11,6 @@ import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'
|
||||
|
||||
import { UserNavItem, type UserNavItemProps } from './UserNavItem'
|
||||
|
||||
vi.mock('../util/license', () => ({
|
||||
isCodeSearchOnlyLicense: () => false,
|
||||
isCodeSearchPlusCodyLicense: () => true,
|
||||
isCodyOnlyLicense: () => false,
|
||||
}))
|
||||
|
||||
describe('UserNavItem', () => {
|
||||
beforeAll(() => {
|
||||
setLinkComponent(RouterLink)
|
||||
|
||||
@ -1,439 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`GlobalNavbar > code search only license 1`] = `
|
||||
<DocumentFragment>
|
||||
<nav
|
||||
aria-label="Main"
|
||||
class="navbar"
|
||||
>
|
||||
<h1
|
||||
class="h1 logo"
|
||||
>
|
||||
<a
|
||||
class="anchorLink d-flex align-items-center"
|
||||
href="/search"
|
||||
>
|
||||
BrandLogo
|
||||
</a>
|
||||
</h1>
|
||||
<hr
|
||||
aria-hidden="true"
|
||||
class="divider"
|
||||
/>
|
||||
<div
|
||||
class="menu list"
|
||||
>
|
||||
<ul
|
||||
class="list"
|
||||
>
|
||||
<li
|
||||
class="item wrapper"
|
||||
>
|
||||
<div
|
||||
class="link d-flex align-items-center p-0"
|
||||
data-test-active="false"
|
||||
data-test-id="/search"
|
||||
>
|
||||
<button
|
||||
aria-controls=""
|
||||
aria-haspopup="true"
|
||||
aria-label="Show search menu"
|
||||
class="btn itemFocusable button"
|
||||
data-reach-menu-button=""
|
||||
id="menu-button-test-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="itemFocusableContent"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Code Search
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="item"
|
||||
>
|
||||
<a
|
||||
class="link"
|
||||
href="/cody"
|
||||
>
|
||||
<span
|
||||
class="linkContent"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.9088 4C14.756 4 15.4429 4.69836 15.4429 5.55983V8.33286C15.4429 9.19433 14.756 9.89269 13.9088 9.89269C13.0615 9.89269 12.3747 9.19433 12.3747 8.33286V5.55983C12.3747 4.69836 13.0615 4 13.9088 4Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M4.19287 7.63942C4.19287 6.77795 4.87971 6.07959 5.72696 6.07959H8.45423C9.30148 6.07959 9.98832 6.77795 9.98832 7.63942C9.98832 8.50089 9.30148 9.19925 8.45423 9.19925H5.72696C4.87971 9.19925 4.19287 8.50089 4.19287 7.63942Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M17.5756 12.1801C18.1216 12.7075 18.1437 13.5851 17.625 14.1403L17.1423 14.6569C13.3654 18.6994 6.99777 18.5987 3.34628 14.4387C2.84481 13.8674 2.89377 12.9909 3.45565 12.481C4.01752 11.9711 4.87954 12.0209 5.38102 12.5922C7.97062 15.5424 12.4865 15.6139 15.1651 12.747L15.6477 12.2304C16.1664 11.6752 17.0296 11.6527 17.5756 12.1801Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Cody
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul
|
||||
class="actions"
|
||||
>
|
||||
<li
|
||||
class="action"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="anchorLink btn btnSecondary btnOutline btnSm mr-1"
|
||||
href="/sign-in?returnTo=/"
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div
|
||||
class="searchNavBar"
|
||||
>
|
||||
SearchNavbarItem
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`GlobalNavbar > cody only license 1`] = `
|
||||
<DocumentFragment>
|
||||
<nav
|
||||
aria-label="Main"
|
||||
class="navbar"
|
||||
>
|
||||
<h1
|
||||
class="h1 logo"
|
||||
>
|
||||
<a
|
||||
class="anchorLink d-flex align-items-center"
|
||||
href="/search"
|
||||
>
|
||||
BrandLogo
|
||||
</a>
|
||||
</h1>
|
||||
<hr
|
||||
aria-hidden="true"
|
||||
class="divider"
|
||||
/>
|
||||
<div
|
||||
class="menu list"
|
||||
>
|
||||
<ul
|
||||
class="list"
|
||||
>
|
||||
<li
|
||||
class="item wrapper"
|
||||
>
|
||||
<div
|
||||
class="link d-flex align-items-center p-0"
|
||||
data-test-active="false"
|
||||
data-test-id="/search"
|
||||
>
|
||||
<button
|
||||
aria-controls=""
|
||||
aria-haspopup="true"
|
||||
aria-label="Show search menu"
|
||||
class="btn itemFocusable button"
|
||||
data-reach-menu-button=""
|
||||
id="menu-button-test-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="itemFocusableContent"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Code Search
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="item"
|
||||
>
|
||||
<a
|
||||
class="link"
|
||||
href="/cody"
|
||||
>
|
||||
<span
|
||||
class="linkContent"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.9088 4C14.756 4 15.4429 4.69836 15.4429 5.55983V8.33286C15.4429 9.19433 14.756 9.89269 13.9088 9.89269C13.0615 9.89269 12.3747 9.19433 12.3747 8.33286V5.55983C12.3747 4.69836 13.0615 4 13.9088 4Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M4.19287 7.63942C4.19287 6.77795 4.87971 6.07959 5.72696 6.07959H8.45423C9.30148 6.07959 9.98832 6.77795 9.98832 7.63942C9.98832 8.50089 9.30148 9.19925 8.45423 9.19925H5.72696C4.87971 9.19925 4.19287 8.50089 4.19287 7.63942Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M17.5756 12.1801C18.1216 12.7075 18.1437 13.5851 17.625 14.1403L17.1423 14.6569C13.3654 18.6994 6.99777 18.5987 3.34628 14.4387C2.84481 13.8674 2.89377 12.9909 3.45565 12.481C4.01752 11.9711 4.87954 12.0209 5.38102 12.5922C7.97062 15.5424 12.4865 15.6139 15.1651 12.747L15.6477 12.2304C16.1664 11.6752 17.0296 11.6527 17.5756 12.1801Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Cody
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul
|
||||
class="actions"
|
||||
>
|
||||
<li
|
||||
class="action"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="anchorLink btn btnSecondary btnOutline btnSm mr-1"
|
||||
href="/sign-in?returnTo=/"
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div
|
||||
class="searchNavBar"
|
||||
>
|
||||
SearchNavbarItem
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`GlobalNavbar > default 1`] = `
|
||||
<DocumentFragment>
|
||||
<nav
|
||||
aria-label="Main"
|
||||
class="navbar"
|
||||
>
|
||||
<h1
|
||||
class="h1 logo"
|
||||
>
|
||||
<a
|
||||
class="anchorLink d-flex align-items-center"
|
||||
href="/search"
|
||||
>
|
||||
BrandLogo
|
||||
</a>
|
||||
</h1>
|
||||
<hr
|
||||
aria-hidden="true"
|
||||
class="divider"
|
||||
/>
|
||||
<div
|
||||
class="menu list"
|
||||
>
|
||||
<ul
|
||||
class="list"
|
||||
>
|
||||
<li
|
||||
class="item wrapper"
|
||||
>
|
||||
<div
|
||||
class="link d-flex align-items-center p-0"
|
||||
data-test-active="false"
|
||||
data-test-id="/search"
|
||||
>
|
||||
<button
|
||||
aria-controls=""
|
||||
aria-haspopup="true"
|
||||
aria-label="Show search menu"
|
||||
class="btn itemFocusable button"
|
||||
data-reach-menu-button=""
|
||||
id="menu-button-test-id"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="itemFocusableContent"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Code Search
|
||||
</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mdi-icon iconInline icon"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="item"
|
||||
>
|
||||
<a
|
||||
class="link"
|
||||
href="/cody"
|
||||
>
|
||||
<span
|
||||
class="linkContent"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13.9088 4C14.756 4 15.4429 4.69836 15.4429 5.55983V8.33286C15.4429 9.19433 14.756 9.89269 13.9088 9.89269C13.0615 9.89269 12.3747 9.19433 12.3747 8.33286V5.55983C12.3747 4.69836 13.0615 4 13.9088 4Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M4.19287 7.63942C4.19287 6.77795 4.87971 6.07959 5.72696 6.07959H8.45423C9.30148 6.07959 9.98832 6.77795 9.98832 7.63942C9.98832 8.50089 9.30148 9.19925 8.45423 9.19925H5.72696C4.87971 9.19925 4.19287 8.50089 4.19287 7.63942Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
<path
|
||||
d="M17.5756 12.1801C18.1216 12.7075 18.1437 13.5851 17.625 14.1403L17.1423 14.6569C13.3654 18.6994 6.99777 18.5987 3.34628 14.4387C2.84481 13.8674 2.89377 12.9909 3.45565 12.481C4.01752 11.9711 4.87954 12.0209 5.38102 12.5922C7.97062 15.5424 12.4865 15.6139 15.1651 12.747L15.6477 12.2304C16.1664 11.6752 17.0296 11.6527 17.5756 12.1801Z"
|
||||
fill="#a6b6d9"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="text iconIncluded"
|
||||
>
|
||||
Cody
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul
|
||||
class="actions"
|
||||
>
|
||||
<li
|
||||
class="action"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
class="anchorLink btn btnSecondary btnOutline btnSm mr-1"
|
||||
href="/sign-in?returnTo=/"
|
||||
>
|
||||
Sign in
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div
|
||||
class="searchNavBar"
|
||||
>
|
||||
SearchNavbarItem
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@ -13,6 +13,10 @@ import { NewGlobalNavigationBar } from './NewGlobalNavigationBar'
|
||||
const decorator: Decorator<GlobalNavbarProps> = Story => {
|
||||
updateJSContextBatchChangesLicense('full')
|
||||
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
return <WebStory>{() => <Story />}</WebStory>
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,155 @@
|
||||
import React from 'react'
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { AuthenticatedUser } from '@sourcegraph/shared/src/auth'
|
||||
import { MockedTestProvider } from '@sourcegraph/shared/src/testing/apollo'
|
||||
import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'
|
||||
|
||||
import { NewGlobalNavigationBar } from './NewGlobalNavigationBar'
|
||||
|
||||
vi.mock('../search/input/SearchNavbarItem', () => ({ SearchNavbarItem: () => 'SearchNavbarItem' }))
|
||||
vi.mock('../components/branding/BrandLogo', () => ({ BrandLogo: () => 'BrandLogo' }))
|
||||
|
||||
const PROPS: React.ComponentProps<typeof NewGlobalNavigationBar> = {
|
||||
authenticatedUser: { username: 'alice', organizations: { nodes: [] } } as Partial<AuthenticatedUser> as any,
|
||||
isSourcegraphDotCom: false,
|
||||
notebooksEnabled: false,
|
||||
searchContextsEnabled: true,
|
||||
codeMonitoringEnabled: true,
|
||||
batchChangesEnabled: true,
|
||||
codeInsightsEnabled: true,
|
||||
showSearchBox: true,
|
||||
showFeedbackModal: () => {},
|
||||
routes: [],
|
||||
__testing__initialSideMenuOpen: true,
|
||||
telemetryService: {} as any,
|
||||
telemetryRecorder: {} as any,
|
||||
}
|
||||
|
||||
describe('NewGlobalNavigationBar', () => {
|
||||
if (!window.context) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
window.context = {} as any
|
||||
}
|
||||
const origCodeSearchEnabledOnInstance = window.context?.codeSearchEnabledOnInstance ?? true
|
||||
const origCodyEnabledOnInstance = window.context?.codyEnabledOnInstance ?? true
|
||||
const origCodyEnabledForCurrentUser = window.context?.codyEnabledForCurrentUser ?? true
|
||||
const reset = () => {
|
||||
window.context.codeSearchEnabledOnInstance = origCodeSearchEnabledOnInstance
|
||||
window.context.codyEnabledOnInstance = origCodyEnabledOnInstance
|
||||
window.context.codyEnabledForCurrentUser = origCodyEnabledForCurrentUser
|
||||
}
|
||||
beforeEach(reset)
|
||||
afterEach(reset)
|
||||
|
||||
test('default', () => {
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('dotcom unauthed', () => {
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} isSourcegraphDotCom={true} authenticatedUser={null} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody'],
|
||||
})
|
||||
})
|
||||
|
||||
test('dotcom authed', () => {
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} isSourcegraphDotCom={true} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody/manage', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('enterprise cody enabled for user', () => {
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('enterprise cody disabled for user', () => {
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody/dashboard'],
|
||||
})
|
||||
})
|
||||
|
||||
test('code search disabled on instance', () => {
|
||||
window.context.codeSearchEnabledOnInstance = false
|
||||
window.context.codyEnabledOnInstance = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({
|
||||
codyItems: ['Cody /cody/dashboard', 'Web Chat /cody/chat'],
|
||||
})
|
||||
})
|
||||
|
||||
test('cody disabled on instance', () => {
|
||||
window.context.codeSearchEnabledOnInstance = true
|
||||
window.context.codyEnabledOnInstance = false
|
||||
window.context.codyEnabledForCurrentUser = false
|
||||
|
||||
const { baseElement } = renderWithBrandedContext(
|
||||
<MockedTestProvider>
|
||||
<NewGlobalNavigationBar {...PROPS} />
|
||||
</MockedTestProvider>
|
||||
)
|
||||
const sidebarElement = baseElement.querySelector<HTMLElement>('[data-reach-dialog-overlay]')!
|
||||
expect(sidebarElement.querySelector('a[href*="/cody"]')).toBeNull()
|
||||
expect(describeNavBarSideMenu(sidebarElement)).toEqual<NavBarTestDescription>({ codyItems: [] })
|
||||
})
|
||||
})
|
||||
|
||||
interface NavBarTestDescription {
|
||||
codyItems: string[]
|
||||
}
|
||||
|
||||
function describeNavBarSideMenu(sidebarElement: HTMLElement): NavBarTestDescription {
|
||||
return {
|
||||
codyItems: Array.from(sidebarElement.querySelectorAll<HTMLAnchorElement>('a[href^="/cody"]')).map(
|
||||
a => `${a.textContent?.trim() ?? ''} ${a.getAttribute('href') ?? ''}`
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ import { Button, ButtonLink, Icon, Link, Modal, ProductStatusBadge, Text } from
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import { BatchChangesIconNav } from '../../batches/icons'
|
||||
import { CodyProRoutes } from '../../cody/codyProRoutes'
|
||||
import { CodyLogo } from '../../cody/components/CodyLogo'
|
||||
import { BrandLogo } from '../../components/branding/BrandLogo'
|
||||
import { DeveloperSettingsGlobalNavItem } from '../../devsettings/DeveloperSettingsGlobalNavItem'
|
||||
@ -49,7 +50,11 @@ interface NewGlobalNavigationBar extends TelemetryProps, TelemetryV2Props {
|
||||
* New experimental global navigation bar with inline search bar and
|
||||
* dynamic navigation items.
|
||||
*/
|
||||
export const NewGlobalNavigationBar: FC<NewGlobalNavigationBar> = props => {
|
||||
export const NewGlobalNavigationBar: FC<
|
||||
NewGlobalNavigationBar & {
|
||||
__testing__initialSideMenuOpen?: boolean
|
||||
}
|
||||
> = props => {
|
||||
const {
|
||||
isSourcegraphDotCom,
|
||||
notebooksEnabled,
|
||||
@ -63,11 +68,12 @@ export const NewGlobalNavigationBar: FC<NewGlobalNavigationBar> = props => {
|
||||
showFeedbackModal,
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
__testing__initialSideMenuOpen,
|
||||
} = props
|
||||
|
||||
const isLightTheme = useIsLightTheme()
|
||||
const [params] = useSearchParams()
|
||||
const [isSideMenuOpen, setSideMenuOpen] = useState(false)
|
||||
const [isSideMenuOpen, setSideMenuOpen] = useState(__testing__initialSideMenuOpen ?? false)
|
||||
const routeMatch = useRoutesMatch(props.routes)
|
||||
|
||||
// Features enablement flags and conditions
|
||||
@ -146,7 +152,6 @@ export const NewGlobalNavigationBar: FC<NewGlobalNavigationBar> = props => {
|
||||
showBatchChanges={showBatchChanges}
|
||||
showCodeInsights={showCodeInsights}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
authenticatedUser={authenticatedUser}
|
||||
onClose={() => setSideMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
@ -311,7 +316,6 @@ interface SidebarNavigationProps {
|
||||
showBatchChanges: boolean
|
||||
showCodeInsights: boolean
|
||||
onClose: () => void
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
}
|
||||
|
||||
const SidebarNavigation: FC<SidebarNavigationProps> = props => {
|
||||
@ -323,7 +327,6 @@ const SidebarNavigation: FC<SidebarNavigationProps> = props => {
|
||||
showBatchChanges,
|
||||
showCodeInsights,
|
||||
isSourcegraphDotCom,
|
||||
authenticatedUser,
|
||||
onClose,
|
||||
} = props
|
||||
|
||||
@ -382,11 +385,23 @@ const SidebarNavigation: FC<SidebarNavigationProps> = props => {
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<NavItemLink url={PageRoutes.Cody} icon={CodyLogo} onClick={handleNavigationClick}>
|
||||
Cody
|
||||
</NavItemLink>
|
||||
{window.context?.codyEnabledOnInstance && (
|
||||
<NavItemLink
|
||||
url={
|
||||
isSourcegraphDotCom
|
||||
? window.context.codyEnabledForCurrentUser
|
||||
? CodyProRoutes.Manage
|
||||
: PageRoutes.CodyRedirectToMarketingOrDashboard
|
||||
: PageRoutes.CodyDashboard
|
||||
}
|
||||
icon={CodyLogo}
|
||||
onClick={handleNavigationClick}
|
||||
>
|
||||
Cody
|
||||
</NavItemLink>
|
||||
)}
|
||||
|
||||
{authenticatedUser && (
|
||||
{window.context?.codyEnabledForCurrentUser && (
|
||||
<ul className={classNames(styles.sidebarNavigationList, styles.sidebarNavigationListNested)}>
|
||||
<NavItemLink url={PageRoutes.CodyChat} onClick={handleNavigationClick}>
|
||||
Web Chat
|
||||
|
||||
@ -4,31 +4,31 @@ import type * as H from 'history'
|
||||
import AlertCircleIcon from 'mdi-react/AlertCircleIcon'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import { Route, Routes, type NavigateFunction } from 'react-router-dom'
|
||||
import { combineLatest, merge, type Observable, of, Subject, Subscription } from 'rxjs'
|
||||
import { Subject, Subscription, combineLatest, merge, of, type Observable } from 'rxjs'
|
||||
import { catchError, distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'
|
||||
|
||||
import { type ErrorLike, isErrorLike, asError, logger } from '@sourcegraph/common'
|
||||
import { gql, dataOrThrowErrors } from '@sourcegraph/http-client'
|
||||
import { asError, isErrorLike, logger, type ErrorLike } from '@sourcegraph/common'
|
||||
import { dataOrThrowErrors, gql } from '@sourcegraph/http-client'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import { type TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { LoadingSpinner, ErrorMessage } from '@sourcegraph/wildcard'
|
||||
import { ErrorMessage, LoadingSpinner } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import { requestGraphQL } from '../../backend/graphql'
|
||||
import type { BatchChangesProps } from '../../batches'
|
||||
import type { BreadcrumbsProps, BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import type { BreadcrumbSetters, BreadcrumbsProps } from '../../components/Breadcrumbs'
|
||||
import { RouteError } from '../../components/ErrorBoundary'
|
||||
import { HeroPage } from '../../components/HeroPage'
|
||||
import { Page } from '../../components/Page'
|
||||
import type { OrganizationResult, OrganizationVariables, OrgAreaOrganizationFields } from '../../graphql-operations'
|
||||
import type { OrgAreaOrganizationFields, OrganizationResult, OrganizationVariables } from '../../graphql-operations'
|
||||
import type { NamespaceProps } from '../../namespaces'
|
||||
import type { RouteV6Descriptor } from '../../util/contributions'
|
||||
import type { OrgSettingsAreaRoute } from '../settings/OrgSettingsArea'
|
||||
import type { OrgSettingsSidebarItems } from '../settings/OrgSettingsSidebar'
|
||||
|
||||
import { type OrgAreaHeaderNavItem, OrgHeader } from './OrgHeader'
|
||||
import { OrgHeader, type OrgAreaHeaderNavItem } from './OrgHeader'
|
||||
import { OrgInvitationPageLegacy } from './OrgInvitationPageLegacy'
|
||||
|
||||
function queryOrganization(args: { name: string }): Observable<OrgAreaOrganizationFields> {
|
||||
@ -138,11 +138,6 @@ export interface OrgAreaRouteContext
|
||||
|
||||
orgSettingsSideBarItems: OrgSettingsSidebarItems
|
||||
orgSettingsAreaRoutes: readonly OrgSettingsAreaRoute[]
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -249,10 +244,6 @@ export class OrgArea extends React.Component<OrgAreaProps> {
|
||||
useBreadcrumb: this.state.useBreadcrumb,
|
||||
orgSettingsAreaRoutes: this.props.orgSettingsAreaRoutes,
|
||||
orgSettingsSideBarItems: this.props.orgSettingsSideBarItems,
|
||||
license: {
|
||||
isCodeSearchEnabled: Boolean(window.context.licenseInfo?.features.codeSearch),
|
||||
isCodyEnabled: Boolean(window.context.licenseInfo?.features.cody),
|
||||
},
|
||||
}
|
||||
|
||||
if (this.props.location.pathname === `/organizations/${this.props.orgName}/invitation`) {
|
||||
|
||||
@ -2,11 +2,10 @@ import React from 'react'
|
||||
|
||||
import { NavLink } from 'react-router-dom'
|
||||
|
||||
import { PageHeader, Button, Link, Icon } from '@sourcegraph/wildcard'
|
||||
import { Button, Icon, Link, PageHeader } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { BatchChangesProps } from '../../batches'
|
||||
import type { NavItemWithIconDescriptor } from '../../util/contributions'
|
||||
import { getLicenseFeatures } from '../../util/license'
|
||||
import { OrgAvatar } from '../OrgAvatar'
|
||||
|
||||
import type { OrgAreaRouteContext } from './OrgArea'
|
||||
@ -24,11 +23,6 @@ export interface OrgSummary {
|
||||
|
||||
export interface OrgAreaHeaderContext extends BatchChangesProps, Pick<Props, 'org'> {
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface OrgAreaHeaderNavItem extends NavItemWithIconDescriptor<OrgAreaHeaderContext> {}
|
||||
@ -51,7 +45,6 @@ export const OrgHeader: React.FunctionComponent<React.PropsWithChildren<Props>>
|
||||
batchChangesWebhookLogsEnabled,
|
||||
org,
|
||||
isSourcegraphDotCom,
|
||||
license: getLicenseFeatures(),
|
||||
}
|
||||
|
||||
const url = `/organizations/${org.name}`
|
||||
|
||||
@ -16,7 +16,8 @@ export const orgAreaHeaderNavItems: readonly OrgAreaHeaderNavItem[] = [
|
||||
to: '/searches',
|
||||
label: 'Saved searches',
|
||||
icon: FeatureSearchOutlineIcon,
|
||||
condition: ({ org: { viewerCanAdminister }, license }) => license.isCodeSearchEnabled && viewerCanAdminister,
|
||||
condition: ({ org: { viewerCanAdminister } }) =>
|
||||
viewerCanAdminister && window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
...namespaceAreaHeaderNavItems,
|
||||
]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { type FC } from 'react'
|
||||
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
import { LoadingSpinner } from '@sourcegraph/wildcard'
|
||||
|
||||
@ -11,7 +11,6 @@ import { RouteError } from '../../components/ErrorBoundary'
|
||||
import { HeroPage } from '../../components/HeroPage'
|
||||
import type { OrgAreaOrganizationFields } from '../../graphql-operations'
|
||||
import type { RouteV6Descriptor } from '../../util/contributions'
|
||||
import { getLicenseFeatures } from '../../util/license'
|
||||
import type { OrgAreaRouteContext } from '../area/OrgArea'
|
||||
|
||||
import { OrgSettingsSidebar, type OrgSettingsSidebarItems } from './OrgSettingsSidebar'
|
||||
@ -35,11 +34,6 @@ export interface OrgSettingsAreaProps extends OrgAreaRouteContext {
|
||||
|
||||
export interface OrgSettingsAreaRouteContext extends OrgSettingsAreaProps {
|
||||
org: OrgAreaOrganizationFields
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,7 +43,6 @@ export interface OrgSettingsAreaRouteContext extends OrgSettingsAreaProps {
|
||||
const AuthenticatedOrgSettingsArea: FC<OrgSettingsAreaProps> = props => {
|
||||
const context: OrgSettingsAreaRouteContext = {
|
||||
...props,
|
||||
license: getLicenseFeatures(),
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -38,7 +38,8 @@ export const orgSettingsAreaRoutes: readonly OrgSettingsAreaRoute[] = [
|
||||
telemetryRecorder={props.platformContext.telemetryRecorder}
|
||||
/>
|
||||
),
|
||||
condition: ({ org: { viewerCanAdminister }, license }) => license.isCodeSearchEnabled && viewerCanAdminister,
|
||||
condition: ({ org: { viewerCanAdminister } }) =>
|
||||
viewerCanAdminister && window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import React, {
|
||||
createContext,
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
type RefObject,
|
||||
Suspense,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
type RefObject,
|
||||
} from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { escapeRegExp } from 'lodash'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { type Location, useLocation, Route, Routes } from 'react-router-dom'
|
||||
import { Route, Routes, useLocation, type Location } from 'react-router-dom'
|
||||
import { NEVER, of } from 'rxjs'
|
||||
import { catchError, switchMap } from 'rxjs/operators'
|
||||
|
||||
import type { StreamingSearchResultsListProps } from '@sourcegraph/branded'
|
||||
import { asError, type ErrorLike, isErrorLike, repeatUntil } from '@sourcegraph/common'
|
||||
import { asError, isErrorLike, repeatUntil, type ErrorLike } from '@sourcegraph/common'
|
||||
import {
|
||||
isCloneInProgressErrorLike,
|
||||
isRepoSeeOtherErrorLike,
|
||||
@ -42,7 +42,7 @@ import type { BatchChangesProps } from '../batches'
|
||||
import type { CodeIntelligenceProps } from '../codeintel'
|
||||
import { RepoContainerEditor } from '../cody/components/RepoContainerEditor'
|
||||
import { CodySidebar } from '../cody/sidebar'
|
||||
import { useCodySidebar, useSidebarSize, CODY_SIDEBAR_SIZES } from '../cody/sidebar/Provider'
|
||||
import { CODY_SIDEBAR_SIZES, useCodySidebar, useSidebarSize } from '../cody/sidebar/Provider'
|
||||
import { useCodyIgnore } from '../cody/useCodyIgnore'
|
||||
import type { BreadcrumbSetters, BreadcrumbsProps } from '../components/Breadcrumbs'
|
||||
import { RouteError } from '../components/ErrorBoundary'
|
||||
@ -56,11 +56,10 @@ import { useV2QueryInput } from '../search/useV2QueryInput'
|
||||
import { useNavbarQueryState } from '../stores'
|
||||
import { EventName } from '../util/constants'
|
||||
import type { RouteV6Descriptor } from '../util/contributions'
|
||||
import { getLicenseFeatures } from '../util/license'
|
||||
import { parseBrowserRepoURL } from '../util/url'
|
||||
|
||||
import { GoToCodeHostAction } from './actions/GoToCodeHostAction'
|
||||
import { fetchFileExternalLinks, type ResolvedRevision, resolveRepoRevision, type Repo } from './backend'
|
||||
import { fetchFileExternalLinks, resolveRepoRevision, type Repo, type ResolvedRevision } from './backend'
|
||||
import { AskCodyButton } from './cody/AskCodyButton'
|
||||
import { RepoContainerError } from './RepoContainerError'
|
||||
import { RepoHeader, type RepoHeaderContributionsLifecycleProps } from './RepoHeader'
|
||||
@ -436,8 +435,7 @@ const RepoUserContainer: FC<RepoUserContainerProps> = ({
|
||||
|
||||
// must exactly match how the revision was encoded in the URL
|
||||
const repoNameAndRevision = `${repoName}${typeof rawRevision === 'string' ? `@${rawRevision}` : ''}`
|
||||
const licenseFeatures = getLicenseFeatures()
|
||||
const showAskCodyBtn = licenseFeatures.isCodyEnabled && !isRepoIgnored(repoName) && !isCodySidebarOpen
|
||||
const showAskCodyBtn = window.context?.codyEnabledForCurrentUser && !isRepoIgnored(repoName) && !isCodySidebarOpen
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -14,7 +14,7 @@ import type { Optional } from 'utility-types'
|
||||
import type { StreamingSearchResultsListProps } from '@sourcegraph/branded'
|
||||
import { TabbedPanelContent } from '@sourcegraph/branded/src/components/panel/TabbedPanelContent'
|
||||
import { NoopEditor } from '@sourcegraph/cody-shared'
|
||||
import { asError, type ErrorLike, isErrorLike, basename, SourcegraphURL } from '@sourcegraph/common'
|
||||
import { asError, basename, isErrorLike, SourcegraphURL, type ErrorLike } from '@sourcegraph/common'
|
||||
import {
|
||||
createActiveSpan,
|
||||
reactManualTracer,
|
||||
@ -25,7 +25,7 @@ import type { FetchFileParameters } from '@sourcegraph/shared/src/backend/file'
|
||||
import { HighlightResponseFormat } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SearchContextProps } from '@sourcegraph/shared/src/search'
|
||||
import { type SettingsCascadeProps, useExperimentalFeatures } from '@sourcegraph/shared/src/settings/settings'
|
||||
import { useExperimentalFeatures, type SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
@ -49,7 +49,6 @@ import {
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import type { CodeIntelligenceProps } from '../../codeintel'
|
||||
import { FileContentEditor } from '../../cody/components/FileContentEditor'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import { useCodySidebar } from '../../cody/sidebar/Provider'
|
||||
import type { BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import { HeroPage } from '../../components/HeroPage'
|
||||
@ -68,7 +67,6 @@ import { parseBrowserRepoURL, toTreeURL } from '../../util/url'
|
||||
import { serviceKindDisplayNameAndIcon } from '../actions/GoToCodeHostAction'
|
||||
import { ToggleBlameAction } from '../actions/ToggleBlameAction'
|
||||
import { useBlameHunks, useBlameVisibility } from '../blame/hooks'
|
||||
import { TryCodyWidget } from '../components/TryCodyWidget/TryCodyWidget'
|
||||
import { FilePathBreadcrumbs } from '../FilePathBreadcrumbs'
|
||||
import { isPackageServiceType } from '../packages/isPackageServiceType'
|
||||
import type { HoverThresholdProps } from '../RepoContainer'
|
||||
@ -371,16 +369,6 @@ export const BlobPage: React.FunctionComponent<BlobPageProps> = ({ className, co
|
||||
const alwaysRender = (
|
||||
<>
|
||||
<PageTitle title={getPageTitle()} />
|
||||
{(props.isSourcegraphDotCom || isCodyEnabled()) && (
|
||||
<TryCodyWidget
|
||||
telemetryService={props.telemetryService}
|
||||
telemetryRecorder={props.telemetryRecorder}
|
||||
type="blob"
|
||||
authenticatedUser={props.authenticatedUser}
|
||||
context={context}
|
||||
isSourcegraphDotCom={props.isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
{window.context.isAuthenticatedUser && (
|
||||
<RepoHeaderContributionPortal
|
||||
position="right"
|
||||
|
||||
@ -22,7 +22,7 @@ import { useKeyboardShortcut } from '@sourcegraph/shared/src/keyboardShortcuts/u
|
||||
import { Shortcut } from '@sourcegraph/shared/src/react-shortcuts'
|
||||
import { useSettings } from '@sourcegraph/shared/src/settings/settings'
|
||||
import type { TemporarySettingsSchema } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings'
|
||||
import { type TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
import { codeCopiedEvent } from '@sourcegraph/shared/src/tracking/event-log-creators'
|
||||
@ -35,7 +35,6 @@ import {
|
||||
import { useLocalStorage } from '@sourcegraph/wildcard'
|
||||
|
||||
import { CodeMirrorEditor } from '../../cody/components/CodeMirrorEditor'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import { useCodySidebar } from '../../cody/sidebar/Provider'
|
||||
import { useCodyIgnore } from '../../cody/useCodyIgnore'
|
||||
import { useFeatureFlag } from '../../featureFlags/useFeatureFlag'
|
||||
@ -340,7 +339,8 @@ export const CodeMirrorBlob: React.FunctionComponent<BlobProps> = props => {
|
||||
)
|
||||
|
||||
const { isFileIgnored } = useCodyIgnore()
|
||||
const isCodyEnabledForFile = isCodyEnabled() && !isFileIgnored(blobInfo.repoName, blobInfo.filePath)
|
||||
const isCodyEnabledForFile =
|
||||
window.context?.codyEnabledForCurrentUser && !isFileIgnored(blobInfo.repoName, blobInfo.filePath)
|
||||
|
||||
const extensions = useMemo(
|
||||
() => [
|
||||
|
||||
@ -1,129 +0,0 @@
|
||||
@import 'wildcard/src/global-styles/breakpoints';
|
||||
|
||||
:global(.theme-dark) {
|
||||
--cody-blob-bg-gradient: linear-gradient(339.95deg, #15171e -44.65%, #49169d 15.56%, #5e398c 88.23%);
|
||||
--cody-repo-bg-gradient: linear-gradient(339.95deg, #15171e -44.65%, #49169d -16.82%, #1e212e 88.23%), #ffffff;
|
||||
--cody-repo-border: var(--gray-09);
|
||||
}
|
||||
|
||||
:global(.theme-light) {
|
||||
--cody-blob-bg-gradient: linear-gradient(339.95deg, #ffffff -44.65%, #d0b9f5 -16.82%, rgba(255, 255, 255, 0) 88.23%),
|
||||
#ffffff;
|
||||
--cody-repo-bg-gradient: var(--cody-blob-bg-gradient);
|
||||
--cody-repo-border: var(--gray-03);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.repo-card-wrapper {
|
||||
padding: 0.065rem !important;
|
||||
background: var(--cody-repo-border);
|
||||
|
||||
.card {
|
||||
background: var(--cody-repo-bg-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.blob-card-wrapper {
|
||||
padding: 0.065rem !important;
|
||||
|
||||
.card {
|
||||
background: var(--cody-blob-bg-gradient);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding-top: 0.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: var(--body-color);
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5rem;
|
||||
margin-bottom: 0.6875rem;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
font-size: 1rem;
|
||||
line-height: 1.25rem;
|
||||
color: var(--body-color);
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.card-images {
|
||||
width: 100%;
|
||||
align-items: self-end;
|
||||
|
||||
@media (--md-breakpoint-down) {
|
||||
width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.card-image {
|
||||
max-width: 35.688rem;
|
||||
max-height: 7.438rem;
|
||||
}
|
||||
|
||||
.no-auth-card {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background: var(--cody-bg-gradient);
|
||||
padding-right: 0.75rem;
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1rem;
|
||||
padding-left: 1.875rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
margin-bottom: 0.75rem;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
.auth-buttons-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.813rem;
|
||||
}
|
||||
|
||||
.terms-link {
|
||||
color: var(--cody-link-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.auth-button,
|
||||
.email-auth-button {
|
||||
text-align: center;
|
||||
border: none;
|
||||
max-width: 11.938rem;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 0.188rem;
|
||||
margin-bottom: 0.313rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.auth-button {
|
||||
background-color: var(--white) !important;
|
||||
color: var(--black) !important;
|
||||
}
|
||||
|
||||
.email-auth-button {
|
||||
background-color: var(--white);
|
||||
color: var(--body-color) !important;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
svg {
|
||||
color: var(--email-icon-color);
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
:global(.theme-dark) & {
|
||||
color: var(--gray-08) !important;
|
||||
}
|
||||
}
|
||||
@ -1,242 +0,0 @@
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
|
||||
import { mdiClose } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
import { Button, H2, H4, Icon, Link, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../../auth'
|
||||
import { ExternalsAuth } from '../../../auth/components/ExternalsAuth'
|
||||
import { MarketingBlock } from '../../../components/MarketingBlock'
|
||||
import type { SourcegraphContext } from '../../../jscontext'
|
||||
import { EventName } from '../../../util/constants'
|
||||
|
||||
import { GlowingCodySVG, MeetCodySVG } from './WidgetIcons'
|
||||
|
||||
import styles from './TryCodyWidget.module.scss'
|
||||
|
||||
const AUTO_DISMISS_ON_EVENTS = new Set([EventName.CODY_SIDEBAR_CHAT_OPENED, EventName.CODY_CHAT_SUBMIT])
|
||||
|
||||
interface WidgetContentProps extends TelemetryProps, TelemetryV2Props {
|
||||
type: 'blob' | 'repo'
|
||||
theme?: 'light' | 'dark'
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
interface NoAuhWidgetContentProps extends WidgetContentProps {
|
||||
context: Pick<SourcegraphContext, 'externalURL'>
|
||||
}
|
||||
|
||||
function useTryCodyWidget(telemetryService: TelemetryProps['telemetryService']): {
|
||||
isDismissed: boolean | undefined
|
||||
onDismiss: () => void
|
||||
} {
|
||||
// `isDismissed = true` maintain the initial concealment of the CTA when loading the settings
|
||||
const [isDismissed = true, setIsDismissed] = useTemporarySetting('cody.blobPageCta.dismissed', false)
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
setIsDismissed(true)
|
||||
}, [setIsDismissed])
|
||||
|
||||
// Listen for telemetry events to auto dismiss the widget
|
||||
useEffect(() => {
|
||||
if (isDismissed) {
|
||||
return
|
||||
}
|
||||
|
||||
return telemetryService.addEventLogListener?.(eventName => {
|
||||
if (AUTO_DISMISS_ON_EVENTS.has(eventName as EventName)) {
|
||||
onDismiss()
|
||||
}
|
||||
})
|
||||
}, [telemetryService, isDismissed, onDismiss])
|
||||
|
||||
return { isDismissed, onDismiss }
|
||||
}
|
||||
|
||||
const NoAuthWidgetContent: React.FC<NoAuhWidgetContentProps> = ({
|
||||
type,
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
context,
|
||||
}) => {
|
||||
const title = type === 'blob' ? 'Sign up to get Cody, our AI assistant, free' : 'Meet Cody, your AI assistant'
|
||||
const eventPage = type === 'blob' ? 'try-cody-widget-blob' : 'try-cody-widget-repo'
|
||||
|
||||
return (
|
||||
<>
|
||||
<MeetCodySVG />
|
||||
<div className="flex-grow-1">
|
||||
<H2 className={styles.cardTitle}>{title}</H2>
|
||||
<Text className={styles.cardDescription}>
|
||||
Cody combines an LLM with the context of Sourcegraph's code graph on public code or your code at
|
||||
work.{' '}
|
||||
</Text>
|
||||
<div className={styles.authButtonsWrap}>
|
||||
<ExternalsAuth
|
||||
page={eventPage}
|
||||
context={context}
|
||||
githubLabel="GitHub"
|
||||
gitlabLabel="GitLab"
|
||||
googleLabel="Google"
|
||||
withCenteredText={true}
|
||||
onClick={() => {}}
|
||||
ctaClassName={styles.authButton}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
telemetryService={telemetryService}
|
||||
/>
|
||||
</div>
|
||||
<Text className="mb-2 mt-2">
|
||||
By registering, you agree to our{' '}
|
||||
<Link
|
||||
to="https://sourcegraph.com/terms"
|
||||
className={styles.termsLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
to="https://sourcegraph.com/terms/privacy"
|
||||
className={styles.termsLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</Text>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const AuthUserWidgetContent: React.FC<WidgetContentProps> = ({ type, theme, isSourcegraphDotCom }) => {
|
||||
const { title, useCases, image } = isSourcegraphDotCom
|
||||
? type === 'blob'
|
||||
? {
|
||||
title: 'Try Cody on public code',
|
||||
useCases: ['Select code in the file below', 'Select an action with Cody widget'],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-action-bar-${theme}.png`,
|
||||
}
|
||||
: {
|
||||
title: 'Try Cody on this repository',
|
||||
useCases: [
|
||||
'Click the Ask Cody button above and to the right of this banner',
|
||||
'Ask Cody a question like “Explain the structure of this repository”',
|
||||
],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-chat-banner-image-${theme}.png`,
|
||||
}
|
||||
: type === 'blob'
|
||||
? {
|
||||
title: 'Try Cody on this file',
|
||||
useCases: ['Select code in the file below', 'Select an action with Cody widget'],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-action-bar-${theme}.png`,
|
||||
}
|
||||
: {
|
||||
title: 'Try Cody on this repository',
|
||||
useCases: [
|
||||
'Click the Ask Cody button above and to the right of this banner',
|
||||
'Ask Cody a question like “Explain the structure of this repository”',
|
||||
],
|
||||
image: `https://storage.googleapis.com/sourcegraph-assets/app-images/cody-chat-banner-image-${theme}.png`,
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex pb-3">
|
||||
<GlowingCodySVG />
|
||||
<div className="d-flex flex-column flex-grow-1 justify-content-center flex-shrink-0">
|
||||
<H4 as="h2" className={styles.cardTitle}>
|
||||
{title}
|
||||
</H4>
|
||||
<ol className={classNames('m-0 pl-4', styles.cardList)}>
|
||||
{useCases.map(useCase => (
|
||||
<Text key={useCase} as="li">
|
||||
{useCase}
|
||||
</Text>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames('d-flex justify-content-center', styles.cardImages)}>
|
||||
<img src={image} alt="Cody" className={classNames(styles.cardImage, 'percy-hide')} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface TryCodyWidgetProps extends TelemetryProps, TelemetryV2Props {
|
||||
className?: string
|
||||
type: 'blob' | 'repo'
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
context: Pick<SourcegraphContext, 'externalURL'>
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
export const TryCodyWidget: React.FC<TryCodyWidgetProps> = ({
|
||||
className,
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
authenticatedUser,
|
||||
context,
|
||||
type,
|
||||
isSourcegraphDotCom,
|
||||
}) => {
|
||||
const isLightTheme = useIsLightTheme()
|
||||
const { isDismissed, onDismiss } = useTryCodyWidget(telemetryService)
|
||||
useEffect(() => {
|
||||
if (isDismissed) {
|
||||
return
|
||||
}
|
||||
const eventPage = type === 'blob' ? 'BlobPage' : 'RepoPage'
|
||||
telemetryService.log(EventName.TRY_CODY_WEB_ONBOARDING_DISPLAYED, { type: eventPage }, { type: eventPage })
|
||||
const v2EventPage = type === 'blob' ? 0 : 1
|
||||
telemetryRecorder.recordEvent('cta.tryCodyWebOnboarding', 'view', { metadata: { page: v2EventPage } })
|
||||
}, [isDismissed, telemetryService, telemetryRecorder, type])
|
||||
|
||||
if (isDismissed) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<MarketingBlock
|
||||
wrapperClassName={classNames(
|
||||
className,
|
||||
type === 'blob' ? styles.blobCardWrapper : styles.repoCardWrapper,
|
||||
'mb-2'
|
||||
)}
|
||||
contentClassName={classNames(
|
||||
'd-flex position-relative pb-0 overflow-auto justify-content-between',
|
||||
styles.card,
|
||||
!authenticatedUser && styles.noAuthCard
|
||||
)}
|
||||
variant="thin"
|
||||
>
|
||||
{authenticatedUser ? (
|
||||
<AuthUserWidgetContent
|
||||
type={type}
|
||||
theme={isLightTheme ? 'light' : 'dark'}
|
||||
telemetryService={telemetryService}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
) : (
|
||||
<NoAuthWidgetContent
|
||||
telemetryService={telemetryService}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
type={type}
|
||||
context={context}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
<Button className={classNames(styles.closeButton, 'position-absolute mt-2')} onClick={onDismiss}>
|
||||
<Icon svgPath={mdiClose} aria-label="Close try Cody widget" />
|
||||
</Button>
|
||||
</MarketingBlock>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -8,10 +8,9 @@ import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry'
|
||||
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing'
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import { type RepositoryFields, RepositoryType } from '../../graphql-operations'
|
||||
import { RepositoryType, type RepositoryFields } from '../../graphql-operations'
|
||||
|
||||
import { type Props, TreePage } from './TreePage'
|
||||
import { TreePage, type Props } from './TreePage'
|
||||
|
||||
// TreePage has a dependency on the `perforceChangelistMapping` experimental feature
|
||||
// in order to build an appropriately-worded Commits button.
|
||||
@ -156,32 +155,5 @@ describe('TreePage', () => {
|
||||
)
|
||||
expect(result.queryByTestId('repo-fork-badge')).toHaveTextContent('Fork')
|
||||
})
|
||||
|
||||
it('Should display cody CTA', () => {
|
||||
const repo = repoDefaults()
|
||||
const props = treePagePropsDefaults(repo)
|
||||
window.context = window.context || {}
|
||||
window.context.codyEnabled = true
|
||||
window.context.codyEnabledForCurrentUser = true
|
||||
|
||||
const mockUser = {
|
||||
id: 'userID',
|
||||
username: 'username',
|
||||
emails: [{ email: 'user@me.com', isPrimary: true, verified: true }],
|
||||
siteAdmin: true,
|
||||
} as AuthenticatedUser
|
||||
|
||||
renderWithBrandedContext(
|
||||
<MockedProvider>
|
||||
<TreePage {...{ ...props, isSourcegraphDotCom: true, authenticatedUser: mockUser }} />
|
||||
</MockedProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Try Cody on this repository')).toBeVisible()
|
||||
expect(screen.getByText('Click the Ask Cody button above and to the right of this banner')).toBeVisible()
|
||||
expect(
|
||||
screen.getByText('Ask Cody a question like “Explain the structure of this repository”')
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -46,13 +46,11 @@ import type { AuthenticatedUser } from '../../auth'
|
||||
import type { BatchChangesProps } from '../../batches'
|
||||
import { RepoBatchChangesButton } from '../../batches/RepoBatchChangesButton'
|
||||
import type { CodeIntelligenceProps } from '../../codeintel'
|
||||
import { isCodyEnabled } from '../../cody/isCodyEnabled'
|
||||
import type { BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import { PageTitle } from '../../components/PageTitle'
|
||||
import type { FileCommitsResult, FileCommitsVariables, RepositoryFields } from '../../graphql-operations'
|
||||
import type { SourcegraphContext } from '../../jscontext'
|
||||
import type { OwnConfigProps } from '../../own/OwnConfigProps'
|
||||
import { TryCodyWidget } from '../components/TryCodyWidget/TryCodyWidget'
|
||||
import { FilePathBreadcrumbs } from '../FilePathBreadcrumbs'
|
||||
import { isPackageServiceType } from '../packages/isPackageServiceType'
|
||||
import { RepoCommitsButton } from '../utils'
|
||||
@ -350,17 +348,6 @@ export const TreePage: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.treePage, className)}>
|
||||
{(isSourcegraphDotCom || isCodyEnabled()) && (
|
||||
<TryCodyWidget
|
||||
className="mb-2"
|
||||
telemetryService={props.telemetryService}
|
||||
telemetryRecorder={props.telemetryRecorder}
|
||||
type="repo"
|
||||
authenticatedUser={authenticatedUser}
|
||||
context={context}
|
||||
isSourcegraphDotCom={isSourcegraphDotCom}
|
||||
/>
|
||||
)}
|
||||
<Container className={styles.container}>
|
||||
<div className={classNames(styles.header)}>
|
||||
<PageTitle title={getPageTitle()} />
|
||||
|
||||
@ -35,6 +35,8 @@ export enum PageRoutes {
|
||||
Notebooks = '/notebooks',
|
||||
SearchNotebook = '/search/notebook',
|
||||
Cody = '/cody',
|
||||
CodyDashboard = '/cody/dashboard',
|
||||
CodyRedirectToMarketingOrDashboard = '/cody',
|
||||
CodyChat = '/cody/chat',
|
||||
CodySwitchAccount = '/cody/switch-account/:username',
|
||||
Own = '/own',
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { Navigate, useNavigate, type RouteObject } from 'react-router-dom'
|
||||
import { Navigate, type RouteObject } from 'react-router-dom'
|
||||
|
||||
import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent'
|
||||
|
||||
import { codyProRoutes } from './cody/codyProRoutes'
|
||||
import { codyRoutes } from './cody/codyRoutes'
|
||||
import { communitySearchContextsRoutes } from './communitySearchContexts/routes'
|
||||
import { LegacyRoute, type LegacyLayoutRouteContext } from './LegacyRouteContext'
|
||||
import { PageRoutes } from './routes.constants'
|
||||
@ -60,13 +61,6 @@ const SearchContextPage = lazyComponent(
|
||||
)
|
||||
const SearchUpsellPage = lazyComponent(() => import('./search/upsell/SearchUpsellPage'), 'SearchUpsellPage')
|
||||
const SearchPageWrapper = lazyComponent(() => import('./search/SearchPageWrapper'), 'SearchPageWrapper')
|
||||
const CodyChatPage = lazyComponent(() => import('./cody/chat/CodyChatPage'), 'CodyChatPage')
|
||||
const CodySwitchAccountPage = lazyComponent(
|
||||
() => import('./cody/switch-account/CodySwitchAccountPage'),
|
||||
'CodySwitchAccountPage'
|
||||
)
|
||||
const CodyUpsellPage = lazyComponent(() => import('./cody/upsell/CodyUpsellPage'), 'CodyUpsellPage')
|
||||
const CodyDashboardPage = lazyComponent(() => import('./cody/dashboard/CodyDashboardPage'), 'CodyDashboardPage')
|
||||
const SearchJob = lazyComponent(() => import('./enterprise/search-jobs/SearchJobsPage'), 'SearchJobsPage')
|
||||
|
||||
const Index = lazyComponent(() => import('./Index'), 'IndexPage')
|
||||
@ -153,8 +147,8 @@ export const routes: RouteObject[] = [
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <GlobalCodeMonitoringArea {...props} />}
|
||||
condition={({ isSourcegraphDotCom, licenseFeatures }) =>
|
||||
!isSourcegraphDotCom && licenseFeatures.isCodeSearchEnabled
|
||||
condition={({ isSourcegraphDotCom }) =>
|
||||
!isSourcegraphDotCom && window.context?.codeSearchEnabledOnInstance
|
||||
}
|
||||
/>
|
||||
),
|
||||
@ -190,7 +184,7 @@ export const routes: RouteObject[] = [
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <SearchContextsListPage {...props} />}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -199,7 +193,7 @@ export const routes: RouteObject[] = [
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <CreateSearchContextPage {...props} />}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -208,7 +202,7 @@ export const routes: RouteObject[] = [
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <EditSearchContextPage {...props} />}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -217,7 +211,7 @@ export const routes: RouteObject[] = [
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => <SearchContextPage {...props} />}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -232,7 +226,7 @@ export const routes: RouteObject[] = [
|
||||
render={props => (
|
||||
<GlobalNotebooksArea {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
)}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -321,61 +315,9 @@ export const routes: RouteObject[] = [
|
||||
path: PageRoutes.Debug,
|
||||
element: <PassThroughToServer />,
|
||||
},
|
||||
// TODO: [TEMPORARY] remove this redirect route when the marketing page is added.
|
||||
{
|
||||
path: `${PageRoutes.Cody}/*`,
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={() => {
|
||||
const chatID = window.location.pathname.split('/').pop()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
navigate(`/cody/chat/${chatID}`)
|
||||
}, [navigate, chatID])
|
||||
|
||||
return <div />
|
||||
}}
|
||||
condition={({ licenseFeatures }) =>
|
||||
!window.location.pathname.startsWith('/cody/chat') && licenseFeatures.isCodyEnabled
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: PageRoutes.CodyChat + '/*',
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => (
|
||||
<CodyIgnoreProvider isSourcegraphDotCom={props.isSourcegraphDotCom}>
|
||||
<CodyChatPage
|
||||
{...props}
|
||||
context={window.context}
|
||||
telemetryRecorder={props.platformContext.telemetryRecorder}
|
||||
/>
|
||||
</CodyIgnoreProvider>
|
||||
)}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodyEnabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: PageRoutes.CodySwitchAccount,
|
||||
element: (
|
||||
<LegacyRoute
|
||||
render={props => (
|
||||
<CodySwitchAccountPage {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
)}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodyEnabled}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...codyProRoutes,
|
||||
...codyRoutes,
|
||||
...communitySearchContextsRoutes,
|
||||
{
|
||||
path: PageRoutes.Cody,
|
||||
element: <LegacyRoute render={props => <CodyDashboardOrUpsellPage {...props} />} />,
|
||||
},
|
||||
// this should be the last route to be regustered because it's a catch all route
|
||||
// when the instance has the code search feature.
|
||||
{
|
||||
@ -392,7 +334,7 @@ export const routes: RouteObject[] = [
|
||||
</CodySidebarStoreProvider>
|
||||
</CodyIgnoreProvider>
|
||||
)}
|
||||
condition={({ licenseFeatures }) => licenseFeatures.isCodeSearchEnabled}
|
||||
condition={() => window.context?.codeSearchEnabledOnInstance}
|
||||
/>
|
||||
),
|
||||
// In RR6, the useMatches hook will only give you the location that is matched
|
||||
@ -404,17 +346,9 @@ export const routes: RouteObject[] = [
|
||||
]
|
||||
|
||||
function SearchPageOrUpsellPage(props: LegacyLayoutRouteContext): JSX.Element {
|
||||
const { isCodeSearchEnabled } = props.licenseFeatures
|
||||
if (!isCodeSearchEnabled) {
|
||||
return <SearchUpsellPage telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
}
|
||||
return <SearchPageWrapper {...props} />
|
||||
}
|
||||
|
||||
function CodyDashboardOrUpsellPage(props: LegacyLayoutRouteContext): JSX.Element {
|
||||
const { isCodyEnabled } = props.licenseFeatures
|
||||
if (!isCodyEnabled) {
|
||||
return <CodyUpsellPage />
|
||||
}
|
||||
return <CodyDashboardPage {...props} telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
return window.context?.codeSearchEnabledOnInstance ? (
|
||||
<SearchPageWrapper {...props} />
|
||||
) : (
|
||||
<SearchUpsellPage telemetryRecorder={props.platformContext.telemetryRecorder} />
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,14 +2,14 @@ import React, { useMemo, useRef } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import MapSearchIcon from 'mdi-react/MapSearchIcon'
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
import type { SiteSettingFields } from '@sourcegraph/shared/src/graphql-operations'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
|
||||
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
|
||||
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
import { PageHeader, LoadingSpinner } from '@sourcegraph/wildcard'
|
||||
import { LoadingSpinner, PageHeader } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../auth'
|
||||
import { withAuthenticatedUser } from '../auth/withAuthenticatedUser'
|
||||
@ -20,15 +20,14 @@ import { Page } from '../components/Page'
|
||||
import { useFeatureFlag } from '../featureFlags/useFeatureFlag'
|
||||
import { useUserExternalAccounts } from '../hooks/useUserExternalAccounts'
|
||||
import type { RouteV6Descriptor } from '../util/contributions'
|
||||
import { getLicenseFeatures } from '../util/license'
|
||||
|
||||
import {
|
||||
maintenanceGroupHeaderLabel,
|
||||
maintenanceGroupInstrumentationItemLabel,
|
||||
maintenanceGroupMonitoringItemLabel,
|
||||
maintenanceGroupMigrationsItemLabel,
|
||||
maintenanceGroupUpdatesItemLabel,
|
||||
maintenanceGroupMonitoringItemLabel,
|
||||
maintenanceGroupTracingItemLabel,
|
||||
maintenanceGroupUpdatesItemLabel,
|
||||
} from './sidebaritems'
|
||||
import { SiteAdminSidebar, type SiteAdminSideBarGroups } from './SiteAdminSidebar'
|
||||
|
||||
@ -62,11 +61,6 @@ export interface SiteAdminAreaRouteContext
|
||||
codeInsightsEnabled: boolean
|
||||
|
||||
endUserOnboardingEnabled: boolean
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SiteAdminAreaRoute extends RouteV6Descriptor<SiteAdminAreaRouteContext> {}
|
||||
@ -149,7 +143,6 @@ const AuthenticatedSiteAdminArea: React.FunctionComponent<React.PropsWithChildre
|
||||
telemetryRecorder: props.telemetryRecorder,
|
||||
codeInsightsEnabled: props.codeInsightsEnabled,
|
||||
endUserOnboardingEnabled,
|
||||
license: getLicenseFeatures(),
|
||||
}
|
||||
|
||||
return (
|
||||
@ -161,7 +154,6 @@ const AuthenticatedSiteAdminArea: React.FunctionComponent<React.PropsWithChildre
|
||||
</PageHeader>
|
||||
<div className="d-flex my-3 flex-column flex-sm-row" ref={reference}>
|
||||
<SiteAdminSidebar
|
||||
license={context.license}
|
||||
className={classNames('flex-0 mr-3 mb-4', styles.sidebar)}
|
||||
groups={adminSideBarGroups}
|
||||
isSourcegraphDotCom={props.isSourcegraphDotCom}
|
||||
|
||||
@ -15,11 +15,6 @@ export interface SiteAdminSideBarGroupContext extends BatchChangesProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
codeInsightsEnabled: boolean
|
||||
endUserOnboardingEnabled: boolean
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SiteAdminSideBarGroup extends NavGroupDescriptor<SiteAdminSideBarGroupContext> {}
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
import { mdiMagnify, mdiSitemap, mdiBookOutline, mdiPuzzleOutline, mdiPoll } from '@mdi/js'
|
||||
import { mdiBookOutline, mdiMagnify, mdiPoll, mdiPuzzleOutline, mdiSitemap } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { useQuery } from '@sourcegraph/http-client'
|
||||
import { H2, H3, Text, LoadingSpinner, Link, Icon, Tooltip } from '@sourcegraph/wildcard'
|
||||
import { H2, H3, Icon, Link, LoadingSpinner, Text, Tooltip } from '@sourcegraph/wildcard'
|
||||
|
||||
import { BatchChangesIconNav } from '../../../batches/icons'
|
||||
import {
|
||||
AnalyticsDateRange,
|
||||
type OverviewDevTimeSavedResult,
|
||||
type OverviewDevTimeSavedVariables,
|
||||
AnalyticsDateRange,
|
||||
} from '../../../graphql-operations'
|
||||
import { isCodyOnlyLicense } from '../../../util/license'
|
||||
import { ValueLegendItem } from '../components/ValueLegendList'
|
||||
import { formatNumber } from '../utils'
|
||||
|
||||
@ -115,7 +114,7 @@ export const DevTimeSaved: React.FunctionComponent<DevTimeSavedProps> = ({ showA
|
||||
return totalHoursSaved
|
||||
})()
|
||||
|
||||
const disableCodeSearchItems = isCodyOnlyLicense()
|
||||
const disableCodeSearchItems = !window.context?.codeSearchEnabledOnInstance
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@ -191,12 +191,12 @@ export const otherSiteAdminRoutes: readonly SiteAdminAreaRoute[] = [
|
||||
{
|
||||
path: '/analytics/search',
|
||||
render: props => <AnalyticsSearchPage {...props} />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: '/analytics/code-intel',
|
||||
render: props => <AnalyticsCodeIntelPage {...props} />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: '/analytics/extensions',
|
||||
@ -209,7 +209,7 @@ export const otherSiteAdminRoutes: readonly SiteAdminAreaRoute[] = [
|
||||
{
|
||||
path: '/analytics/cody',
|
||||
render: props => <AnalyticsCodyPage {...props} />,
|
||||
condition: ({ license }) => license.isCodyEnabled,
|
||||
condition: () => window.context?.codyEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: '/analytics/code-insights',
|
||||
@ -224,7 +224,7 @@ export const otherSiteAdminRoutes: readonly SiteAdminAreaRoute[] = [
|
||||
{
|
||||
path: '/analytics/notebooks',
|
||||
render: props => <AnalyticsNotebooksPage {...props} />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: '/configuration',
|
||||
@ -457,13 +457,13 @@ export const otherSiteAdminRoutes: readonly SiteAdminAreaRoute[] = [
|
||||
{
|
||||
path: '/code-intelligence/*',
|
||||
render: () => <NavigateToCodeGraph />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
// Code graph routes
|
||||
{
|
||||
path: '/code-graph/*',
|
||||
render: props => <AdminCodeIntelArea {...props} />,
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
path: '/lsif-uploads/:id',
|
||||
|
||||
@ -29,17 +29,17 @@ const analyticsGroup: SiteAdminSideBarGroup = {
|
||||
{
|
||||
label: 'Search',
|
||||
to: '/site-admin/analytics/search',
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
label: 'Cody',
|
||||
to: '/site-admin/analytics/cody',
|
||||
condition: ({ license }) => license.isCodyEnabled,
|
||||
condition: () => window.context?.codyEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
label: 'Code navigation',
|
||||
to: '/site-admin/analytics/code-intel',
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
label: 'Users',
|
||||
@ -58,7 +58,7 @@ const analyticsGroup: SiteAdminSideBarGroup = {
|
||||
{
|
||||
label: 'Notebooks',
|
||||
to: '/site-admin/analytics/notebooks',
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
label: 'Search extensions',
|
||||
@ -67,7 +67,7 @@ const analyticsGroup: SiteAdminSideBarGroup = {
|
||||
{
|
||||
label: 'Code ownership',
|
||||
to: '/site-admin/analytics/own',
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
{
|
||||
label: 'Feedback survey',
|
||||
@ -277,7 +277,7 @@ const codeIntelGroup: SiteAdminSideBarGroup = {
|
||||
to: '/site-admin/own-signal-page',
|
||||
},
|
||||
],
|
||||
condition: ({ license }) => license.isCodeSearchEnabled,
|
||||
condition: () => window.context?.codeSearchEnabledOnInstance,
|
||||
}
|
||||
|
||||
const usersGroup: SiteAdminSideBarGroup = {
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
@import 'wildcard/src/global-styles/breakpoints';
|
||||
|
||||
.upsell {
|
||||
padding: 1.75rem 2.5rem;
|
||||
padding-right: 1rem;
|
||||
margin-top: 4rem;
|
||||
max-width: 60rem;
|
||||
border-radius: 0.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: 1rem;
|
||||
|
||||
@media (--sm-breakpoint-down) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&-logo {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&-image {
|
||||
filter: drop-shadow(-7px -16px 32px #a112ff24);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-meta {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: var(--text-title);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
&-description {
|
||||
color: var(--text-body);
|
||||
font-size: 0.9375rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
&-link {
|
||||
&-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
import type { FC } from 'react'
|
||||
|
||||
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
import { Link, Text } from '@sourcegraph/wildcard'
|
||||
|
||||
import { CodyLogo } from '../../../cody/components/CodyLogo'
|
||||
|
||||
import { MultiLineCompletion } from './MultilineCompletion'
|
||||
|
||||
import styles from './CodyUpsell.module.scss'
|
||||
|
||||
interface CodyUpsellProps {
|
||||
isSourcegraphDotCom: boolean
|
||||
}
|
||||
|
||||
export const CodyUpsell: FC<CodyUpsellProps> = ({ isSourcegraphDotCom }) => {
|
||||
const isLightTheme = useIsLightTheme()
|
||||
// On DotCom, we want to redirect to the PLG page. On Enterprise instances, we redirect to their Cody dashboard page.
|
||||
const exploreCodyLink = isSourcegraphDotCom ? 'https://sourcegraph.com/cody' : '/cody'
|
||||
return (
|
||||
<section className={styles.upsell}>
|
||||
<section className={styles.upsellMeta}>
|
||||
<CodyLogo withColor={true} className={styles.upsellLogo} />
|
||||
<Text className={styles.upsellTitle}>Introducing Cody: your new AI coding assistant.</Text>
|
||||
<Text className={styles.upsellDescription}>
|
||||
Cody autocompletes single lines, or entire code blocks, in any programming language, keeping all of
|
||||
your company’s codebase in mind.
|
||||
</Text>
|
||||
<Link to={exploreCodyLink}>Explore Cody</Link>
|
||||
</section>
|
||||
<MultiLineCompletion isLightTheme={isLightTheme} className={styles.upsellImage} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
import { type FC, useEffect, useState } from 'react'
|
||||
import { useEffect, useState, type FC } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
|
||||
@ -19,7 +19,6 @@ import { GettingStartedTour } from '../../../tour/GettingStartedTour'
|
||||
import { useShowOnboardingTour } from '../../../tour/hooks'
|
||||
|
||||
import { AddCodeHostWidget } from './AddCodeHostWidget'
|
||||
import { CodyUpsell } from './CodyUpsell'
|
||||
import { KeywordSearchCtaSection } from './KeywordSearchCtaSection'
|
||||
import { SearchPageFooter } from './SearchPageFooter'
|
||||
import { SearchPageInput } from './SearchPageInput'
|
||||
@ -158,7 +157,6 @@ export const SearchPageContent: FC<SearchPageContentProps> = props => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<CodyUpsell isSourcegraphDotCom={isSourcegraphDotCom} />
|
||||
<SearchPageFooter />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type FC, useMemo, Suspense } from 'react'
|
||||
import { Suspense, useMemo, type FC } from 'react'
|
||||
|
||||
import { useParams, Routes, Route } from 'react-router-dom'
|
||||
import { Route, Routes, useParams } from 'react-router-dom'
|
||||
|
||||
import { gql, useQuery } from '@sourcegraph/http-client'
|
||||
import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
|
||||
@ -10,7 +10,7 @@ import { LoadingSpinner } from '@sourcegraph/wildcard'
|
||||
|
||||
import type { AuthenticatedUser } from '../../auth'
|
||||
import type { BatchChangesProps } from '../../batches'
|
||||
import type { BreadcrumbsProps, BreadcrumbSetters } from '../../components/Breadcrumbs'
|
||||
import type { BreadcrumbSetters, BreadcrumbsProps } from '../../components/Breadcrumbs'
|
||||
import { RouteError } from '../../components/ErrorBoundary'
|
||||
import { NotFoundPage } from '../../components/HeroPage'
|
||||
import { Page } from '../../components/Page'
|
||||
@ -21,7 +21,6 @@ import type {
|
||||
} from '../../graphql-operations'
|
||||
import type { NamespaceProps } from '../../namespaces'
|
||||
import type { RouteV6Descriptor } from '../../util/contributions'
|
||||
import { getLicenseFeatures } from '../../util/license'
|
||||
import { isAccessTokenCallbackPage } from '../settings/accessTokens/UserSettingsCreateAccessTokenCallbackPage'
|
||||
import type { UserSettingsAreaRoute } from '../settings/UserSettingsArea'
|
||||
import type { UserSettingsSidebarItems } from '../settings/UserSettingsSidebar'
|
||||
@ -127,12 +126,6 @@ export interface UserAreaRouteContext
|
||||
userSettingsAreaRoutes: readonly UserSettingsAreaRoute[]
|
||||
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
// license related properties
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,7 +186,6 @@ export const UserArea: FC<UserAreaProps> = ({ useBreadcrumb, userAreaRoutes, isS
|
||||
namespace: user,
|
||||
...childBreadcrumbSetters,
|
||||
isSourcegraphDotCom,
|
||||
license: getLicenseFeatures(),
|
||||
telemetryRecorder: props.platformContext.telemetryRecorder,
|
||||
}
|
||||
|
||||
|
||||
@ -20,11 +20,6 @@ interface Props extends UserAreaRouteContext {
|
||||
|
||||
export interface UserAreaHeaderContext extends BatchChangesProps, Pick<Props, 'user'> {
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
license: {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserAreaHeaderNavItem extends NavItemWithIconDescriptor<UserAreaHeaderContext> {}
|
||||
|
||||
@ -22,7 +22,8 @@ export const userAreaHeaderNavItems: readonly UserAreaHeaderNavItem[] = [
|
||||
to: '/searches',
|
||||
label: 'Saved searches',
|
||||
icon: FeatureSearchOutlineIcon,
|
||||
condition: ({ user: { viewerCanAdminister }, license }) => viewerCanAdminister && license.isCodeSearchEnabled,
|
||||
condition: ({ user: { viewerCanAdminister } }) =>
|
||||
viewerCanAdminister && window.context?.codeSearchEnabledOnInstance,
|
||||
},
|
||||
...namespaceAreaHeaderNavItems,
|
||||
]
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
import { describe, expect, it, afterEach } from 'vitest'
|
||||
|
||||
import { isCodyOnlyLicense, isCodeSearchOnlyLicense, isCodeSearchPlusCodyLicense } from './license'
|
||||
|
||||
describe('licensing utils', () => {
|
||||
const origContext = window.context
|
||||
afterEach(() => {
|
||||
window.context = origContext
|
||||
})
|
||||
|
||||
it('Cody only license', () => {
|
||||
window.context = {
|
||||
licenseInfo: {
|
||||
features: {
|
||||
cody: true,
|
||||
codeSearch: false,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
expect(isCodyOnlyLicense()).toEqual(true)
|
||||
expect(isCodeSearchOnlyLicense()).toEqual(false)
|
||||
expect(isCodeSearchPlusCodyLicense()).toEqual(false)
|
||||
})
|
||||
|
||||
it('Code Search only license', () => {
|
||||
window.context = {
|
||||
licenseInfo: {
|
||||
features: {
|
||||
cody: false,
|
||||
codeSearch: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
expect(isCodyOnlyLicense()).toEqual(false)
|
||||
expect(isCodeSearchOnlyLicense()).toEqual(true)
|
||||
expect(isCodeSearchPlusCodyLicense()).toEqual(false)
|
||||
})
|
||||
|
||||
it('Code Search plus Cody license', () => {
|
||||
window.context = {
|
||||
licenseInfo: {
|
||||
features: {
|
||||
cody: true,
|
||||
codeSearch: true,
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
expect(isCodyOnlyLicense()).toEqual(false)
|
||||
expect(isCodeSearchOnlyLicense()).toEqual(false)
|
||||
expect(isCodeSearchPlusCodyLicense()).toEqual(true)
|
||||
})
|
||||
})
|
||||
@ -1,30 +0,0 @@
|
||||
export const isCodyOnlyLicense = (): boolean =>
|
||||
Boolean(
|
||||
typeof window !== 'undefined' &&
|
||||
!window.context.licenseInfo?.features.codeSearch &&
|
||||
window.context.licenseInfo?.features.cody
|
||||
)
|
||||
|
||||
export const isCodeSearchOnlyLicense = (): boolean =>
|
||||
Boolean(
|
||||
typeof window !== 'undefined' &&
|
||||
window.context.licenseInfo?.features.codeSearch &&
|
||||
!window.context.licenseInfo?.features.cody
|
||||
)
|
||||
|
||||
export const isCodeSearchPlusCodyLicense = (): boolean =>
|
||||
Boolean(
|
||||
typeof window !== 'undefined' &&
|
||||
window.context.licenseInfo?.features.codeSearch &&
|
||||
window.context.licenseInfo?.features.cody
|
||||
)
|
||||
|
||||
interface LicenseFeatures {
|
||||
isCodeSearchEnabled: boolean
|
||||
isCodyEnabled: boolean
|
||||
}
|
||||
|
||||
export const getLicenseFeatures = (): LicenseFeatures => ({
|
||||
isCodeSearchEnabled: Boolean(window.context.licenseInfo?.features.codeSearch),
|
||||
isCodyEnabled: Boolean(window.context.licenseInfo?.features.cody),
|
||||
})
|
||||
@ -132,19 +132,9 @@ type FeatureBatchChanges struct {
|
||||
MaxNumChangesets int `json:"maxNumChangesets"`
|
||||
}
|
||||
|
||||
// LicenseFeatures contains information about licensed features that are
|
||||
// enabled/disabled on the current license.
|
||||
type LicenseFeatures struct {
|
||||
CodeSearch bool `json:"codeSearch"`
|
||||
Cody bool `json:"cody"`
|
||||
}
|
||||
|
||||
// LicenseInfo contains non-sensitive information about the legitimate usage of the
|
||||
// current license on the instance. It is technically accessible to all users, so only
|
||||
// include information that is safe to be seen by others.
|
||||
// LicenseInfo contains non-sensitive information about the current license on the instance.
|
||||
type LicenseInfo struct {
|
||||
BatchChanges *FeatureBatchChanges `json:"batchChanges"`
|
||||
Features LicenseFeatures `json:"features"`
|
||||
}
|
||||
|
||||
// FrontendCodyProConfig is the configuration data for Cody Pro that needs to be passed
|
||||
@ -221,15 +211,22 @@ type JSContext struct {
|
||||
BatchChangesDisableWebhooksWarning bool `json:"batchChangesDisableWebhooksWarning"`
|
||||
BatchChangesWebhookLogsEnabled bool `json:"batchChangesWebhookLogsEnabled"`
|
||||
|
||||
// CodyEnabled is true `cody.enabled` is not false in site-config
|
||||
CodyEnabled bool `json:"codyEnabled"`
|
||||
// CodyEnabledForCurrentUser is true if CodyEnabled is true and current
|
||||
// CodyEnabledOnInstance is true `cody.enabled` is not false in site config. Check
|
||||
// CodyEnabledForCurrentUser to see if the current user has access to Cody.
|
||||
CodyEnabledOnInstance bool `json:"codyEnabledOnInstance"`
|
||||
|
||||
// CodyEnabledForCurrentUser is true if CodyEnabled is true and the current
|
||||
// user has access to Cody.
|
||||
CodyEnabledForCurrentUser bool `json:"codyEnabledForCurrentUser"`
|
||||
|
||||
// CodyRequiresVerifiedEmail is true if usage of Cody requires the current
|
||||
// user to have a verified email.
|
||||
CodyRequiresVerifiedEmail bool `json:"codyRequiresVerifiedEmail"`
|
||||
|
||||
// CodeSearchEnabledOnInstance is true if code search is licensed. (There is currently no
|
||||
// separate config to disable it if licensed.)
|
||||
CodeSearchEnabledOnInstance bool `json:"codeSearchEnabledOnInstance"`
|
||||
|
||||
ExecutorsEnabled bool `json:"executorsEnabled"`
|
||||
CodeIntelAutoIndexingEnabled bool `json:"codeIntelAutoIndexingEnabled"`
|
||||
CodeIntelAutoIndexingAllowGlobalPolicies bool `json:"codeIntelAutoIndexingAllowGlobalPolicies"`
|
||||
@ -374,6 +371,8 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
|
||||
|
||||
isDotComMode := dotcom.SourcegraphDotComMode()
|
||||
|
||||
licenseInfo, codeSearchLicensed, codyLicensed := licenseInfo()
|
||||
|
||||
// 🚨 SECURITY: This struct is sent to all users regardless of whether or
|
||||
// not they are logged in, for example on an auth.public=false private
|
||||
// server. Including secret fields here is OK if it is based on the user's
|
||||
@ -432,10 +431,12 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
|
||||
BatchChangesDisableWebhooksWarning: conf.Get().BatchChangesDisableWebhooksWarning,
|
||||
BatchChangesWebhookLogsEnabled: webhooks.LoggingEnabled(conf.Get()),
|
||||
|
||||
CodyEnabled: conf.CodyEnabled(),
|
||||
CodyEnabledOnInstance: conf.CodyEnabled(),
|
||||
CodyEnabledForCurrentUser: codyEnabled,
|
||||
CodyRequiresVerifiedEmail: siteResolver.RequiresVerifiedEmailForCody(ctx),
|
||||
|
||||
CodeSearchEnabledOnInstance: codeSearchLicensed,
|
||||
|
||||
ExecutorsEnabled: conf.ExecutorsEnabled(),
|
||||
CodeIntelAutoIndexingEnabled: conf.CodeIntelAutoIndexingEnabled(),
|
||||
CodeIntelAutoIndexingAllowGlobalPolicies: conf.CodeIntelAutoIndexingAllowGlobalPolicies(),
|
||||
@ -457,7 +458,7 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
|
||||
|
||||
ExperimentalFeatures: conf.ExperimentalFeatures(),
|
||||
|
||||
LicenseInfo: licenseInfo(),
|
||||
LicenseInfo: licenseInfo,
|
||||
|
||||
HashedLicenseKey: conf.HashedCurrentLicenseKeyForAnalytics(),
|
||||
|
||||
@ -483,7 +484,8 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
|
||||
|
||||
// If the license a Sourcegraph instance is running under does not support Code Search features
|
||||
// we force disable related features (executors, batch-changes, executors, code-insights).
|
||||
if !context.LicenseInfo.Features.CodeSearch {
|
||||
if !codeSearchLicensed {
|
||||
context.CodeSearchEnabledOnInstance = false
|
||||
context.BatchChangesEnabled = false
|
||||
context.CodeInsightsEnabled = false
|
||||
context.ExecutorsEnabled = false
|
||||
@ -500,8 +502,8 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
|
||||
|
||||
// If the license a Sourcegraph instance is running under does not support Cody features,
|
||||
// we force disable related features.
|
||||
if !context.LicenseInfo.Features.Cody {
|
||||
context.CodyEnabled = false
|
||||
if !codyLicensed {
|
||||
context.CodyEnabledOnInstance = false
|
||||
context.CodyEnabledForCurrentUser = false
|
||||
}
|
||||
|
||||
@ -668,7 +670,7 @@ func isBot(userAgent string) bool {
|
||||
return isBotPat.MatchString(userAgent)
|
||||
}
|
||||
|
||||
func licenseInfo() (info LicenseInfo) {
|
||||
func licenseInfo() (info LicenseInfo, codeSearchLicensed, codyLicensed bool) {
|
||||
if !dotcom.SourcegraphDotComMode() {
|
||||
bcFeature := &licensing.FeatureBatchChanges{}
|
||||
if err := licensing.Check(bcFeature); err == nil {
|
||||
@ -687,12 +689,10 @@ func licenseInfo() (info LicenseInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
info.Features = LicenseFeatures{
|
||||
CodeSearch: licensing.Check(licensing.FeatureCodeSearch) == nil,
|
||||
Cody: licensing.Check(licensing.FeatureCody) == nil,
|
||||
}
|
||||
codeSearchLicensed = licensing.Check(licensing.FeatureCodeSearch) == nil
|
||||
codyLicensed = licensing.Check(licensing.FeatureCody) == nil
|
||||
|
||||
return info
|
||||
return info, codeSearchLicensed, codyLicensed
|
||||
}
|
||||
|
||||
func makeFrontendCodyProConfig(config *schema.CodyProConfig) *FrontendCodyProConfig {
|
||||
|
||||
@ -367,8 +367,7 @@ func serveHome(db database.DB) handlerFunc {
|
||||
// On non-Sourcegraph.com instances, there is no separate homepage, so redirect to /search.
|
||||
// except if the instance is on a Cody-Only license.
|
||||
redirectURL := "/search"
|
||||
features := common.Context.LicenseInfo.Features
|
||||
if !features.CodeSearch && features.Cody && !dotcom.SourcegraphDotComMode() {
|
||||
if !common.Context.CodeSearchEnabledOnInstance && common.Context.CodyEnabledOnInstance && !dotcom.SourcegraphDotComMode() {
|
||||
redirectURL = "/cody"
|
||||
}
|
||||
|
||||
|
||||
@ -34,8 +34,8 @@ export const defaultProjectConfig: UserWorkspaceConfig = {
|
||||
],
|
||||
css: { modules: { classNameStrategy: 'non-scoped' } },
|
||||
hideSkippedTests: true,
|
||||
setupFiles: [path.join(process.cwd(), `client/testing/src/perTestSetup.${TS_EXT}`)],
|
||||
globalSetup: [path.join(process.cwd(), `client/testing/src/globalTestSetup.${TS_EXT}`)],
|
||||
setupFiles: [path.join(BAZEL ? process.cwd() : __dirname, `client/testing/src/perTestSetup.${TS_EXT}`)],
|
||||
globalSetup: [path.join(BAZEL ? process.cwd() : __dirname, `client/testing/src/globalTestSetup.${TS_EXT}`)],
|
||||
},
|
||||
plugins: BAZEL
|
||||
? [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user