Skip to content

Commit 9bd2738

Browse files
committed
Add DEVELOPER_GUIDE.md for router setup + fix missing files (opensearch-project#493)
* fix missing route service files Signed-off-by: Fen Qin <[email protected]> * add DEVELOPER_GUIDE.md for middleware layer router setup for APIs Signed-off-by: Fen Qin <[email protected]> --------- Signed-off-by: Fen Qin <[email protected]>
1 parent b5fc9a8 commit 9bd2738

File tree

7 files changed

+384
-9
lines changed

7 files changed

+384
-9
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React, { useState } from 'react';
7+
import {
8+
EuiText,
9+
EuiFieldText,
10+
EuiButton,
11+
EuiForm,
12+
EuiFormRow,
13+
EuiSpacer,
14+
EuiPanel,
15+
EuiCallOut,
16+
EuiCodeBlock,
17+
} from '@elastic/eui';
18+
import { postQuerySet } from '../../services';
19+
import { CoreStart } from '../../../../../src/core/public';
20+
21+
export interface TestProps {
22+
http: CoreStart['http'];
23+
}
24+
export const QuerySetTester = ({ http }: TestProps) => {
25+
const [querySetId, setQuerySetId] = useState('');
26+
const [response, setResponse] = useState(null);
27+
const [isLoading, setIsLoading] = useState(false);
28+
const [error, setError] = useState(null);
29+
30+
const handleSubmit = async (e) => {
31+
e.preventDefault();
32+
setIsLoading(true);
33+
setError(null);
34+
35+
try {
36+
const result = await postQuerySet(querySetId, http);
37+
setResponse(result);
38+
} catch (err) {
39+
setError(err.message || 'An error occurred');
40+
} finally {
41+
setIsLoading(false);
42+
}
43+
};
44+
45+
return (
46+
<EuiPanel paddingSize="l">
47+
<EuiText>
48+
<h2>Query Set Tester</h2>
49+
</EuiText>
50+
<EuiSpacer size="m" />
51+
<EuiForm component="form" onSubmit={handleSubmit}>
52+
<EuiFormRow label="Query Set ID:">
53+
<EuiFieldText
54+
placeholder="Enter Query Set ID"
55+
value={querySetId}
56+
onChange={(e) => setQuerySetId(e.target.value)}
57+
fullWidth
58+
/>
59+
</EuiFormRow>
60+
<EuiSpacer size="m" />
61+
<EuiButton type="submit" fill isLoading={isLoading}>
62+
{isLoading ? 'Sending...' : 'Send Request'}
63+
</EuiButton>
64+
</EuiForm>
65+
<EuiSpacer size="l" />
66+
67+
{error && (
68+
<EuiCallOut title="Error" color="danger" iconType="alert">
69+
<p>{error}</p>
70+
</EuiCallOut>
71+
)}
72+
73+
{response && (
74+
<>
75+
<EuiText>
76+
<h3>Response:</h3>
77+
</EuiText>
78+
<EuiSpacer size="s" />
79+
<EuiCodeBlock language="json" paddingSize="m" isCopyable>
80+
{JSON.stringify(response, null, 2)}
81+
</EuiCodeBlock>
82+
</>
83+
)}
84+
</EuiPanel>
85+
);
86+
};
87+
88+
export default QuerySetTester;

public/plugin_nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ export function registerAllPluginNavGroups(core: CoreSetup<SearchRelevancePlugin
3535
showInAllNavGroup: true
3636
},
3737
]);
38-
}
38+
}

