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

make Cody and Code Search global navbar items one-click #63514

Merged
merged 1 commit into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 6 additions & 68 deletions client/web/src/integration/nav.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { subDays } from 'date-fns'
import expect from 'expect'
import { describe, test, before, afterEach, beforeEach, after } from 'mocha'
import { after, afterEach, before, beforeEach, describe, test } from 'mocha'

import { encodeURIPathComponent } from '@sourcegraph/common'
import type { SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations'
import { mixedSearchStreamEvents } from '@sourcegraph/shared/src/search/integration/streaming-search-mocks'
import { type Driver, createDriverForTest } from '@sourcegraph/shared/src/testing/driver'
import { createDriverForTest, type Driver } from '@sourcegraph/shared/src/testing/driver'
import { afterEachSaveScreenshotIfFailed } from '@sourcegraph/shared/src/testing/screenshotReporter'

import type { NotebookFields, WebGraphQlOperations } from '../graphql-operations'

import { type WebIntegrationTestContext, createWebIntegrationTestContext } from './context'
import { createWebIntegrationTestContext, type WebIntegrationTestContext } from './context'
import {
createResolveRepoRevisionResult,
createFileExternalLinksResult,
createBlobContentResult,
createTreeEntriesResult,
createFileExternalLinksResult,
createFileNamesResult,
createResolveRepoRevisionResult,
createTreeEntriesResult,
} from './graphQlResponseHelpers'
import { commonWebGraphQlResults } from './graphQlResults'
import { createEditorAPI, removeContextFromQuery } from './utils'
Expand Down Expand Up @@ -108,66 +108,4 @@ describe('GlobalNavbar', () => {
)
})
})

describe('Code Search Dropdown', () => {
test('is highlighted on search page', async () => {
await driver.page.goto(driver.sourcegraphBaseUrl + '/search?q=test&patternType=regexp')
await driver.page.waitForSelector('[data-test-id="/search"]')
await driver.page.waitForSelector('[data-test-active="true"]')

const active = await driver.page.evaluate(() =>
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
)

expect(active).toEqual('true')
})

test('is highlighted on repo page', async () => {
await driver.page.goto(driver.sourcegraphBaseUrl + '/github.com/sourcegraph/sourcegraph')
await driver.page.waitForSelector('[data-test-id="/search"]')
await driver.page.waitForSelector('[data-test-active="true"]')

const active = await driver.page.evaluate(() =>
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
)

expect(active).toEqual('true')
})

test('is highlighted on repo file page', async () => {
await driver.page.goto(driver.sourcegraphBaseUrl + '/github.com/sourcegraph/sourcegraph/-/blob/README.md')
await driver.page.waitForSelector('[data-test-id="/search"]')
await driver.page.waitForSelector('[data-test-active="true"]')

const active = await driver.page.evaluate(() =>
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
)

expect(active).toEqual('true')
})

test('is highlighted on notebook page', async () => {
await driver.page.goto(driver.sourcegraphBaseUrl + '/notebooks/id')
await driver.page.waitForSelector('[data-test-id="/search"]')
await driver.page.waitForSelector('[data-test-active="true"]')

const active = await driver.page.evaluate(() =>
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
)

expect(active).toEqual('true')
})

test('is not highlighted on insights page', async () => {
await driver.page.goto(driver.sourcegraphBaseUrl + '/insights/id')
await driver.page.waitForSelector('[data-test-id="/search"]')
await driver.page.waitForSelector('[data-test-active="false"]')

const active = await driver.page.evaluate(() =>
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
)

expect(active).toEqual('false')
})
})
})
37 changes: 12 additions & 25 deletions client/web/src/nav/GlobalNavbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const PROPS: React.ComponentProps<typeof GlobalNavbar> = {
codeMonitoringEnabled: true,
ownEnabled: true,
showFeedbackModal: () => undefined,
__testing__isOpen: true,
}

describe('GlobalNavbar', () => {
Expand Down Expand Up @@ -68,8 +67,8 @@ describe('GlobalNavbar', () => {
</MockedTestProvider>
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'dropdown',
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
codyItemType: 'link',
codyItemLink: 'Cody /cody/chat',
})
})

Expand All @@ -82,7 +81,7 @@ describe('GlobalNavbar', () => {
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'link',
codyItemLink: 'https://sourcegraph.com/cody',
codyItemLink: 'Cody https://sourcegraph.com/cody',
})
})

