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
11 changes: 11 additions & 0 deletions .changeset/quiet-pugs-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@hyperdx/app': patch
---

fix: keep the LLM dashboard scope filters clearable when their options fail to
load

The session and user dropdowns were disabled whenever their distinct-value query
was loading or had failed. With a filter applied that left the user looking at a
scope they could see but could not remove — permanently, if the query kept
failing. They now stay interactive whenever a value is applied.
32 changes: 31 additions & 1 deletion packages/app/src/llm/__tests__/DistinctValueSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { DistinctValueSelect } from '@/llm/dashboard/DistinctValueSelect';

let mockRows: Record<string, unknown>[] | undefined = [];
let mockLoading = false;
let mockError = false;
jest.mock('@/hooks/useChartConfig', () => ({
useQueriedChartConfig: () => ({
data: mockRows == null ? undefined : { data: mockRows },
isLoading: mockLoading,
isError: false,
isError: mockError,
}),
}));

Expand All @@ -28,6 +29,7 @@ describe('DistinctValueSelect', () => {
beforeEach(() => {
mockRows = [];
mockLoading = false;
mockError = false;
});

it('shows the applied value when it is present in the fetched options', () => {
Expand Down Expand Up @@ -83,6 +85,34 @@ describe('DistinctValueSelect', () => {
expect(onChange).not.toHaveBeenCalled();
});

// Otherwise a failing distinct query strands the user with a filter they
// can see but cannot remove.
it('stays interactive while a value is applied even if the query fails', () => {
mockRows = undefined;
mockError = true;
renderWithMantine(
<DistinctValueSelect
{...baseProps}
value="alice@x.com"
onChange={jest.fn()}
/>,
);

const input = screen.getByRole('combobox');
expect(input).toHaveValue('alice@x.com');
expect(input).not.toBeDisabled();

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.

🔵 minor — New test asserts not.toBeDisabled() as a proxy instead of exercising the clear the PR is about

The regression that matters is that the user cannot remove the filter, and that depends on Mantine rendering the clear × (which requires !disabled && !readOnly and clearable) — expect(input).not.toBeDisabled() still passes if a later change adds readOnly, drops clearable, or supplies a rightSection. Click the rendered clear button and assert onChange was called with ''.

});

it('disables the control while loading with nothing applied', () => {
mockRows = undefined;
mockLoading = true;
renderWithMantine(
<DistinctValueSelect {...baseProps} value="" onChange={jest.fn()} />,
);

expect(screen.getByRole('combobox')).toBeDisabled();
});

it('falls back to the placeholder when no value is applied', () => {
mockRows = [{ value: 'bob@x.com' }];
renderWithMantine(
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/llm/dashboard/DistinctValueSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ export function DistinctValueSelect({
data={values}
value={value || null}
onChange={v => onChange(v ?? '')}
disabled={isLoading || isError}
// Never lock the control while a scope is applied: the options are of no
// use yet, but clearing the filter must stay possible. On a failing
// distinct query that is otherwise permanent.
disabled={(isLoading || isError) && !value}

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.

🔵 minor — Enabling the control on error removes the only signal that the option list failed to load

With a value applied and isError true the select is now interactive but shows a dropdown containing just allLabel and the pinned value, so the user reads "this workspace has one user" rather than "the query failed" (nothingFoundMessage never fires because the list is non-empty). Pass the failure through to the control, e.g. error={isError} (optionally with a short message), so the state that used to be conveyed by disabled is still visible.

comboboxProps={{ withinPortal: false }}
searchable
clearable
Expand Down
Loading