server/DEVELOPER_GUIDE.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
## Developer Guide
2+
3+
This developer guide explains how to add new APIs to the middleware layer.
4+
5+
6+
To add search-relevance related operations, directory structure should look like this:
7+
8+
```md
9+
.
10+
├── dashboards-search-relevance
11+
│ └── common
12+
│ └── index.ts
13+
│ └── public
14+
│ └── services.ts
15+
│ └── server
16+
│ └── routes
17+
│ └── search_relevance_route_service.ts
18+
19+
```
20+
#### Step1 - add common index
21+
define your backend APIs and its node APIs as common index under `common/index.ts`
22+
- [backend search relevance APIs](https://github.com/opensearch-project/dashboards-search-relevance/blob/evaluation_lab/common/index.ts#L12)
23+
```
24+
/**
25+
* BACKEND SEARCH RELEVANCE APIs
26+
*/
27+
export const SEARCH_RELEVANCE_QUERY_SET_API = `${SEARCH_RELEVANCE_BASE_API}/queryset`;
28+
```
29+
- [node APIs](https://github.com/opensearch-project/dashboards-search-relevance/blob/evaluation_lab/common/index.ts#L23)
30+
```
31+
/**
32+
* Node APIs
33+
*/
34+
export const BASE_QUERYSET_NODE_API_PATH = `${BASE_NODE_API_PATH}/queryset`;
35+
```
36+
37+
38+
#### Step2 - client-side API
39+
add public-facing API routing under `public/service.ts`. When a user create a queryset, the application makes a POST request to ../api/relevancy/queryset path
40+
```
41+
export const postQuerySet = async (id: string, http: any) => {
42+
try {
43+
return await http.post(`..${BASE_QUERYSET_NODE_API_PATH}`, {
44+
body: JSON.stringify({
45+
querySetId: id,
46+
}),
47+
});
48+
} catch (e) {
49+
return e;
50+
}
51+
};
52+
```
53+
54+
55+
#### Step3 - server-side API
56+
add router `server/routes/search_relevance_route_service.ts` to route your node APIs to the functions. It's recommended to add data_source_id enabled router as well.
57+
```
58+
router.post(
59+
{
60+
path: BASE_QUERYSET_NODE_API_PATH,
61+
validate: {
62+
body: schema.any(),
63+
},
64+
options: {
65+
body: {
66+
accepts: 'application/json',
67+
},
68+
},
69+
},
70+
searchRelevanceRoutesService.createQuerySet
71+
);
72+
```
73+
add function definition under the same path `server/routes/search_relevance_route_service.ts`
74+
```
75+
createQuerySet = async (
76+
context: RequestHandlerContext,
77+
req: OpenSearchDashboardsRequest,
78+
res: OpenSearchDashboardsResponseFactory
79+
): Promise<IOpenSearchDashboardsResponse<any>> => {
80+
const body = req.body;
81+
const { data_source_id = '' } = req.params as { data_source_id?: string };
82+
try {
83+
const callWithRequest = getClientBasedOnDataSource(
84+
context,
85+
this.dataSourceEnabled,
86+
req,
87+
data_source_id,
88+
this.client
89+
);
90+
91+
const querysetResponse = await callWithRequest('searchRelevance.createQuerySet', {
92+
body,
93+
});
94+
95+
return res.ok({
96+
body: {
97+
ok: true,
98+
resp: querysetResponse,
99+
},
100+
});
101+
} catch (err) {
102+
return res.ok({
103+
body: {
104+
ok: false,
105+
resp: err.message,
106+
},
107+
});
108+
}
109+
};
110+
```
111+
112+
add server-side API routing under `server/clusters/search_relevance_plugin.ts` to make calls to backend APIs.
113+
```
114+
searchRelevance.createQuerySet = ca({
115+
url: {
116+
fmt: `${SEARCH_RELEVANCE_QUERY_SET_API}`,
117+
},
118+
method: 'POST',
119+
});
120+
```
121+
122+
### Simple page testing
123+
This page testing is not recommended after we have a page/workflow.
124+
jest tests should be added components by components, this is a quick example for connector tests, since we don't have any workflows/pages ready yet.
125+
126+
127+
To verify your api connection, your directory should look like this:
128+
129+
```md
130+
.
131+
├── dashboards-search-relevance
132+
│ └── public
133+
│ └── components
134+
│ └── api
135+
│ └── search_relevance_testing_page.tsx
136+
│ └── app.tsx
137+
138+
```
139+
#### step1 - change your API calls
140+
The original testing page `search_relevance_testing_page.tsx` is pointing to postQuerySet function. you can replace with your new function.
141+
```
142+
const result = await postQuerySet(querySetId, http);
143+
```
144+
145+
#### step2 - change your application landing page
146+
You can change your landing page to point to your testing page under `app.tsx`.
147+
Currently, the application is pointing to ExperimentPage if user opted in for the new version or QueryCompareHome.
148+
149+
By replacing ~~`<ExperimentPage application={application} chrome={chrome} />`~~
150+
with `<QuerySetTester http={http} />`
151+
152+
- spin up your backend cluster, for example for search-relevance plugin, run `./gradlew run`
153+
- start your frontend server, make sure your frontend plugins under OpenSearch-Dashboard like `OpenSearch-Dashboard/plugins/dashboard-search-relevance`, then under `OpenSearch-Dashboard` run `yarn start`
154+
- now, you able to open a page `http://localhost:5603/{uuid}`
155+
- you can now test your api and your backend api response should be returned and rendered after click.
156+
- more detailed can be found from this [issue](https://github.com/opensearch-project/dashboards-search-relevance/pull/490)
157+
158+
159+
160+
161+

server/common/helper.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import {
7+
ILegacyClusterClient,
8+
OpenSearchDashboardsRequest,
9+
RequestHandlerContext,
10+
} from '../../../../src/core/server';
11+
12+
export function getClientBasedOnDataSource(
13+
context: RequestHandlerContext,
14+
dataSourceEnabled: boolean,
15+
request: OpenSearchDashboardsRequest,
16+
dataSourceId: string,
17+
client: ILegacyClusterClient
18+
): (endpoint: string, clientParams?: Record<string, any>) => any {
19+
if (dataSourceEnabled && dataSourceId && dataSourceId.trim().length != 0) {
20+
// client for remote cluster
21+
return context.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI;
22+
} else {
23+
// fall back to default local cluster
24+
return client.asScoped(request).callAsCurrentUser;
25+
}
26+
}

server/plugin.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
CoreStart,
1212
Logger,
1313
Plugin,
14-
PluginInitializerContext
14+
PluginInitializerContext,
1515
} from '../../../src/core/server';
1616
import {
1717
defineRoutes,
1818
registerSearchRelevanceRoutes,
19-
SearchRelevanceRoutesService
19+
SearchRelevanceRoutesService,
2020
} from './routes';
2121

2222
import { DataSourcePluginSetup } from '../../../src/plugins/data_source/server/types';
@@ -32,8 +32,7 @@ export interface SearchRelevancePluginSetupDependencies {
3232
}
3333

3434
export class SearchRelevancePlugin
35-
implements Plugin<SearchRelevancePluginSetup, SearchRelevancePluginStart>
36-
{
35+
implements Plugin<SearchRelevancePluginSetup, SearchRelevancePluginStart> {
3736
private readonly globalConfig$;
3837
private readonly config$: Observable<SearchRelevancePluginConfigType>;
3938
private readonly logger: Logger;
@@ -46,8 +45,7 @@ export class SearchRelevancePlugin
4645
this.metricsService = new MetricsService(this.logger.get('metrics-service'));
4746
}
4847

49-
public async setup(core: CoreSetup, {dataSource}: SearchRelevancePluginSetupDependencies) {
50-
48+
public async setup(core: CoreSetup, { dataSource }: SearchRelevancePluginSetupDependencies) {
5149
const dataSourceEnabled = !!dataSource;
5250
this.logger.debug('SearchRelevance: Setup');
5351

server/routes/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@ import { IRouter, OpenSearchServiceSetup } from '../../../../src/core/server';
77
import { registerDslRoute } from './dsl_route';
88
import { registerMetricsRoute } from './metrics_route';
99

10-
export function defineRoutes(router: IRouter, openSearchServiceSetup: OpenSearchServiceSetup, dataSourceEnabled: boolean) {
10+
export function defineRoutes(
11+
router: IRouter,
12+
openSearchServiceSetup: OpenSearchServiceSetup,
13+
dataSourceEnabled: boolean
14+
) {
1115
registerDslRoute(router, openSearchServiceSetup, dataSourceEnabled);
1216
registerMetricsRoute(router);
1317
}
1418

15-
export * from './search_relevance_rourte_servce';
19+
export * from './search_relevance_route_service';

0 commit comments

Comments
 (0)