Expand All @@ -93,8 +92,8 @@ describe('GlobalNavbar', () => {
</MockedTestProvider>
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'dropdown',
codyDropdownItems: ['Dashboard /cody/manage', 'Web Chat /cody/chat'],
codyItemType: 'link',
codyItemLink: 'Cody /cody/chat',
})
})

Expand All @@ -106,8 +105,8 @@ describe('GlobalNavbar', () => {
</MockedTestProvider>
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'dropdown',
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
codyItemType: 'link',
codyItemLink: 'Cody /cody/chat',
})
})

Expand All @@ -120,7 +119,7 @@ describe('GlobalNavbar', () => {
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'link',
codyItemLink: '/cody/dashboard',
codyItemLink: 'Cody /cody/dashboard',
})
})

Expand All @@ -135,8 +134,8 @@ describe('GlobalNavbar', () => {
</MockedTestProvider>
)
expect(describeNavBar(baseElement)).toEqual<NavBarTestDescription>({
codyItemType: 'dropdown',
codyDropdownItems: ['Dashboard /cody/dashboard', 'Web Chat /cody/chat'],
codyItemType: 'link',
codyItemLink: 'BrandLogo /cody/chat',
})
})

Expand All @@ -156,28 +155,16 @@ describe('GlobalNavbar', () => {
})

interface NavBarTestDescription {
codyItemType: 'none' | 'link' | 'dropdown'
codyItemType: 'none' | 'link'
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') ?? '',
codyItemLink: `${item.textContent} ${item.getAttribute('href') ?? ''}`,
}
: { codyItemType: 'none' }
}
111 changes: 38 additions & 73 deletions client/web/src/nav/GlobalNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import classNames from 'classnames'
import BarChartIcon from 'mdi-react/BarChartIcon'
import MagnifyIcon from 'mdi-react/MagnifyIcon'
import ToolsIcon from 'mdi-react/ToolsIcon'
import { useLocation, type RouteObject } from 'react-router-dom'
import useResizeObserver from 'use-resize-observer'

Expand All @@ -23,13 +24,12 @@ import type { SearchContextInputProps } from '@sourcegraph/shared/src/search'
import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings'
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { useIsLightTheme } from '@sourcegraph/shared/src/theme'
import { Button, ButtonLink, Link, ProductStatusBadge } from '@sourcegraph/wildcard'
import { Button, ButtonLink, Link } from '@sourcegraph/wildcard'

import type { AuthenticatedUser } from '../auth'
import type { BatchChangesProps } from '../batches'
import { BatchChangesNavItem } from '../batches/BatchChangesNavItem'
import type { CodeMonitoringProps } from '../codeMonitoring'
import { CodyProRoutes } from '../cody/codyProRoutes'
import { CODY_MARKETING_PAGE_URL } from '../cody/codyRoutes'
import { CodyLogo } from '../cody/components/CodyLogo'
import { BrandLogo } from '../components/branding/BrandLogo'
Expand Down Expand Up @@ -77,8 +77,6 @@ export interface GlobalNavbarProps
showFeedbackModal: () => void

setFuzzyFinderIsVisible: React.Dispatch<SetStateAction<boolean>>

__testing__isOpen?: boolean
}

/**
Expand Down Expand Up @@ -135,7 +133,6 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
notebooksEnabled,
ownEnabled,
showFeedbackModal,
__testing__isOpen,
...props
}) => {
const location = useLocation()
Expand Down Expand Up @@ -199,7 +196,6 @@ export const GlobalNavbar: React.FunctionComponent<React.PropsWithChildren<Globa
showCodeInsights={codeInsights}
routeMatch={routeMatch}
isSourcegraphDotCom={isSourcegraphDotCom}
__testing__isOpen={__testing__isOpen}
/>

<NavActions>
Expand Down Expand Up @@ -291,8 +287,6 @@ export interface InlineNavigationPanelProps {
/** A current react router route match */
routeMatch?: string
className?: string

__testing__isOpen?: boolean
}

export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
Expand All @@ -306,13 +300,12 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
isSourcegraphDotCom,
routeMatch,
className,
__testing__isOpen,
} = props

const navbarReference = useRef<HTMLDivElement | null>(null)
const navLinkVariant = useCalculatedNavLinkVariant(navbarReference)

