Skip to content

Commit c5d28ef

Browse files
[7.10] [UX] Fix search term reset from url (#81654) (#81821)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 7ecedd1 commit c5d28ef

File tree

4 files changed

+117
-8
lines changed

4 files changed

+117
-8
lines changed

x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/SelectableUrlList.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React, {
1010
useRef,
1111
useState,
1212
KeyboardEvent,
13+
useEffect,
1314
} from 'react';
1415
import {
1516
EuiFlexGroup,
@@ -67,6 +68,7 @@ interface Props {
6768
searchValue: string;
6869
onClose: () => void;
6970
popoverIsOpen: boolean;
71+
initialValue?: string;
7072
setPopoverIsOpen: React.Dispatch<SetStateAction<boolean>>;
7173
}
7274

@@ -80,6 +82,7 @@ export function SelectableUrlList({
8082
onClose,
8183
popoverIsOpen,
8284
setPopoverIsOpen,
85+
initialValue,
8386
}: Props) {
8487
const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
8588

@@ -92,6 +95,9 @@ export function SelectableUrlList({
9295
if (evt.key.toLowerCase() === 'enter') {
9396
onTermChange();
9497
setPopoverIsOpen(false);
98+
if (searchRef) {
99+
searchRef.blur();
100+
}
95101
}
96102
};
97103

@@ -126,6 +132,16 @@ export function SelectableUrlList({
126132
}
127133
};
128134

135+
useEffect(() => {
136+
if (searchRef && initialValue) {
137+
searchRef.value = initialValue;
138+
}
139+
140+
// only want to call it at initial render to set value
141+
// coming from initial value/url
142+
// eslint-disable-next-line react-hooks/exhaustive-deps
143+
}, [searchRef]);
144+
129145
const loadingMessage = (
130146
<EuiSelectableMessage style={{ minHeight: 300 }}>
131147
<EuiLoadingSpinner size="l" />
@@ -165,12 +181,12 @@ export function SelectableUrlList({
165181
renderOption={selectableRenderOptions}
166182
singleSelection={false}
167183
searchProps={{
168-
placeholder: I18LABELS.searchByUrl,
169184
isClearable: true,
170185
onFocus: searchOnFocus,
171186
onBlur: searchOnBlur,
172187
onInput: onSearchInput,
173188
inputRef: setSearchRef,
189+
placeholder: I18LABELS.searchByUrl,
174190
}}
175191
listProps={{
176192
rowHeight: 68,
@@ -197,7 +213,7 @@ export function SelectableUrlList({
197213
<EuiText size="s">
198214
<FormattedMessage
199215
id="xpack.apm.ux.url.hitEnter.include"
200-
defaultMessage="Hit {icon} to include all urls matching {searchValue}"
216+
defaultMessage="Hit {icon} or click apply to include all urls matching {searchValue}"
201217
values={{
202218
searchValue: <strong>{searchValue}</strong>,
203219
icon: (
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import React from 'react';
7+
import { createMemoryHistory } from 'history';
8+
import * as fetcherHook from '../../../../../../hooks/useFetcher';
9+
import { SelectableUrlList } from '../SelectableUrlList';
10+
import { render } from '../../../utils/test_helper';
11+
12+
describe('SelectableUrlList', () => {
13+
it('it uses search term value from url', () => {
14+
jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({
15+
data: {},
16+
status: fetcherHook.FETCH_STATUS.SUCCESS,
17+
refetch: jest.fn(),
18+
});
19+
20+
const customHistory = createMemoryHistory({
21+
initialEntries: ['/?searchTerm=blog'],
22+
});
23+
24+
const { getByDisplayValue } = render(
25+
<SelectableUrlList
26+
initialValue={'blog'}
27+
loading={false}
28+
data={{ items: [], total: 0 }}
29+
onChange={jest.fn()}
30+
searchValue={'blog'}
31+
onClose={jest.fn()}
32+
onInputChange={jest.fn()}
33+
onTermChange={jest.fn()}
34+
popoverIsOpen={false}
35+
setPopoverIsOpen={jest.fn()}
36+
/>,
37+
{ customHistory }
38+
);
39+
expect(getByDisplayValue('blog')).toBeInTheDocument();
40+
});
41+
});

x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
3030

3131
const [popoverIsOpen, setPopoverIsOpen] = useState(false);
3232

33-
const [searchValue, setSearchValue] = useState('');
33+
const [searchValue, setSearchValue] = useState(searchTerm ?? '');
3434

35-
const [debouncedValue, setDebouncedValue] = useState('');
35+
const [debouncedValue, setDebouncedValue] = useState(searchTerm ?? '');
3636

3737
useDebounce(
3838
() => {
@@ -44,12 +44,16 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
4444

4545
const updateSearchTerm = useCallback(
4646
(searchTermN: string) => {
47+
const newQuery = {
48+
...toQuery(history.location.search),
49+
searchTerm: searchTermN || undefined,
50+
};
51+
if (!searchTermN) {
52+
delete newQuery.searchTerm;
53+
}
4754
const newLocation = {
4855
...history.location,
49-
search: fromQuery({
50-
...toQuery(history.location.search),
51-
searchTerm: searchTermN,
52-
}),
56+
search: fromQuery(newQuery),
5357
};
5458
history.push(newLocation);
5559
},
@@ -133,6 +137,7 @@ export function URLSearch({ onChange: onFilterChange }: Props) {
133137
<h4>{I18LABELS.url}</h4>
134138
</EuiTitle>
135139
<SelectableUrlList
140+
initialValue={searchTerm}
136141
loading={isLoading}
137142
onInputChange={onInputChange}
138143
onTermChange={onTermChange}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React from 'react';
8+
import { render as testLibRender } from '@testing-library/react';
9+
import { CoreStart } from 'kibana/public';
10+
import { of } from 'rxjs';
11+
import { createMemoryHistory } from 'history';
12+
import { Router } from 'react-router-dom';
13+
import { MemoryHistory } from 'history';
14+
import { EuiThemeProvider } from '../../../../../../observability/public';
15+
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
16+
import { UrlParamsProvider } from '../../../../context/UrlParamsContext';
17+
18+
export const core = ({
19+
http: {
20+
basePath: {
21+
prepend: jest.fn(),
22+
},
23+
},
24+
uiSettings: {
25+
get: (key: string) => true,
26+
get$: (key: string) => of(true),
27+
},
28+
} as unknown) as CoreStart;
29+
30+
export const render = (
31+
component: React.ReactNode,
32+
options: { customHistory: MemoryHistory }
33+
) => {
34+
const history = options?.customHistory ?? createMemoryHistory();
35+
36+
history.location.key = 'TestKeyForTesting';
37+
38+
return testLibRender(
39+
<Router history={history}>
40+
<KibanaContextProvider services={{ ...core }}>
41+
<UrlParamsProvider>
42+
<EuiThemeProvider>{component}</EuiThemeProvider>
43+
</UrlParamsProvider>
44+
</KibanaContextProvider>
45+
</Router>
46+
);
47+
};

0 commit comments

Comments
 (0)