Tempo: Virtualized search dropdowns for attribute values (#88569)

* TraceQL: Make the search field component use the virtualized prop for performance gains

* Use act on timers to eliminate warningsd

* Update more timers to use act to fix testing library warnings

* Fix linting failure

* Run make commands

---------

Co-authored-by: Joey Tawadrous <joey.tawadrous@grafana.com>
This commit is contained in:
Ronan 2024-06-18 08:20:06 +01:00 committed by GitHub
parent ea7f6ea32f
commit 3c5f07201e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 44 additions and 16 deletions

View File

@ -19970,7 +19970,6 @@
}
},
"State": {
"description": "+enum",
"type": "string"
},
"Status": {
@ -20730,7 +20729,6 @@
}
},
"Type": {
"description": "+enum",
"type": "string"
},
"TypeMeta": {

View File

@ -1,4 +1,4 @@
import { render, screen, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
@ -86,20 +86,30 @@ describe('SearchField', () => {
if (select) {
// Add first value
await user.click(select);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
const driverVal = await screen.findByText('driver');
await user.click(driverVal);
await act(async () => {
await user.click(driverVal);
});
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['driver'] });
// Add a second value
await user.click(select);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
const customerVal = await screen.findByText('customer');
await user.click(customerVal);
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['driver', 'customer'] });
// Remove the first value
const firstValRemove = await screen.findAllByLabelText('Remove');
await user.click(firstValRemove[0]);
expect(updateFilter).toHaveBeenCalledWith({ ...filter, value: ['customer'] });
}

View File

@ -183,6 +183,12 @@ const SearchField = ({
/>
{!hideValue && (
<Select
/**
* Trace cardinality means we need to use the virtualized variant of the Select component.
* For example the number of span names being returned can easily reach 10s of thousands,
* which is enough to cause a user's web browser to seize up
*/
virtualized
className={styles.dropdown}
inputId={`${filter.id}-value`}
isLoading={isLoadingValues}

View File

@ -114,25 +114,37 @@ describe('TraceQLSearch', () => {
});
await user.click(screen.getByText('Select tag'));
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
await user.click(screen.getByText('foo'));
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
await user.click(screen.getAllByText('Select value')[2]);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
await user.click(screen.getByText('driver'));
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
await act(async () => {
expect(screen.getAllByLabelText('Add tag').length).toBe(1);
expect(screen.getAllByLabelText(/Remove tag/).length).toBe(1);
});
await user.click(screen.getByLabelText('Add tag'));
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the new tag, so no need to add another one
expect(screen.getAllByLabelText(/Remove tag/).length).toBe(2); // one for each tag
await user.click(screen.getAllByLabelText(/Remove tag/)[1]);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
expect(screen.queryAllByLabelText('Add tag').length).toBe(1); // filled in the default tag, so can add another one
expect(screen.queryAllByLabelText(/Remove tag/).length).toBe(1); // filled in the default tag, so can remove values
@ -155,7 +167,9 @@ describe('TraceQLSearch', () => {
if (minDurationOperator) {
await user.click(minDurationOperator);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
const regexOp = await screen.findByText('>=');
await user.click(regexOp);
const minDurationFilter = query.filters.find((f) => f.id === 'min-duration');
@ -176,7 +190,9 @@ describe('TraceQLSearch', () => {
if (serviceNameValue) {
await user.click(serviceNameValue);
jest.advanceTimersByTime(1000);
await act(async () => {
jest.advanceTimersByTime(1000);
});
const customerValue = await screen.findByText('customer');
await user.click(customerValue);
const nameFilter = query.filters.find((f) => f.id === 'service-name');

View File

@ -10349,7 +10349,6 @@
"type": "object"
},
"State": {
"description": "+enum",
"type": "string"
},
"Status": {
@ -11109,7 +11108,6 @@
"type": "array"
},
"Type": {
"description": "+enum",
"type": "string"
},
"TypeMeta": {