Skip to content

Commit 4e3be12

Browse files
authored
Component to enable search-relevance and opensearch API calls (opensearch-project#490)
* Add code formatting Signed-off-by: Fen Qin <[email protected]> * Add switch to old and new search relevance workbench experience Signed-off-by: Fen Qin <[email protected]> * change name conventions for APIs Signed-off-by: Fen Qin <[email protected]> * add search-relevance backend router Signed-off-by: Fen Qin <[email protected]> --------- Signed-off-by: Fen Qin <[email protected]>
1 parent 72b85cb commit 4e3be12

File tree

17 files changed

+284
-52
lines changed

17 files changed

+284
-52
lines changed

.eslintrc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
extends:
3+
- "@elastic/eslint-config-kibana"
4+
- "plugin:@elastic/eui/recommended"

.lintstagedrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"*.{js,jsx,json,css,md}": ["prettier --write", "git add"]
3+
}

.prettierignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.vscode
2+
build
3+
coverage
4+
node_modules
5+
npm-debug.log
6+
yarn.lock
7+
*.md
8+
*.lock

.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "es5",
4+
"printWidth": 100
5+
}

babel.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
module.exports = {
7+
presets: [
8+
require('@babel/preset-env'),
9+
require('@babel/preset-react'),
10+
require('@babel/preset-typescript'),
11+
],
12+
plugins: [
13+
['@babel/plugin-transform-modules-commonjs', { allowTopLevelThis: true }],
14+
[require('@babel/plugin-transform-runtime'), { regenerator: true }],
15+
],
16+
};

common/index.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,38 @@ export const PLUGIN_NAME = 'Search Relevance';
88
export const COMPARE_SEARCH_RESULTS_TITLE = 'Compare Search Results';
99
export const SEARCH_RELEVANCE_WORKBENCH = 'Search Relevance Workbench';
1010

11-
export enum ServiceEndpoints {
12-
GetIndexes = '/api/relevancy/search/indexes',
13-
GetPipelines = '/api/relevancy/search/pipelines',
14-
GetSearchResults = '/api/relevancy/search',
15-
GetStats = '/api/relevancy/stats',
16-
}
11+
/**
12+
* BACKEND SEARCH RELEVANCE APIs
13+
*/
14+
export const SEARCH_RELEVANCE_BASE_API = '/_plugins/search_relevance';
15+
export const SEARCH_RELEVANCE_QUERY_SET_API = `${SEARCH_RELEVANCE_BASE_API}/queryset`;
1716

17+
/**
18+
* OPEN SEARCH CORE APIs
19+
*/
1820
export const SEARCH_API = '/_search';
1921

20-
//Query1 for the left search and Query2 for the right search page
22+
/**
23+
* Node APIs
24+
*/
25+
export const BASE_NODE_API_PATH = '/api/relevancy';
26+
27+
// OpenSearch node APIs
28+
export const INDEX_NODE_API_PATH = `${BASE_NODE_API_PATH}/search/indexes`;
29+
export const SEARCH_PIPELINE_NODE_API_PATH = `${BASE_NODE_API_PATH}/search/pipelines`;
30+
export const SEARCH_NODE_API_PATH = `${BASE_NODE_API_PATH}/search`;
31+
export const STATS_NODE_API_PATH = `${BASE_NODE_API_PATH}/stats`;
32+
33+
// Search Relevance node APIs
34+
export const BASE_QUERYSET_NODE_API_PATH = `${BASE_NODE_API_PATH}/queryset`;
35+
36+
export const DEFAULT_HEADERS = {
37+
'Content-Type': 'application/json',
38+
Accept: 'application/json',
39+
'User-Agent': 'OpenSearch-Dashboards',
40+
'osd-xsrf': true,
41+
};
42+
43+
// Query1 for the left search and Query2 for the right search page
2144
export const QUERY_NUMBER_ONE = '1';
2245
export const QUERY_NUMBER_TWO = '2';

public/components/app.tsx

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { EuiGlobalToastList } from '@elastic/eui';
6+
import {
7+
EuiButton,
8+
EuiButtonEmpty,
9+
EuiGlobalToastList,
10+
EuiModal,
11+
EuiModalBody,
12+
EuiModalFooter,
13+
EuiModalHeader,
14+
EuiSwitch,
15+
EuiModalHeaderTitle
16+
} from '@elastic/eui';
717
import { I18nProvider } from '@osd/i18n/react';
818
import React, { useState } from 'react';
919
import { HashRouter, Route, Switch } from 'react-router-dom';
@@ -13,7 +23,7 @@ import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/
1323
import { PLUGIN_NAME, COMPARE_SEARCH_RESULTS_TITLE } from '../../common';
1424
import { SearchRelevanceContextProvider } from '../contexts';
1525
import { Home as QueryCompareHome } from './query_compare/home';
16-
import { ExperimentPage } from "./experiment/experiment_page";
26+
import { ExperimentPage } from './experiment';
1727

