From 5a20781c63834519ecc9917c4dbb518d53e06ceb Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 20:31:31 +0000 Subject: [PATCH] feat(cmdk): Tag Seer chats started from the command palette Pass where a forwarded query came from (Tab or the no-results Ask Seer item) through to the explorer-chat POST that starts the run, and add it to the session_created analytics event as entry_point. Continued runs never send it. Co-Authored-By: Nate Moore <7118177+natemoo-re@users.noreply.github.com> Co-Authored-By: Matt Duncan --- .../commandPalette/ui/commandPalette.spec.tsx | 48 +++++++++++++++++ .../commandPalette/ui/commandPalette.tsx | 12 ++++- .../events/autofix/v3/nextStep.spec.tsx | 1 + .../utils/analytics/seerAnalyticsEvents.tsx | 7 ++- static/app/views/navigation/topBar.spec.tsx | 1 + .../drawer/explorerDrawerContent.tsx | 4 ++ .../drawer/useSeerExplorerDrawer.tsx | 15 +++++- .../components/seerExplorerContent.spec.tsx | 32 ++++++++++-- .../components/seerExplorerContent.tsx | 9 +++- .../components/sidebar/seerExplorerPanel.tsx | 2 + .../seerExplorerSidebarLayout.spec.tsx | 10 +++- .../hooks/useSeerExplorer.spec.tsx | 51 +++++++++++++++++++ .../seerExplorer/hooks/useSeerExplorer.tsx | 9 +++- .../seerExplorerContextProvider.tsx | 20 +++++++- static/app/views/seerExplorer/types.tsx | 5 ++ .../seerExplorer/useSeerExplorerContext.tsx | 5 +- 16 files changed, 214 insertions(+), 17 deletions(-) diff --git a/static/app/components/commandPalette/ui/commandPalette.spec.tsx b/static/app/components/commandPalette/ui/commandPalette.spec.tsx index 0c964ed8cf57..0fd95dd263db 100644 --- a/static/app/components/commandPalette/ui/commandPalette.spec.tsx +++ b/static/app/components/commandPalette/ui/commandPalette.spec.tsx @@ -382,6 +382,54 @@ describe('CommandPalette', () => { ).toBeInTheDocument(); }); + it('tells Seer a Tab handoff came from the command palette', async () => { + const openSeerExplorer = jest.fn(); + render( + + + + + ); + + await userEvent.type( + screen.getByRole('searchbox', {name: 'Search commands'}), + 'dsn' + ); + await userEvent.keyboard('{Tab}'); + + expect(openSeerExplorer).toHaveBeenCalledWith({ + initialQuery: 'dsn', + initialQueryReferrer: 'command_palette.tab', + }); + }); + + it('tells Seer a no-results handoff came from the command palette', async () => { + const openSeerExplorer = jest.fn(); + render( + + + + + ); + + await userEvent.type( + screen.getByRole('searchbox', {name: 'Search commands'}), + 'xyzzy' + ); + await userEvent.click(await screen.findByRole('option', {name: 'Ask Seer: xyzzy'})); + + expect(openSeerExplorer).toHaveBeenCalledWith({ + initialQuery: 'xyzzy', + initialQueryReferrer: 'command_palette.no_results', + }); + }); + it('clearing the query restores all top-level items', async () => { render( diff --git a/static/app/components/commandPalette/ui/commandPalette.tsx b/static/app/components/commandPalette/ui/commandPalette.tsx index 0cd9136ca839..bbb138910c9d 100644 --- a/static/app/components/commandPalette/ui/commandPalette.tsx +++ b/static/app/components/commandPalette/ui/commandPalette.tsx @@ -40,6 +40,7 @@ import {fzf} from 'sentry/utils/search/fzf'; import {normalizeUrl} from 'sentry/utils/url/normalizeUrl'; import {useFeedbackForm} from 'sentry/utils/useFeedbackForm'; import {useNavigate} from 'sentry/utils/useNavigate'; +import type {SeerExplorerChatReferrer} from 'sentry/views/seerExplorer/types'; import type {CMDKActionData} from './cmdk'; import {CMDKCollection} from './cmdk'; @@ -91,7 +92,10 @@ interface CommandPaletteScore { } interface CommandPaletteProps extends ModalRenderProps { - openSeerExplorer?: (options?: {initialQuery?: string}) => void; + openSeerExplorer?: (options?: { + initialQuery?: string; + initialQueryReferrer?: SeerExplorerChatReferrer; + }) => void; } export function CommandPalette({ @@ -201,7 +205,10 @@ export function CommandPalette({ listItemType: 'action', display: {label: t('Ask Seer: %s', state.query), icon: }, onAction: () => - openSeerExplorer?.({initialQuery: state.query.trim() || undefined}), + openSeerExplorer?.({ + initialQuery: state.query.trim() || undefined, + initialQueryReferrer: 'command_palette.no_results', + }), }, ...(openForm ? [ @@ -422,6 +429,7 @@ export function CommandPalette({ closeModal?.(); openSeerExplorer({ initialQuery: state.query.trim() || undefined, + initialQueryReferrer: 'command_palette.tab', }); return; } diff --git a/static/app/components/events/autofix/v3/nextStep.spec.tsx b/static/app/components/events/autofix/v3/nextStep.spec.tsx index 30ac0b3fd645..224b835ee03f 100644 --- a/static/app/components/events/autofix/v3/nextStep.spec.tsx +++ b/static/app/components/events/autofix/v3/nextStep.spec.tsx @@ -141,6 +141,7 @@ describe('SeerDrawerNextStep', () => { setSidebarPosition: jest.fn(), sidebarAppendInitialQuery: false, sidebarInitialQuery: undefined, + sidebarInitialQueryReferrer: undefined, sidebarKey: 0, sidebarPosition: 'auto', toggleSeerExplorer: jest.fn(), diff --git a/static/app/utils/analytics/seerAnalyticsEvents.tsx b/static/app/utils/analytics/seerAnalyticsEvents.tsx index b1b977f63daa..9c9a42b8cf6e 100644 --- a/static/app/utils/analytics/seerAnalyticsEvents.tsx +++ b/static/app/utils/analytics/seerAnalyticsEvents.tsx @@ -3,7 +3,10 @@ import type { PullRequestReviewStatus, } from 'sentry/types/integrations'; import type {Organization} from 'sentry/types/organization'; -import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types'; +import type { + SeerExplorerChatReferrer, + SeerExplorerRunId, +} from 'sentry/views/seerExplorer/types'; type InvestigationActionRequestEvent = { action_kind: string; @@ -227,6 +230,8 @@ export type SeerAnalyticsEventsParameters = { 'seer.explorer.session_created': { referrer: string; surface: 'global_panel'; + /** The UI entry point that started the chat, when it was recorded. */ + entry_point?: SeerExplorerChatReferrer; }; 'seer.explorer.session_link_copied': Record; 'seer.explorer.sidebar.position_changed': { diff --git a/static/app/views/navigation/topBar.spec.tsx b/static/app/views/navigation/topBar.spec.tsx index e5ee49b7fa24..9aaa8c24ef9e 100644 --- a/static/app/views/navigation/topBar.spec.tsx +++ b/static/app/views/navigation/topBar.spec.tsx @@ -221,6 +221,7 @@ describe('TopBar', () => { setSidebarPosition: jest.fn(), sidebarAppendInitialQuery: false, sidebarInitialQuery: undefined, + sidebarInitialQueryReferrer: undefined, sidebarKey: 0, sidebarPosition: 'auto', toggleSeerExplorer: jest.fn(), diff --git a/static/app/views/seerExplorer/components/drawer/explorerDrawerContent.tsx b/static/app/views/seerExplorer/components/drawer/explorerDrawerContent.tsx index d62826ee13cb..a9a85cbc0304 100644 --- a/static/app/views/seerExplorer/components/drawer/explorerDrawerContent.tsx +++ b/static/app/views/seerExplorer/components/drawer/explorerDrawerContent.tsx @@ -1,6 +1,7 @@ import {DrawerHeader, useDrawerContentContext} from '@sentry/scraps/drawer'; import {SeerExplorerContent} from 'sentry/views/seerExplorer/components/seerExplorerContent'; +import type {SeerExplorerChatReferrer} from 'sentry/views/seerExplorer/types'; /** * Drawer surface for Seer Explorer: renders the shared content with the drawer's @@ -15,11 +16,13 @@ import {SeerExplorerContent} from 'sentry/views/seerExplorer/components/seerExpl export function ExplorerDrawerContent({ getPageReferrer, initialQuery, + initialQueryReferrer, appendInitialQuery, }: { getPageReferrer: () => string; appendInitialQuery?: boolean; initialQuery?: string; + initialQueryReferrer?: SeerExplorerChatReferrer; }) { const {onClose = () => {}} = useDrawerContentContext(); @@ -27,6 +30,7 @@ export function ExplorerDrawerContent({ ( diff --git a/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.tsx b/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.tsx index a1ca751603df..b4fc56eab7cb 100644 --- a/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.tsx +++ b/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.tsx @@ -8,7 +8,10 @@ import {isSeerExplorerEnabled} from 'sentry/utils/seer/isSeerExplorerEnabled'; import {useOrganization} from 'sentry/utils/useOrganization'; import type {ChatPrompt} from 'sentry/views/seerExplorer/chatPrompt'; import {useSeerExplorerChatDispatch} from 'sentry/views/seerExplorer/seerExplorerChatStateContext'; -import type {SeerExplorerRunId} from 'sentry/views/seerExplorer/types'; +import type { + SeerExplorerChatReferrer, + SeerExplorerRunId, +} from 'sentry/views/seerExplorer/types'; import {usePageReferrer} from 'sentry/views/seerExplorer/utils'; import {ExplorerDrawerContent} from './explorerDrawerContent'; @@ -32,6 +35,8 @@ export type OpenSeerExplorerDrawerOptions = { * an empty session, or the open one with `appendToOpenRun`. */ initialQuery?: string; + /** Where `initialQuery` came from, recorded on the run it starts. */ + initialQueryReferrer?: SeerExplorerChatReferrer; /** * Optional run ID to open. If provided, opens an existing session. */ @@ -75,7 +80,12 @@ export const useSeerExplorerDrawer = (options?: {onClose?: () => void}) => { const openSeerExplorerDrawer = useCallback( (drawerOptions?: OpenSeerExplorerDrawerOptions) => { - const {runId: openRunId, initialQuery, appendToOpenRun} = drawerOptions ?? {}; + const { + runId: openRunId, + initialQuery, + initialQueryReferrer, + appendToOpenRun, + } = drawerOptions ?? {}; if (initialQuery) { // A forwarded query starts a fresh session unless the caller asked to @@ -94,6 +104,7 @@ export const useSeerExplorerDrawer = (options?: {onClose?: () => void}) => { ), diff --git a/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx b/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx index fee2aff7b96b..43e0188009dd 100644 --- a/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx +++ b/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx @@ -488,7 +488,13 @@ describe('SeerExplorerContent', () => { // The explicit null run id is what forces a fresh run rather than a post // into the run that just failed. await waitFor(() => { - expect(sendMessage).toHaveBeenCalledWith('why is this slow?', 0, null); + expect(sendMessage).toHaveBeenCalledWith( + 'why is this slow?', + 0, + null, + undefined, + undefined + ); }); }); }); @@ -2225,7 +2231,13 @@ describe('SeerExplorerContent', () => { renderContent({initialQuery: 'why is this slow?'}); await waitFor(() => { - expect(sendMessage).toHaveBeenCalledWith('why is this slow?', 0); + expect(sendMessage).toHaveBeenCalledWith( + 'why is this slow?', + 0, + undefined, + undefined, + undefined + ); }); expect(creditCheck).toHaveBeenCalledTimes(1); expect( @@ -2260,7 +2272,13 @@ describe('SeerExplorerContent', () => { answerCreditCheck(); await waitFor(() => { - expect(sendMessage).toHaveBeenCalledWith('why is this slow?', 0); + expect(sendMessage).toHaveBeenCalledWith( + 'why is this slow?', + 0, + undefined, + undefined, + undefined + ); }); expect(sendMessage).toHaveBeenCalledTimes(1); expect(textarea).toHaveTextContent('my own question'); @@ -2316,7 +2334,13 @@ describe('SeerExplorerContent', () => { answerCreditCheck(); await waitFor(() => { - expect(sendMessage).toHaveBeenCalledWith('why is this slow?', 0); + expect(sendMessage).toHaveBeenCalledWith( + 'why is this slow?', + 0, + undefined, + undefined, + undefined + ); }); }); diff --git a/static/app/views/seerExplorer/components/seerExplorerContent.tsx b/static/app/views/seerExplorer/components/seerExplorerContent.tsx index 729d2f576b83..9ef25e125f8b 100644 --- a/static/app/views/seerExplorer/components/seerExplorerContent.tsx +++ b/static/app/views/seerExplorer/components/seerExplorerContent.tsx @@ -66,6 +66,7 @@ import type { Block, ChatSuggestion, PendingUserInput, + SeerExplorerChatReferrer, SeerExplorerRunId, SeerExplorerSidebarPosition, } from 'sentry/views/seerExplorer/types'; @@ -141,6 +142,7 @@ function SidebarHeaderShell({ export function SeerExplorerContent({ getPageReferrer, initialQuery, + initialQueryReferrer, appendInitialQuery, onClose, renderHeader, @@ -153,6 +155,8 @@ export function SeerExplorerContent({ /** Submit `initialQuery` into the open run rather than only an empty one. */ appendInitialQuery?: boolean; initialQuery?: string; + /** Where `initialQuery` came from, recorded on the run it starts. */ + initialQueryReferrer?: SeerExplorerChatReferrer; onSidebarPositionChange?: (position: SeerExplorerSidebarPosition) => void; /** Surface chrome for the header. Defaults to the sidebar shell. */ renderHeader?: RenderHeader; @@ -502,12 +506,13 @@ export function SeerExplorerContent({ if (showLoadError) { // The open run failed to load, so appending to it would post into a dead run. // A forwarded query still deserves an answer: start it in a fresh one. - sendMessage(query, 0, null); + sendMessage(query, 0, null, undefined, initialQueryReferrer); return; } - sendMessage(query, blocks.length); + sendMessage(query, blocks.length, undefined, undefined, initialQueryReferrer); }, [ initialQuery, + initialQueryReferrer, appendInitialQuery, isEmptyState, isCheckingCredits, diff --git a/static/app/views/seerExplorer/components/sidebar/seerExplorerPanel.tsx b/static/app/views/seerExplorer/components/sidebar/seerExplorerPanel.tsx index e1561e4dea8d..e4de96550cde 100644 --- a/static/app/views/seerExplorer/components/sidebar/seerExplorerPanel.tsx +++ b/static/app/views/seerExplorer/components/sidebar/seerExplorerPanel.tsx @@ -15,6 +15,7 @@ export function SeerExplorerPanel() { sidebarPosition, setSidebarPosition, sidebarInitialQuery, + sidebarInitialQueryReferrer, sidebarAppendInitialQuery, sidebarKey, } = useSeerExplorerContext(); @@ -29,6 +30,7 @@ export function SeerExplorerPanel() { key={sidebarKey} getPageReferrer={getPageReferrer} initialQuery={sidebarInitialQuery} + initialQueryReferrer={sidebarInitialQueryReferrer} appendInitialQuery={sidebarAppendInitialQuery} onClose={closeSeerExplorer} sidebarPosition={sidebarPosition} diff --git a/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx b/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx index f5fe07d35de0..451ab88e178c 100644 --- a/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx +++ b/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx @@ -401,7 +401,10 @@ describe('SeerExplorerSidebarLayout', () => { it('auto-submits an initialQuery when opened (command palette)', async () => { mockWideScreen(true); - renderSidebar(orgWithSidebar, {initialQuery: 'find the bug'}); + renderSidebar(orgWithSidebar, { + initialQuery: 'find the bug', + initialQueryReferrer: 'command_palette.tab', + }); await userEvent.click(screen.getByText('open-seer')); @@ -409,7 +412,10 @@ describe('SeerExplorerSidebarLayout', () => { await waitFor(() => expect(defaultHookReturn.sendMessage).toHaveBeenCalledWith( 'find the bug', - expect.any(Number) + expect.any(Number), + undefined, + undefined, + 'command_palette.tab' ) ); }); diff --git a/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx b/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx index 6c48c678a30d..4349e947ca51 100644 --- a/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx +++ b/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx @@ -130,6 +130,57 @@ describe('useSeerExplorer', () => { }); }); + it('records the referrer only on the message that starts a run', async () => { + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/explorer-chat/`, + method: 'GET', + body: {session: null}, + }); + const newRunMock = MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/explorer-chat/`, + method: 'POST', + body: {run_id: 123}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/explorer-chat/123/`, + method: 'GET', + body: {session: {blocks: [], run_id: 123, status: 'completed'}}, + }); + const continueMock = MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/explorer-chat/123/`, + method: 'POST', + body: {run_id: 123}, + }); + + const {result} = renderHookWithProviders(() => useSeerExplorer(), { + organization, + additionalWrapper: SeerExplorerChatStateProvider, + }); + + act(() => { + result.current.sendMessage('dsn', 0, null, undefined, 'command_palette.tab'); + }); + await waitFor(() => expect(result.current.runId).toBe(123)); + expect(newRunMock).toHaveBeenCalledWith( + `/organizations/${organization.slug}/seer/explorer-chat/`, + expect.objectContaining({ + data: expect.objectContaining({referrer: 'command_palette.tab'}), + }) + ); + + act(() => { + result.current.sendMessage( + 'more', + undefined, + undefined, + undefined, + 'command_palette.tab' + ); + }); + await waitFor(() => expect(continueMock).toHaveBeenCalled()); + expect(continueMock.mock.calls[0][1].data.referrer).toBeUndefined(); + }); + it('sends structured JSON on dashboard page with feature flag', async () => { jest.spyOn(seerExplorerUtils, 'usePageReferrer').mockReturnValue({ getPageReferrer: () => '/dashboard/:dashboardId/', diff --git a/static/app/views/seerExplorer/hooks/useSeerExplorer.tsx b/static/app/views/seerExplorer/hooks/useSeerExplorer.tsx index 0be7985d2024..ba1a32323461 100644 --- a/static/app/views/seerExplorer/hooks/useSeerExplorer.tsx +++ b/static/app/views/seerExplorer/hooks/useSeerExplorer.tsx @@ -28,6 +28,7 @@ import type { Block, RepoPRState, RespondToUserInputOptions, + SeerExplorerChatReferrer, SeerExplorerResponse, SeerExplorerRunId, } from 'sentry/views/seerExplorer/types'; @@ -266,6 +267,7 @@ export const useSeerExplorer = () => { /** The pending prompt this send took from chat state, put back if it fails. */ pendingChatPrompt: ChatPrompt | null; query: string; + referrer: SeerExplorerChatReferrer | undefined; requestId: string; runId: SeerExplorerRunId | null; screenshot: string | undefined; @@ -294,6 +296,7 @@ export const useSeerExplorer = () => { override_code_mode_enable: params.overrideCodeModeEnable, chat_prompt: params.chatPrompt?.text, chat_prompt_context: params.chatPrompt?.context, + referrer: params.referrer, }, }); }, @@ -489,7 +492,8 @@ export const useSeerExplorer = () => { query: string, explicitInsertIndex?: number, explicitRunId?: SeerExplorerRunId | null, - explicitChatPrompt?: ChatPrompt | null + explicitChatPrompt?: ChatPrompt | null, + referrer?: SeerExplorerChatReferrer ) => { if (!orgSlug) { return; @@ -528,6 +532,7 @@ export const useSeerExplorer = () => { if (effectiveRunId === null) { trackAnalytics('seer.explorer.session_created', { referrer: pageName, + entry_point: referrer, surface: 'global_panel', organization, }); @@ -559,6 +564,8 @@ export const useSeerExplorer = () => { // Send POST request sendMessageMutate({ query, + // Only a new run records where it started. + referrer: effectiveRunId === null ? referrer : undefined, chatPrompt, pendingChatPrompt: usesPendingChatPrompt ? pendingChatPrompt : null, requestId, diff --git a/static/app/views/seerExplorer/seerExplorerContextProvider.tsx b/static/app/views/seerExplorer/seerExplorerContextProvider.tsx index a01897690d5d..deadae192ad2 100644 --- a/static/app/views/seerExplorer/seerExplorerContextProvider.tsx +++ b/static/app/views/seerExplorer/seerExplorerContextProvider.tsx @@ -31,7 +31,11 @@ import { useSeerExplorerChatDispatch, useSeerExplorerChatState, } from './seerExplorerChatStateContext'; -import type {SeerExplorerRunId, SeerExplorerSidebarPosition} from './types'; +import type { + SeerExplorerChatReferrer, + SeerExplorerRunId, + SeerExplorerSidebarPosition, +} from './types'; import { SeerExplorerContext, type SeerExplorerContextValue, @@ -78,6 +82,9 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { const [sidebarInitialQuery, setSidebarInitialQuery] = useState( undefined ); + const [sidebarInitialQueryReferrer, setSidebarInitialQueryReferrer] = useState< + SeerExplorerChatReferrer | undefined + >(undefined); const [sidebarKey, setSidebarKey] = useState(0); const [sidebarPosition, setSidebarPositionState] = useLocalStorageState( @@ -198,7 +205,12 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { // Mirror `useSeerExplorerDrawer`'s option handling so deep links // (runId), the command palette (initialQuery), and session switching // behave the same in sidebar mode as in the drawer. - const {runId: openRunId, initialQuery, appendToOpenRun} = drawerOptions ?? {}; + const { + runId: openRunId, + initialQuery, + initialQueryReferrer, + appendToOpenRun, + } = drawerOptions ?? {}; if (initialQuery) { // A forwarded query starts a fresh session unless the caller asked to // add to the open run. Bump the nonce either way so re-forwarding the @@ -213,6 +225,7 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { dispatch({type: 'set run id', payload: openRunId}); } setSidebarInitialQuery(initialQuery); + setSidebarInitialQueryReferrer(initialQueryReferrer); setSidebarAppendInitialQuery(!!appendToOpenRun); openSidebar(); return; @@ -407,6 +420,7 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { sidebarContainerRef, sidebarAppendInitialQuery, sidebarInitialQuery, + sidebarInitialQueryReferrer, sidebarKey, sidebarPosition, setSidebarPosition, @@ -421,6 +435,7 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { sessionState, sidebarAppendInitialQuery, sidebarInitialQuery, + sidebarInitialQueryReferrer, sidebarKey, sidebarPosition, setSidebarPosition, @@ -479,6 +494,7 @@ export function SeerExplorerContextProvider({children}: {children: ReactNode}) { key={sidebarKey} getPageReferrer={getPageReferrer} initialQuery={sidebarInitialQuery} + initialQueryReferrer={sidebarInitialQueryReferrer} appendInitialQuery={sidebarAppendInitialQuery} onClose={closeSeerExplorer} sidebarPosition={sidebarPosition} diff --git a/static/app/views/seerExplorer/types.tsx b/static/app/views/seerExplorer/types.tsx index 9732ab0db687..c4e4711fb588 100644 --- a/static/app/views/seerExplorer/types.tsx +++ b/static/app/views/seerExplorer/types.tsx @@ -9,6 +9,11 @@ import type {EmbedOutput} from 'sentry/components/seer/markdown/embeds/utils'; */ export type SeerExplorerSidebarPosition = 'auto' | 'right' | 'bottom'; +/** The UI entry point that started a chat, recorded on the new run. */ +export type SeerExplorerChatReferrer = + | 'command_palette.tab' + | 'command_palette.no_results'; + /** * z.enum but forward-compatible: accepts any string at runtime while preserving * autocomplete for the known values via the `(string & {})` trick. diff --git a/static/app/views/seerExplorer/useSeerExplorerContext.tsx b/static/app/views/seerExplorer/useSeerExplorerContext.tsx index a9c49a20a292..345479639286 100644 --- a/static/app/views/seerExplorer/useSeerExplorerContext.tsx +++ b/static/app/views/seerExplorer/useSeerExplorerContext.tsx @@ -1,7 +1,7 @@ import {createContext, useContext, type RefObject} from 'react'; import type {OpenSeerExplorerDrawerOptions} from './components/drawer/useSeerExplorerDrawer'; -import type {SeerExplorerSidebarPosition} from './types'; +import type {SeerExplorerChatReferrer, SeerExplorerSidebarPosition} from './types'; type SeerExplorerSessionState = 'inactive' | 'thinking' | 'done-thinking'; @@ -32,6 +32,8 @@ export type SeerExplorerContextValue = { */ sidebarContainerRef: RefObject; sidebarInitialQuery: string | undefined; + /** Where `sidebarInitialQuery` came from. Sidebar mode only. */ + sidebarInitialQueryReferrer: SeerExplorerChatReferrer | undefined; /** * Increments on each forwarded query so the (always-mounted) sidebar content * resubmits a re-forwarded query. Only meaningful in sidebar mode. @@ -52,6 +54,7 @@ export const SeerExplorerContext = createContext({ setSidebarPosition: () => {}, sidebarAppendInitialQuery: false, sidebarInitialQuery: undefined, + sidebarInitialQueryReferrer: undefined, sidebarKey: 0, sidebarPosition: 'auto', toggleSeerExplorer: () => {},