Skip to content

Commit 08a81e0

Browse files
committed
[Discover-next] Address comments for search bar extensions and query assist (opensearch-project#6933)
* pass dependencies to isEnabled func Signed-off-by: Joshua Li <joshuali925@gmail.com> * add lazy and memo to search bar extensions Signed-off-by: Joshua Li <joshuali925@gmail.com> * move ppl specific string out from query assist Signed-off-by: Joshua Li <joshuali925@gmail.com> * prevent setstate after hook unmounts Signed-off-by: Joshua Li <joshuali925@gmail.com> * add max-height to search bar extensions Signed-off-by: Joshua Li <joshuali925@gmail.com> * prevent setstate after component unmounts Signed-off-by: Joshua Li <joshuali925@gmail.com> * move ml-commons API to common/index.ts Signed-off-by: Joshua Li <joshuali925@gmail.com> * improve i18n and accessibility usages Signed-off-by: Joshua Li <joshuali925@gmail.com> * add hard-coded suggestions for sample data indices Signed-off-by: Joshua Li <joshuali925@gmail.com> --------- Signed-off-by: Joshua Li <joshuali925@gmail.com> (cherry picked from commit 4aade0f)
1 parent d90734a commit 08a81e0

File tree

7 files changed

+80
-73
lines changed

7 files changed

+80
-73
lines changed

src/plugins/data/public/ui/query_editor/query_editor_top_row.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ export default function QueryEditorTopRow(props: QueryEditorTopRowProps) {
255255
return (
256256
<SearchBarExtensions
257257
configs={props.queryEnhancements?.get(queryLanguage!)?.searchBar?.extensions}
258-
dependencies={{ indexPatterns: props.indexPatterns }}
259-
portalInsert={{ sibling: queryEditorHeaderRef.current, position: 'before' }}
258+
portalContainer={queryEditorHeaderRef.current}
259+
indexPatterns={props.indexPatterns}
260260
/>
261261
);
262262
}

src/plugins/data/public/ui/search_bar_extensions/index.ts

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React, { ComponentProps } from 'react';
7+
8+
const Fallback = () => <div />;
9+
10+
const LazySearchBarExtensions = React.lazy(() => import('./search_bar_extensions'));
11+
export const SearchBarExtensions = (props: ComponentProps<typeof LazySearchBarExtensions>) => (
12+
<React.Suspense fallback={<Fallback />}>
13+
<LazySearchBarExtensions {...props} />
14+
</React.Suspense>
15+
);
16+
17+
export { SearchBarExtensionConfig } from './search_bar_extension';

src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.test.tsx

+4-18
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import React, { ComponentProps } from 'react';
88
import { IIndexPattern } from '../../../common';
99
import { SearchBarExtension } from './search_bar_extension';
1010

11-
jest.mock('@elastic/eui', () => ({
12-
...jest.requireActual('@elastic/eui'),
13-
EuiPortal: jest.fn(({ children }) => <div>{children}</div>),
14-
EuiErrorBoundary: jest.fn(({ children }) => <div>{children}</div>),
11+
jest.mock('react-dom', () => ({
12+
...jest.requireActual('react-dom'),
13+
createPortal: jest.fn((element) => element),
1514
}));
1615

1716
type SearchBarExtensionProps = ComponentProps<typeof SearchBarExtension>;
@@ -45,7 +44,7 @@ describe('SearchBarExtension', () => {
4544
dependencies: {
4645
indexPatterns: [mockIndexPattern],
4746
},
48-
portalInsert: { sibling: document.createElement('div'), position: 'after' },
47+
portalContainer: document.createElement('div'),
4948
};
5049

5150
beforeEach(() => {
@@ -78,17 +77,4 @@ describe('SearchBarExtension', () => {
7877

7978
expect(isEnabledMock).toHaveBeenCalled();
8079
});
81-
82-
it('calls isEnabled and getComponent correctly', async () => {
83-
isEnabledMock.mockResolvedValue(true);
84-
getComponentMock.mockReturnValue(<div>Test Component</div>);
85-
86-
render(<SearchBarExtension {...defaultProps} />);
87-
88-
await waitFor(() => {
89-
expect(isEnabledMock).toHaveBeenCalled();
90-
});
91-
92-
expect(getComponentMock).toHaveBeenCalledWith(defaultProps.dependencies);
93-
});
9480
});

src/plugins/data/public/ui/search_bar_extensions/search_bar_extension.tsx

+19-10
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { EuiErrorBoundary, EuiPortal } from '@elastic/eui';
7-
import { EuiPortalProps } from '@opensearch-project/oui';
8-
import React, { useEffect, useMemo, useState } from 'react';
6+
import { EuiErrorBoundary } from '@elastic/eui';
7+
import React, { useEffect, useMemo, useRef, useState } from 'react';
8+
import ReactDOM from 'react-dom';
99
import { IIndexPattern } from '../../../common';
1010

1111
interface SearchBarExtensionProps {
1212
config: SearchBarExtensionConfig;
1313
dependencies: SearchBarExtensionDependencies;
14-
portalInsert: EuiPortalProps['insert'];
14+
portalContainer: Element;
1515
}
1616

1717
export interface SearchBarExtensionDependencies {
@@ -34,7 +34,7 @@ export interface SearchBarExtensionConfig {
3434
* A function that determines if the search bar extension is enabled and should be rendered on UI.
3535
* @returns whether the extension is enabled.
3636
*/
37-
isEnabled: () => Promise<boolean>;
37+
isEnabled: (dependencies: SearchBarExtensionDependencies) => Promise<boolean>;
3838
/**
3939
* A function that returns the mount point for the search bar extension.
4040
* @param dependencies - The dependencies required for the extension.
@@ -45,21 +45,30 @@ export interface SearchBarExtensionConfig {
4545

4646
export const SearchBarExtension: React.FC<SearchBarExtensionProps> = (props) => {
4747
const [isEnabled, setIsEnabled] = useState(false);
48+
const isMounted = useRef(true);
4849

4950
const component = useMemo(() => props.config.getComponent(props.dependencies), [
5051
props.config,
5152
props.dependencies,
5253
]);
5354

5455
useEffect(() => {
55-
props.config.isEnabled().then(setIsEnabled);
56+
isMounted.current = true;
57+
return () => {
58+
isMounted.current = false;
59+
};
60+
}, []);
61+
62+
useEffect(() => {
63+
props.config.isEnabled(props.dependencies).then((enabled) => {
64+
if (isMounted.current) setIsEnabled(enabled);
65+
});
5666
}, [props.dependencies, props.config]);
5767

5868
if (!isEnabled) return null;
5969

60-
return (
61-
<EuiPortal insert={props.portalInsert}>
62-
<EuiErrorBoundary>{component}</EuiErrorBoundary>
63-
</EuiPortal>
70+
return ReactDOM.createPortal(
71+
<EuiErrorBoundary>{component}</EuiErrorBoundary>,
72+
props.portalContainer
6473
);
6574
};

src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.test.tsx

+20-22
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { render, waitFor } from '@testing-library/react';
77
import React, { ComponentProps } from 'react';
88
import { SearchBarExtension } from './search_bar_extension';
9-
import { SearchBarExtensions } from './search_bar_extensions';
9+
import SearchBarExtensions from './search_bar_extensions';
1010

1111
type SearchBarExtensionProps = ComponentProps<typeof SearchBarExtension>;
1212
type SearchBarExtensionsProps = ComponentProps<typeof SearchBarExtensions>;
@@ -15,32 +15,30 @@ jest.mock('./search_bar_extension', () => ({
1515
SearchBarExtension: jest.fn(({ config, dependencies }: SearchBarExtensionProps) => (
1616
<div>
1717
Mocked SearchBarExtension {config.id} with{' '}
18-
{dependencies.indexPatterns?.map((i) => i.title).join(', ')}
18+
{dependencies.indexPatterns?.map((i) => (typeof i === 'string' ? i : i.title)).join(', ')}
1919
</div>
2020
)),
2121
}));
2222

2323
describe('SearchBarExtensions', () => {
2424
const defaultProps: SearchBarExtensionsProps = {
25-
dependencies: {
26-
indexPatterns: [
27-
{
28-
id: '1234',
29-
title: 'logstash-*',
30-
fields: [
31-
{
32-
name: 'response',
33-
type: 'number',
34-
esTypes: ['integer'],
35-
aggregatable: true,
36-
filterable: true,
37-
searchable: true,
38-
},
39-
],
40-
},
41-
],
42-
},
43-
portalInsert: { sibling: document.createElement('div'), position: 'after' },
25+
indexPatterns: [
26+
{
27+
id: '1234',
28+
title: 'logstash-*',
29+
fields: [
30+
{
31+
name: 'response',
32+
type: 'number',
33+
esTypes: ['integer'],
34+
aggregatable: true,
35+
filterable: true,
36+
searchable: true,
37+
},
38+
],
39+
},
40+
],
41+
portalContainer: document.createElement('div'),
4442
};
4543

4644
beforeEach(() => {
@@ -89,7 +87,7 @@ describe('SearchBarExtensions', () => {
8987

9088
expect(SearchBarExtension).toHaveBeenCalledWith(
9189
expect.objectContaining({
92-
dependencies: defaultProps.dependencies,
90+
dependencies: { indexPatterns: defaultProps.indexPatterns },
9391
}),
9492
expect.anything()
9593
);

src/plugins/data/public/ui/search_bar_extensions/search_bar_extensions.tsx

+18-14
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,49 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { EuiPortalProps } from '@elastic/eui';
76
import React, { useMemo } from 'react';
87
import {
98
SearchBarExtension,
109
SearchBarExtensionConfig,
1110
SearchBarExtensionDependencies,
1211
} from './search_bar_extension';
1312

14-
interface SearchBarExtensionsProps {
13+
interface SearchBarExtensionsProps extends SearchBarExtensionDependencies {
1514
configs?: SearchBarExtensionConfig[];
16-
dependencies: SearchBarExtensionDependencies;
17-
portalInsert: EuiPortalProps['insert'];
15+
portalContainer: Element;
1816
}
1917

20-
export const SearchBarExtensions: React.FC<SearchBarExtensionsProps> = (props) => {
21-
const configs = useMemo(() => {
22-
if (!props.configs) return [];
18+
const SearchBarExtensions: React.FC<SearchBarExtensionsProps> = React.memo((props) => {
19+
const { configs, portalContainer, ...dependencies } = props;
20+
21+
const sortedConfigs = useMemo(() => {
22+
if (!configs) return [];
2323

2424
const seenIds = new Set();
25-
props.configs.forEach((config) => {
25+
configs.forEach((config) => {
2626
if (seenIds.has(config.id)) {
2727
throw new Error(`Duplicate search bar extension id '${config.id}' found.`);
2828
}
2929
seenIds.add(config.id);
3030
});
3131

32-
return [...props.configs].sort((a, b) => a.order - b.order);
33-
}, [props.configs]);
32+
return [...configs].sort((a, b) => a.order - b.order);
33+
}, [configs]);
3434

3535
return (
3636
<>
37-
{configs.map((config) => (
37+
{sortedConfigs.map((config) => (
3838
<SearchBarExtension
3939
key={config.id}
4040
config={config}
41-
dependencies={props.dependencies}
42-
portalInsert={props.portalInsert}
41+
dependencies={dependencies}
42+
portalContainer={portalContainer}
4343
/>
4444
))}
4545
</>
4646
);
47-
};
47+
});
48+
49+
// Needed for React.lazy
50+
// eslint-disable-next-line import/no-default-export
51+
export default SearchBarExtensions;

0 commit comments

Comments
 (0)