1828
interface SearchRelevanceAppDeps {
1929
notifications: CoreStart['notifications'];
@@ -41,6 +51,9 @@ export const SearchRelevanceApp = ({
4151
const [toasts, setToasts] = useState<Toast[]>([]);
4252
const [toastRightSide, setToastRightSide] = useState<boolean>(true);
4353

54+
const [useOldVersion, setUseOldVersion] = useState(false);
55+
const [isModalVisible, setIsModalVisible] = useState(true);
56+
4457
// Render the application DOM.
4558
// Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract.
4659

@@ -55,6 +68,56 @@ export const SearchRelevanceApp = ({
5568
setToastRightSide(!side ? true : false);
5669
setToasts([...toasts, { id: new Date().toISOString(), title, text, color } as Toast]);
5770
};
71+
72+
const onToggleChange = (e) => {
73+
setUseOldVersion(e.target.checked);
74+
};
75+
76+
const closeModal = () => {
77+
setIsModalVisible(false);
78+
};
79+
80+
const selectVersion = (isOld: boolean) => {
81+
setUseOldVersion(isOld);
82+
closeModal();
83+
};
84+
85+
const versionModal = (
86+
<>
87+
{isModalVisible && (
88+
<EuiModal onClose={closeModal}>
89+
<EuiModalHeader>
90+
<EuiModalHeaderTitle>
91+
<h1>Select Version</h1>
92+
</EuiModalHeaderTitle>
93+
</EuiModalHeader>
94+
95+
<EuiModalBody>
96+
<p>Please select which version you would like to use:</p>
97+
</EuiModalBody>
98+
99+
<EuiModalFooter>
100+
<EuiButtonEmpty onClick={() => selectVersion(true)}>
101+
Use Old Version
102+
</EuiButtonEmpty>
103+
<EuiButton fill onClick={() => selectVersion(false)}>
104+
Use New Version
105+
</EuiButton>
106+
</EuiModalFooter>
107+
</EuiModal>
108+
)}
109+
</>
110+
);
111+
112+
const versionToggle = (
113+
<EuiSwitch
114+
label="Use Old Version"
115+
checked={useOldVersion}
116+
onChange={onToggleChange}
117+
style={{ marginBottom: '16px' }}
118+
/>
119+
);
120+
58121
return (
59122
<HashRouter>
60123
<I18nProvider>
@@ -69,17 +132,36 @@ export const SearchRelevanceApp = ({
69132
toastLifeTimeMs={6000}
70133
/>
71134
<Switch>
72-
<Route
73-
path={['/']}
74-
render={(props) => {
75-
return (
76-
<ExperimentPage
77-
application={application}
78-
chrome={chrome}
79-
/>
80-
);
81-
}}
82-
/>
135+
<Route
136+
path={['/']}
137+
render={(props) => {
138+
return (
139+
<>
140+
{versionModal}
141+
{versionToggle}
142+
143+
{useOldVersion ? (
144+
<QueryCompareHome
145+
application={application}
146+
parentBreadCrumbs={parentBreadCrumbs}
147+
notifications={notifications}
148+
http={http}
149+
navigation={navigation}
150+
setBreadcrumbs={chrome.setBreadcrumbs}
151+
setToast={setToast}
152+
chrome={chrome}
153+
savedObjects={savedObjects}
154+
dataSourceEnabled={dataSourceEnabled}
155+
dataSourceManagement={dataSourceManagement}
156+
setActionMenu={setActionMenu}
157+
/>
158+
) : (
159+
<ExperimentPage application={application} chrome={chrome} />
160+
)}
161+
</>
162+
);
163+
}}
164+
/>
83165
</Switch>
84166
</>
85167
</SearchRelevanceContextProvider>

public/components/query_compare/home.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import {
1616
DataSourceManagementPluginSetup,
1717
} from '../../../../../src/plugins/data_source_management/public';
1818
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
19-
import { QUERY_NUMBER_ONE, QUERY_NUMBER_TWO, ServiceEndpoints } from '../../../common';
19+
import {
20+
INDEX_NODE_API_PATH,
21+
QUERY_NUMBER_ONE,
22+
QUERY_NUMBER_TWO,
23+
SEARCH_PIPELINE_NODE_API_PATH,
24+
} from '../../../common';
2025
import '../../ace-themes/sql_console';
2126
import { useSearchRelevanceContext } from '../../contexts';
2227
import { DocumentsIndex } from '../../types/index';
@@ -82,9 +87,9 @@ export const Home = ({
8287
const [shouldShowCreateIndex, setShouldShowCreateIndex] = useState(false);
8388
const fetchIndexes = (dataConnectionId: string, queryNumber: string) => {
8489
http
85-
.get(`${ServiceEndpoints.GetIndexes}/${dataConnectionId}`)
90+
.get(`${INDEX_NODE_API_PATH}/${dataConnectionId}`)
8691
.then((res: DocumentsIndex[]) => {
87-
if (queryNumber == QUERY_NUMBER_ONE) {
92+
if (queryNumber === QUERY_NUMBER_ONE) {
8893
setDocumentsIndexes1(res);
8994
} else {
9095
setDocumentsIndexes2(res);
@@ -101,7 +106,7 @@ export const Home = ({
101106
};
102107
const fetchPipelines = (dataConnectionId: string, queryNumber: string) => {
103108
http
104-
.get(`${ServiceEndpoints.GetPipelines}/${dataConnectionId}`)
109+
.get(`${SEARCH_PIPELINE_NODE_API_PATH}/${dataConnectionId}`)
105110
.then((res: {}) => {
106111
if (queryNumber === QUERY_NUMBER_ONE) {
107112
setFetchedPipelines1(res);

public/components/query_compare/search_result/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { CoreStart, MountPoint } from '../../../../../../src/core/public';
1010
import { DataSourceManagementPluginSetup } from '../../../../../../src/plugins/data_source_management/public';
1111
import { DataSourceOption } from '../../../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector';
1212
import { NavigationPublicPluginStart } from '../../../../../../src/plugins/navigation/public';
13-
import { ServiceEndpoints } from '../../../../common';
1413
import { useSearchRelevanceContext } from '../../../contexts';
1514
import {
1615
QueryError,
@@ -23,6 +22,7 @@ import { Header } from '../../common/header';
2322
import { ResultComponents } from './result_components/result_components';
2423
import { SearchInputBar } from './search_components/search_bar';
2524
import { SearchConfigsPanel } from './search_components/search_configs/search_configs';
25+
import { SEARCH_NODE_API_PATH } from '../../../../common';
2626

2727
const DEFAULT_QUERY = '{}';
2828

@@ -61,7 +61,7 @@ export const SearchResult = ({ application, chrome, http, savedObjects, dataSour
6161
const HeaderControlledPopoverWrapper = ({ children }: { children: React.ReactElement }) => {
6262
const HeaderControl = navigation.ui.HeaderControl;
6363
const getNavGroupEnabled = chrome.navGroup.getNavGroupEnabled();
64-
64+
6565
if (getNavGroupEnabled && HeaderControl) {
6666
return (
6767
<HeaderControl
@@ -70,7 +70,7 @@ export const SearchResult = ({ application, chrome, http, savedObjects, dataSour
7070
/>
7171
);
7272
}
73-
73+
7474
return <>{children}</>;
7575
};
7676

@@ -158,7 +158,7 @@ export const SearchResult = ({ application, chrome, http, savedObjects, dataSour
158158
if (Object.keys(requestBody1).length !== 0 || Object.keys(requestBody2).length !== 0) {
159159
// First Query
160160
if (Object.keys(requestBody1).length !== 0) {
161-
http.post(ServiceEndpoints.GetSearchResults, {
161+
http.post(SEARCH_NODE_API_PATH, {
162162
body: JSON.stringify({ query1: requestBody1, dataSourceId1: datasource1? datasource1: '' }),
163163
})
164164
.then((res) => {
@@ -182,7 +182,7 @@ export const SearchResult = ({ application, chrome, http, savedObjects, dataSour
182182

183183
// Second Query
184184
if (Object.keys(requestBody2).length !== 0) {
185-
http.post(ServiceEndpoints.GetSearchResults, {
185+
http.post(SEARCH_NODE_API_PATH, {
186186
body: JSON.stringify({ query2: requestBody2, dataSourceId2: datasource2? datasource2: '' }),
187187
})
188188
.then((res) => {

public/services.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { BASE_QUERYSET_NODE_API_PATH } from '../common';
7+
8+
export const postQuerySet = async (id: string, http: any) => {
9+
try {
10+
return await http.post(`..${BASE_QUERYSET_NODE_API_PATH}`, {
11+
body: JSON.stringify({
12+
querySetId: id,
13+
}),
14+
});
15+
} catch (e) {
16+
return e;
17+
}
18+
};

server/clusters/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export * from './search_relevance_plugin';
7+
export * from './search_relevance_cluster';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { DEFAULT_HEADERS } from '../../common';
7+
import searchRelevancePlugin from './search_relevance_plugin';
8+
9+
export function createSearchRelevanceCluster(core: any, globalConfig: any) {
10+
const { customHeaders, ...rest } = globalConfig.opensearch;
11+
return core.opensearch.legacy.createClient('searchRelevance', {
12+
plugins: [searchRelevancePlugin],
13+
// Currently we are overriding any headers with our own since we explicitly required User-Agent to be OpenSearch Dashboards
14+
// for integration with our backend plugin.
15+
customHeaders: { ...customHeaders, ...DEFAULT_HEADERS },
16+
...rest,
17+
});
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { SEARCH_RELEVANCE_QUERY_SET_API } from '../../common';
7+
8+
/**
9+
* Register client actions representing search relevance plugin APIs.
10+
*/
11+
// eslint-disable-next-line import/no-default-export
12+
export default function searchRelevancePlugin(Client: any, config: any, components: any) {
13+
const ca = components.clientAction.factory;
14+
15+
Client.prototype.searchRelevance = components.clientAction.namespaceFactory();
16+
const searchRelevance = Client.prototype.searchRelevance.prototype;
17+
18+
searchRelevance.createQuerySet = ca({
19+
url: {
20+
fmt: `${SEARCH_RELEVANCE_QUERY_SET_API}`,
21+
},
22+
method: 'POST',
23+
});
24+
}

0 commit comments

Comments
 (0)