Skip to content

Commit d22039b

Browse files
authored
fix: show list options even in traces explorer empty / error states (#9339)
1 parent 457b697 commit d22039b

File tree

2 files changed

+204
-18
lines changed

2 files changed

+204
-18
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { rest, server } from 'mocks-server/server';
2+
import { render, screen, waitFor } from 'tests/test-utils';
3+
4+
import ListView from './index';
5+
6+
// Helper to create error response
7+
const createErrorResponse = (
8+
status: number,
9+
code: string,
10+
message: string,
11+
): {
12+
httpStatusCode: number;
13+
error: {
14+
code: string;
15+
message: string;
16+
url: string;
17+
errors: unknown[];
18+
};
19+
} => ({
20+
httpStatusCode: status,
21+
error: {
22+
code,
23+
message,
24+
url: '',
25+
errors: [],
26+
},
27+
});
28+
29+
// Helper to create MSW error handler
30+
const createErrorHandler = (
31+
status: number,
32+
code: string,
33+
message: string,
34+
): ReturnType<typeof rest.post> =>
35+
rest.post(/query-range/, (_req, res, ctx) =>
36+
res(ctx.status(status), ctx.json(createErrorResponse(status, code, message))),
37+
);
38+
39+
// Helper to render with required props
40+
const renderListView = (
41+
props: Record<string, unknown> = {},
42+
): ReturnType<typeof render> => {
43+
const setWarning = jest.fn();
44+
const setIsLoadingQueries = jest.fn();
45+
return render(
46+
<ListView
47+
isFilterApplied={false}
48+
setWarning={setWarning}
49+
setIsLoadingQueries={setIsLoadingQueries}
50+
// eslint-disable-next-line react/jsx-props-no-spreading
51+
{...props}
52+
/>,
53+
undefined,
54+
{ initialRoute: '/traces-explorer' },
55+
);
56+
};
57+
58+
// Helper to verify all controls are visible
59+
const verifyControlsVisibility = (): void => {
60+
// Order by controls
61+
expect(screen.getByText(/Order by/i)).toBeInTheDocument();
62+
63+
// Pagination controls
64+
expect(screen.getByRole('button', { name: /previous/i })).toBeInTheDocument();
65+
expect(screen.getByRole('button', { name: /next/i })).toBeInTheDocument();
66+
67+
// Items per page selector (there are multiple comboboxes, so we check for at least 2)
68+
const comboboxes = screen.getAllByRole('combobox');
69+
expect(comboboxes.length).toBeGreaterThanOrEqual(2);
70+
71+
// Options menu (settings button) - check for translation key or actual text
72+
expect(screen.getByText(/options_menu.options|options/i)).toBeInTheDocument();
73+
};
74+
75+
describe('Traces ListView - Error and Empty States', () => {
76+
afterEach(() => {
77+
jest.clearAllMocks();
78+
});
79+
80+
describe('Empty State', () => {
81+
it('shows all controls and empty state when filters are applied but no results', async () => {
82+
// MSW default returns empty result
83+
renderListView({ isFilterApplied: true });
84+
85+
// All controls should be visible
86+
verifyControlsVisibility();
87+
88+
// Empty state with filter message should be visible
89+
await waitFor(() => {
90+
expect(screen.getByText(/This query had no results/i)).toBeInTheDocument();
91+
});
92+
});
93+
});
94+
95+
describe('Error States', () => {
96+
it('shows all controls when API returns 400 error', async () => {
97+
server.use(createErrorHandler(400, 'BadRequestError', 'Bad Request'));
98+
99+
renderListView();
100+
101+
// All controls should be visible even when there's an error
102+
verifyControlsVisibility();
103+
104+
// Wait for the component to render
105+
await waitFor(() => {
106+
expect(screen.getByText(/Order by/i)).toBeInTheDocument();
107+
});
108+
});
109+
110+
it('shows all controls when API returns 500 error', async () => {
111+
server.use(
112+
createErrorHandler(500, 'InternalServerError', 'Internal Server Error'),
113+
);
114+
115+
renderListView();
116+
117+
// All controls should be visible even when there's an error
118+
verifyControlsVisibility();
119+
120+
// Wait for the component to render
121+
await waitFor(() => {
122+
expect(screen.getByText(/Order by/i)).toBeInTheDocument();
123+
});
124+
});
125+
126+
it('shows all controls when API returns network error', async () => {
127+
server.use(
128+
rest.post(/query-range/, (_req, res) =>
129+
res.networkError('Failed to connect'),
130+
),
131+
);
132+
133+
renderListView();
134+
135+
// All controls should be visible even when there's an error
136+
verifyControlsVisibility();
137+
138+
// Wait for the component to render
139+
await waitFor(() => {
140+
expect(screen.getByText(/Order by/i)).toBeInTheDocument();
141+
});
142+
});
143+
});
144+
145+
describe('Controls Functionality', () => {
146+
it('allows interaction with controls in error state', async () => {
147+
server.use(createErrorHandler(400, 'BadRequestError', 'Bad Request'));
148+
149+
renderListView();
150+
151+
// Wait for component to render
152+
await waitFor(() => {
153+
expect(screen.getByText(/Order by/i)).toBeInTheDocument();
154+
});
155+
156+
// Order by controls should be interactive
157+
const comboboxes = screen.getAllByRole('combobox');
158+
expect(comboboxes.length).toBeGreaterThanOrEqual(2);
159+
160+
// Pagination controls should be present
161+
const previousButton = screen.getByRole('button', { name: /previous/i });
162+
const nextButton = screen.getByRole('button', { name: /next/i });
163+
expect(previousButton).toBeInTheDocument();
164+
expect(nextButton).toBeInTheDocument();
165+
166+
// Options menu should be clickable
167+
const optionsButton = screen.getByText(/options_menu.options|options/i);
168+
expect(optionsButton).toBeInTheDocument();
169+
});
170+
171+
it('allows interaction with controls in empty state', async () => {
172+
renderListView();
173+
174+
// Wait for empty state
175+
await waitFor(() => {
176+
expect(screen.getByText(/No traces yet/i)).toBeInTheDocument();
177+
});
178+
179+
// All controls should be interactive
180+
const comboboxes = screen.getAllByRole('combobox');
181+
expect(comboboxes.length).toBeGreaterThanOrEqual(2);
182+
183+
// Options menu should be clickable
184+
const optionsButton = screen.getByText(/options_menu.options|options/i);
185+
expect(optionsButton).toBeInTheDocument();
186+
});
187+
});
188+
});

frontend/src/container/TracesExplorer/ListView/index.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -242,28 +242,26 @@ function ListView({
242242
}, [isLoading, isFetching, isError, transformedQueryTableData, panelType]);
243243
return (
244244
<Container>
245-
{transformedQueryTableData.length !== 0 && (
246-
<div className="trace-explorer-controls">
247-
<div className="order-by-container">
248-
<div className="order-by-label">
249-
Order by <Minus size={14} /> <ArrowUp10 size={14} />
250-
</div>
251-
252-
<ListViewOrderBy
253-
value={orderBy}
254-
onChange={handleOrderChange}
255-
dataSource={DataSource.TRACES}
256-
/>
245+
<div className="trace-explorer-controls">
246+
<div className="order-by-container">
247+
<div className="order-by-label">
248+
Order by <Minus size={14} /> <ArrowUp10 size={14} />
257249
</div>
258250

259-
<TraceExplorerControls
260-
isLoading={isFetching}
261-
totalCount={totalCount}
262-
config={config}
263-
perPageOptions={PER_PAGE_OPTIONS}
251+
<ListViewOrderBy
252+
value={orderBy}
253+
onChange={handleOrderChange}
254+
dataSource={DataSource.TRACES}
264255
/>
265256
</div>
266-
)}
257+
258+
<TraceExplorerControls
259+
isLoading={isFetching}
260+
totalCount={totalCount}
261+
config={config}
262+
perPageOptions={PER_PAGE_OPTIONS}
263+
/>
264+
</div>
267265

268266
{isError && error && <ErrorInPlace error={error as APIError} />}
269267

0 commit comments

Comments
 (0)