Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ describe('SeerExplorerHeader', () => {
// New Chat buttons) are always in the DOM here. These tests assert presence and
// behavior; which variant is actually visible at a given width is a CSS concern
// verified visually.
function mockChatHistory() {
MockApiClient.addMockResponse({
url: `/organizations/org-slug/seer/runs/`,
method: 'GET',
body: [{id: 1, title: 'Flaky deploy', lastTriggeredAt: '2026-11-11T00:00:00Z'}],
});
MockApiClient.addMockResponse({
url: `/organizations/org-slug/seer/runs/`,
method: 'GET',
body: [],
match: [MockApiClient.matchQuery({query: 'nothing'})],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search mock never matches API query

Medium Severity

The no-results mock matches query: 'nothing', but the sessions request sends buildRunsSearchQuery's scoped string, not the raw input. That matcher never hits, so the catch-all mock still returns a chat and the test cannot reach No chats match your search.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 90a8fbb. Configure here.

}

async function renderHeader(props = {}, org = orgWith()) {
const result = render(
<SeerExplorerSessionsProvider>
Expand Down Expand Up @@ -239,6 +253,7 @@ describe('SeerExplorerHeader', () => {
});

it('opens a searchable chat history dropdown', async () => {
mockChatHistory();
await renderHeader();

// Both the inline and overflow variants mount a chat history trigger.
Expand All @@ -248,5 +263,27 @@ describe('SeerExplorerHeader', () => {
await screen.findByPlaceholderText('Search chats\u2026')
).toBeInTheDocument();
});

it('shows an empty state when there is no chat history', async () => {
await renderHeader();

const [chatHistory] = screen.getAllByRole('button', {name: 'Chat history'});
await userEvent.click(chatHistory!);
expect(await screen.findByText('No chats yet')).toBeInTheDocument();
expect(screen.queryByPlaceholderText('Search chats\u2026')).not.toBeInTheDocument();
});

it('shows a no-results state when a search matches nothing', async () => {
mockChatHistory();
await renderHeader();

const [chatHistory] = screen.getAllByRole('button', {name: 'Chat history'});
await userEvent.click(chatHistory!);
await userEvent.type(
await screen.findByPlaceholderText('Search chats\u2026'),
'nothing'
);
expect(await screen.findByText('No chats match your search.')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export function SeerExplorerHeader({
isPoppedOut,
sidebarPosition,
onSidebarPositionChange,
hasNoChats: !search && !debouncedSearch && !isError && data?.length === 0,
sessionOptions,
sessionsLoading: isFetching || isSearchPending,
sessionsError: isError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {IconWindow} from '@sentry/icons/window';
import {Button} from '@sentry/scraps/button';
import {CompactSelect} from '@sentry/scraps/compactSelect';
import {DropdownMenu, type MenuItemProps} from '@sentry/scraps/dropdownMenu';
import {Flex} from '@sentry/scraps/layout';
import {Flex, Stack} from '@sentry/scraps/layout';
import {OverlayTrigger} from '@sentry/scraps/overlayTrigger';
import {Text} from '@sentry/scraps/text';

import {t} from 'sentry/locale';
import type {
Expand All @@ -38,6 +39,7 @@ interface SessionOption {
* overflow menu.
*/
export interface SeerExplorerHeaderActionsProps {
hasNoChats: boolean;
isPipSupported: boolean;
isPoppedOut: boolean;
onChangeSession: (runId: SeerExplorerRunId) => void;
Expand All @@ -53,7 +55,34 @@ export interface SeerExplorerHeaderActionsProps {
sidebarPosition?: SeerExplorerSidebarPosition;
}

function getChatHistoryEmptyMessage({
sessionsError,
sessionsLoading,
}: Pick<SeerExplorerHeaderActionsProps, 'sessionsError' | 'sessionsLoading'>) {
if (sessionsError) {
return t('Error loading session history.');
}
// Options are cleared while a fetch is in flight, so keep the list blank
// rather than flashing an empty state before results arrive.
if (sessionsLoading) {
return ' ';
}
return t('No chats match your search.');
}

function ChatHistoryEmptyState() {
return (
<Stack align="center" gap="xs" padding="xl lg">
<Text bold>{t('No chats yet')}</Text>
<Text size="sm" variant="muted" align="center">
{t('Your past conversations with Seer will show up here.')}
</Text>
</Stack>
);
}

function ChatHistorySelect({
hasNoChats,
sessionOptions,
sessionsLoading,
sessionsError,
Expand All @@ -62,6 +91,7 @@ function ChatHistorySelect({
onSearchChange,
}: Pick<
SeerExplorerHeaderActionsProps,
| 'hasNoChats'
| 'sessionOptions'
| 'sessionsLoading'
| 'sessionsError'
Expand All @@ -77,12 +107,21 @@ function ChatHistorySelect({
onChange={option => onChangeSession(option.value)}
onOpenChange={onHistoryOpenChange}
loading={sessionsLoading}
emptyMessage={sessionsError ? t('Error loading session history.') : ' '}
search={{
filter: false,
onChange: onSearchChange,
placeholder: t('Search chats\u2026'),
}}
emptyMessage={getChatHistoryEmptyMessage({
sessionsError,
sessionsLoading,
})}
search={
hasNoChats
? undefined
: {
filter: false,
onChange: onSearchChange,
placeholder: t('Search chats\u2026'),
}
}
hideOptions={hasNoChats}
menuBody={hasNoChats ? <ChatHistoryEmptyState /> : undefined}
menuTitle={t('Chat history')}
// Seer's panel is a right-docked pane with `overflow: hidden` ancestors,
// and CompactSelect renders its overlay inline (no portal), so `strategy:
Expand Down Expand Up @@ -119,6 +158,7 @@ export function SeerExplorerHeaderActions({
onTogglePictureInPicture,
sidebarPosition = 'auto',
onSidebarPositionChange,
hasNoChats,
sessionOptions,
sessionsLoading,
sessionsError,
Expand Down Expand Up @@ -211,6 +251,7 @@ export function SeerExplorerHeaderActions({
/>
)}
<ChatHistorySelect
hasNoChats={hasNoChats}
sessionOptions={sessionOptions}
sessionsLoading={sessionsLoading}
sessionsError={sessionsError}
Expand All @@ -230,6 +271,7 @@ export function SeerExplorerHeaderActionsMenu({
onTogglePictureInPicture,
sidebarPosition = 'auto',
onSidebarPositionChange,
hasNoChats,
sessionOptions,
sessionsLoading,
sessionsError,
Expand Down Expand Up @@ -331,6 +373,7 @@ export function SeerExplorerHeaderActionsMenu({
collapsing into the overflow menu — the DropdownMenu has no search,
so a submenu couldn't offer the same filtering the inline actions do. */}
<ChatHistorySelect
hasNoChats={hasNoChats}
sessionOptions={sessionOptions}
sessionsLoading={sessionsLoading}
sessionsError={sessionsError}
Expand Down
Loading