Skip to content

Commit

Permalink
[8.x] [TableListView] Fix letters are skipped when typing fast (#194009
Browse files Browse the repository at this point in the history
…) (#194407)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[TableListView] Fix letters are skipped when typing fast
(#194009)](#194009)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Anton
Dosov","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-30T10:24:09Z","message":"[TableListView]
Fix letters are skipped when typing fast
(#194009)","sha":"23effbedd4f88219b71088b34d8e9221527e7a91","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:SharedUX","backport:prev-minor","Component:TableListView"],"title":"[TableListView]
Fix letters are skipped when typing
fast","number":194009,"url":"https://github.com/elastic/kibana/pull/194009","mergeCommit":{"message":"[TableListView]
Fix letters are skipped when typing fast
(#194009)","sha":"23effbedd4f88219b71088b34d8e9221527e7a91"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194009","number":194009,"mergeCommit":{"message":"[TableListView]
Fix letters are skipped when typing fast
(#194009)","sha":"23effbedd4f88219b71088b34d8e9221527e7a91"}}]}]
BACKPORT-->

Co-authored-by: Anton Dosov <[email protected]>
  • Loading branch information
kibanamachine and Dosant authored Sep 30, 2024
1 parent 7ba0966 commit 7709912
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1222,37 +1222,41 @@ describe('TableListView', () => {
describe('url state', () => {
let router: Router | undefined;

const setupTagFiltering = registerTestBed<string, TableListViewTableProps>(
WithServices<TableListViewTableProps>(TableListViewTable, {
getTagList: () => [
{
id: 'id-tag-1',
name: 'tag-1',
type: 'tag',
description: '',
color: '',
managed: false,
},
{
id: 'id-tag-2',
name: 'tag-2',
type: 'tag',
description: '',
color: '',
managed: false,
},
],
}),
{
defaultProps: { ...requiredProps, urlStateEnabled: true },
memoryRouter: {
wrapComponent: true,
onRouter: (_router: Router) => {
router = _router;
const setupInitialUrl = (initialSearchQuery: string = '') =>
registerTestBed<string, TableListViewTableProps>(
WithServices<TableListViewTableProps>(TableListViewTable, {
getTagList: () => [
{
id: 'id-tag-1',
name: 'tag-1',
type: 'tag',
description: '',
color: '',
managed: false,
},
{
id: 'id-tag-2',
name: 'tag-2',
type: 'tag',
description: '',
color: '',
managed: false,
},
],
}),
{
defaultProps: { ...requiredProps, urlStateEnabled: true },
memoryRouter: {
wrapComponent: true,
initialEntries: [{ search: initialSearchQuery }],
onRouter: (_router: Router) => {
router = _router;
},
},
},
}
);
}
);

const setupTagFiltering = setupInitialUrl();

const hits: UserContentCommonSchema[] = [
{
Expand All @@ -1277,13 +1281,13 @@ describe('TableListView', () => {
},
];

test('should read search term from URL', async () => {
test('should read the initial search term from URL', async () => {
let testBed: TestBed;

const findItems = jest.fn().mockResolvedValue({ total: hits.length, hits: [...hits] });

await act(async () => {
testBed = await setupTagFiltering({
testBed = await setupInitialUrl('?s=hello')({
findItems,
});
});
Expand All @@ -1294,22 +1298,23 @@ describe('TableListView', () => {
const getSearchBoxValue = () => find('tableListSearchBox').props().defaultValue;

// Start with empty search box
expect(getSearchBoxValue()).toBe('');
expect(router?.history.location?.search).toBe('');
expect(getSearchBoxValue()).toBe('hello');
expect(router?.history.location?.search).toBe('?s=hello');

// Change the URL
await act(async () => {
if (router?.history.push) {
router.history.push({
search: `?${queryString.stringify({ s: 'hello' }, { encode: false })}`,
search: `?${queryString.stringify({ s: '' }, { encode: false })}`,
});
}
});

component.update();

// Search box is updated
// Search box is not updated
expect(getSearchBoxValue()).toBe('hello');
expect(router?.history.location?.search).toBe('?s=hello');
expect(router?.history.location?.search).toBe('?s=');
});

test('should update the URL when changing the search term', async () => {
Expand Down Expand Up @@ -1338,13 +1343,15 @@ describe('TableListView', () => {
expect(router?.history.location?.search).toBe('?s=search-changed');
});

test('should filter by tag from the URL', async () => {
test('should filter by initial tag from the URL', async () => {
let testBed: TestBed;

const findItems = jest.fn().mockResolvedValue({ total: hits.length, hits: [...hits] });

await act(async () => {
testBed = await setupTagFiltering({
testBed = await setupInitialUrl(
`?${queryString.stringify({ s: 'tag:(tag-2)' }, { encode: false })}`
)({
findItems,
});
});
Expand All @@ -1357,7 +1364,7 @@ describe('TableListView', () => {

const getSearchBoxValue = () => find('tableListSearchBox').props().defaultValue;

let expected = '';
let expected = 'tag:(tag-2)';
let [searchTerm] = getLastCallArgsFromFindItems();
expect(getSearchBoxValue()).toBe(expected);
expect(searchTerm).toBe(expected);
Expand All @@ -1366,13 +1373,13 @@ describe('TableListView', () => {
await act(async () => {
if (router?.history.push) {
router.history.push({
search: `?${queryString.stringify({ s: 'tag:(tag-2)' }, { encode: false })}`,
search: `?${queryString.stringify({ s: '' }, { encode: false })}`,
});
}
});
component.update();

// The search bar should be updated
// The search bar shouldn't be updated
expected = 'tag:(tag-2)';
[searchTerm] = getLastCallArgsFromFindItems();
expect(getSearchBoxValue()).toBe(expected);
Expand Down Expand Up @@ -1413,51 +1420,34 @@ describe('TableListView', () => {
expect(router?.history.location?.search).toBe('?s=tag:(tag-2)');
});

test('should set sort column and direction from URL', async () => {
test('should set initial sort column and direction from URL', async () => {
let testBed: TestBed;

const findItems = jest.fn().mockResolvedValue({ total: hits.length, hits: [...hits] });

await act(async () => {
testBed = await setupTagFiltering({
testBed = await setupInitialUrl(
`?${queryString.stringify({ sort: 'updatedAt', sortdir: 'asc' })}`
)({
findItems,
});
});

const { component, table } = testBed!;
component.update();

// Start with empty search box
expect(router?.history.location?.search).toBe('');

let { tableCellsValues } = table.getMetaData('itemsInMemTable');

expect(tableCellsValues).toEqual([
['Item 1tag-1', yesterdayToString],
['Item 2tag-2', twoDaysAgoToString],
]);

// Change the URL
await act(async () => {
if (router?.history.push) {
router.history.push({
search: `?${queryString.stringify({ sort: 'updatedAt', sortdir: 'asc' })}`,
});
}
});
component.update();

({ tableCellsValues } = table.getMetaData('itemsInMemTable'));

expect(tableCellsValues).toEqual([
['Item 2tag-2', twoDaysAgoToString], // Sort got inverted
['Item 1tag-1', yesterdayToString],
]);

// Change the URL
await act(async () => {
if (router?.history.push) {
router.history.push({
search: `?${queryString.stringify({ sort: 'title' })}`, // if dir not specified, asc by default
search: `?${queryString.stringify({ sort: 'updatedAt', sortdir: 'desc' })}`,
});
}
});
Expand All @@ -1466,8 +1456,8 @@ describe('TableListView', () => {
({ tableCellsValues } = table.getMetaData('itemsInMemTable'));

expect(tableCellsValues).toEqual([
['Item 1tag-1', yesterdayToString],
['Item 2tag-2', twoDaysAgoToString],
['Item 1tag-1', yesterdayToString], // Sort stayed the same
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
);
}

const [urlState, setUrlState] = useUrlState<URLState, URLQueryParams>({
const [initialUrlState, setUrlState] = useUrlState<URLState, URLQueryParams>({
queryParamsDeserializer: urlStateDeserializer,
queryParamsSerializer: urlStateSerializer,
});
Expand Down Expand Up @@ -495,7 +495,6 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
(query: Query) => {
if (urlStateEnabled) {
setUrlState({ s: query.text });
return;
}

dispatch({
Expand Down Expand Up @@ -849,12 +848,10 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
});
}

if (data.page || !urlStateEnabled) {
dispatch({
type: 'onTableChange',
data,
});
}
dispatch({
type: 'onTableChange',
data,
});
},
[setUrlState, urlStateEnabled]
);
Expand Down Expand Up @@ -1024,6 +1021,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
// ------------
useDebounce(fetchItems, 300, [fetchItems, refreshListBouncer]);

// set the initial state from the URL
useEffect(() => {
if (!urlStateEnabled) {
return;
Expand Down Expand Up @@ -1075,10 +1073,10 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
});
};

updateQueryFromURL(urlState.s);
updateSortFromURL(urlState.sort);
updateFilterFromURL(urlState.filter);
}, [urlState, buildQueryFromText, urlStateEnabled]);
updateQueryFromURL(initialUrlState.s);
updateSortFromURL(initialUrlState.sort);
updateFilterFromURL(initialUrlState.filter);
}, [initialUrlState, buildQueryFromText, urlStateEnabled]);

useEffect(() => {
isMounted.current = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
*/

import queryString from 'query-string';
import { useCallback, useMemo, useState, useEffect } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import { useCallback, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';

export function useInRouterContext() {
try {
Expand All @@ -20,11 +20,6 @@ export function useInRouterContext() {
}
}

function useQuery<T extends Record<string, unknown> = {}>() {
const { search } = useLocation();
return useMemo<T>(() => queryString.parse(search) as T, [search]);
}

export function useUrlState<
T extends Record<string, unknown> = {},
Q extends Record<string, unknown> = {}
Expand All @@ -36,29 +31,26 @@ export function useUrlState<
queryParamsSerializer: (params: Record<string, unknown>) => Partial<Q>;
}): [T, (updated: Record<string, unknown>) => void] {
const history = useHistory();
const params = useQuery<Q>();
const [urlState, setUrlState] = useState<T>({} as T);

const updateQuerParams = useCallback(
const [initialUrlState] = useState(() =>
queryParamsDeserializer(queryString.parse(history.location.search) as Q)
);

const updateQueryParams = useCallback(
(updated: Record<string, unknown>) => {
const updatedQuery = queryParamsSerializer(updated);

const queryParams = {
...params,
...queryString.parse(history.location.search),
...updatedQuery,
};

history.replace({
search: `?${queryString.stringify(queryParams, { encode: false })}`,
});
},
[history, params, queryParamsSerializer]
[history, queryParamsSerializer]
);

useEffect(() => {
const updatedState = queryParamsDeserializer(params);
setUrlState(updatedState);
}, [params, queryParamsDeserializer]);

return [urlState, updateQuerParams];
return [initialUrlState, updateQueryParams];
}

0 comments on commit 7709912

Please sign in to comment.