const searchNavBarItems = useMemo(() => {
const toolsItems = useMemo(() => {
const items: (NavDropdownItem | false)[] = [
showSearchContext && { path: PageRoutes.Contexts, content: 'Contexts' },
showSearchNotebook && { path: PageRoutes.Notebooks, content: 'Notebooks' },
Expand All @@ -321,83 +314,46 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
showCodeMonitoring && { path: '/code-monitoring', content: 'Monitoring' },
showSearchJobs && {
path: PageRoutes.SearchJobs,
content: (
<>
Search Jobs <ProductStatusBadge className="ml-2" status="beta" />
</>
),
content: 'Search Jobs',
},
]
return items.filter<NavDropdownItem>((item): item is NavDropdownItem => !!item)
}, [showSearchContext, showSearchJobs, showCodeMonitoring, showSearchNotebook])

const searchNavigation =
searchNavBarItems.length > 0 ? (
<NavDropdown
key="search"
toggleItem={{
path: PageRoutes.Search,
altPath: PageRoutes.RepoContainer,
icon: MagnifyIcon,
content: 'Code Search',
variant: navLinkVariant,
}}
routeMatch={routeMatch}
homeItem={{ content: 'Search home' }}
items={searchNavBarItems}
name="search"
/>
) : (
<NavItem icon={MagnifyIcon} key="search">
<NavLink variant={navLinkVariant} to={PageRoutes.Search}>
Code Search
</NavLink>
</NavItem>
)

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={isSourcegraphDotCom ? CODY_MARKETING_PAGE_URL : PageRoutes.CodyDashboard}
>
Cody
</NavLink>
</NavItem>
) : (
const toolsItem = toolsItems.length > 0 && (
<NavDropdown
key="cody"
key="tools"
toggleItem={{
path: '/cody/*',
icon: () => <CodyLogoWrapper />,
content: 'Cody',
path: '<never-active>',
icon: ToolsIcon,
content: 'Tools',
variant: navLinkVariant,
}}
routeMatch={routeMatch}
items={[
{
path: isSourcegraphDotCom ? CodyProRoutes.Manage : PageRoutes.CodyDashboard,
content: 'Dashboard',
},
{
path: PageRoutes.CodyChat,
content: 'Web Chat',
},
]}
name="cody"
__testing__isOpen={__testing__isOpen}
items={toolsItems}
name="tools"
/>
)

let prioritizedLinks: JSX.Element[] = [searchNavigation, codyNavigation].filter(isDefined)
const searchItem = (
<NavItem icon={MagnifyIcon} key="search">
<NavLink variant={navLinkVariant} to={PageRoutes.Search}>
Code Search
</NavLink>
</NavItem>
)

if (!window.context?.codeSearchEnabledOnInstance) {
// This should be cheap considering there will only be two items in the array.
prioritizedLinks = prioritizedLinks.reverse()
}
const CodyLogoWrapper = (): JSX.Element => <CodyLogo withColor={routeMatch?.startsWith(PageRoutes.CodyChat)} />
const codyItem = window.context?.codyEnabledOnInstance ? (
<NavItem icon={() => <CodyLogoWrapper />} key="cody">
<NavLink variant={navLinkVariant} to={linkForCodyNavItem(isSourcegraphDotCom)}>
Cody
</NavLink>
</NavItem>
) : null

const prioritizedLinks = (
window.context?.codeSearchEnabledOnInstance ? [searchItem, codyItem] : [codyItem, searchItem]
).filter(isDefined)
return (
<NavGroup ref={navbarReference} className={classNames(className, styles.list)}>
{prioritizedLinks}
Expand All @@ -409,6 +365,7 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
</NavLink>
</NavItem>
)}
{toolsItem}
{isSourcegraphDotCom && (
<NavItem>
<NavLink variant={navLinkVariant} to="https://sourcegraph.com" external={true}>
Expand All @@ -419,3 +376,11 @@ export const InlineNavigationPanel: FC<InlineNavigationPanelProps> = props => {
</NavGroup>
)
}

export function linkForCodyNavItem(isSourcegraphDotCom: boolean): string {
return window.context.codyEnabledForCurrentUser
? PageRoutes.CodyChat
: isSourcegraphDotCom
? CODY_MARKETING_PAGE_URL
: PageRoutes.CodyDashboard
}
Loading
Loading