Skip to content

Add DEVELOPER_GUIDE.md for router setup + fix missing files #493

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions public/components/api/search_relevance_testing_page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiText,
EuiFieldText,
EuiButton,
EuiForm,
EuiFormRow,
EuiSpacer,
EuiPanel,
EuiCallOut,
EuiCodeBlock,
} from '@elastic/eui';
import { postQuerySet } from '../../services';
import { CoreStart } from '../../../../../src/core/public';

export interface TestProps {
http: CoreStart['http'];
}
export const QuerySetTester = ({ http }: TestProps) => {
const [querySetId, setQuerySetId] = useState('');
const [response, setResponse] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError(null);

try {
const result = await postQuerySet(querySetId, http);
setResponse(result);
} catch (err) {
setError(err.message || 'An error occurred');
} finally {
setIsLoading(false);
}
};

return (
<EuiPanel paddingSize="l">
<EuiText>
<h2>Query Set Tester</h2>
</EuiText>
<EuiSpacer size="m" />
<EuiForm component="form" onSubmit={handleSubmit}>
<EuiFormRow label="Query Set ID:">
<EuiFieldText
placeholder="Enter Query Set ID"
value={querySetId}
onChange={(e) => setQuerySetId(e.target.value)}
fullWidth
/>
</EuiFormRow>
<EuiSpacer size="m" />
<EuiButton type="submit" fill isLoading={isLoading}>
{isLoading ? 'Sending...' : 'Send Request'}
</EuiButton>
</EuiForm>
<EuiSpacer size="l" />

{error && (
<EuiCallOut title="Error" color="danger" iconType="alert">
<p>{error}</p>
</EuiCallOut>
)}

{response && (
<>
<EuiText>
<h3>Response:</h3>
</EuiText>
<EuiSpacer size="s" />
<EuiCodeBlock language="json" paddingSize="m" isCopyable>
{JSON.stringify(response, null, 2)}
</EuiCodeBlock>
</>
)}
</EuiPanel>
);
};

export default QuerySetTester;
2 changes: 1 addition & 1 deletion public/plugin_nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ export function registerAllPluginNavGroups(core: CoreSetup<SearchRelevancePlugin
showInAllNavGroup: true
},
]);
}
}
161 changes: 161 additions & 0 deletions server/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
## Developer Guide

This developer guide explains how to add new APIs to the middleware layer.


To add search-relevance related operations, directory structure should look like this:

```md
.
├── dashboards-search-relevance
│ └── common
│ └── index.ts
│ └── public
│ └── services.ts
│ └── server
│ └── routes
│ └── search_relevance_route_service.ts

```
#### Step1 - add common index
define your backend APIs and its node APIs as common index under `common/index.ts`
- [backend search relevance APIs](https://github.com/opensearch-project/dashboards-search-relevance/blob/evaluation_lab/common/index.ts#L12)
```
/**
* BACKEND SEARCH RELEVANCE APIs
*/
export const SEARCH_RELEVANCE_QUERY_SET_API = `${SEARCH_RELEVANCE_BASE_API}/queryset`;
```
- [node APIs](https://github.com/opensearch-project/dashboards-search-relevance/blob/evaluation_lab/common/index.ts#L23)
```
/**
* Node APIs
*/
export const BASE_QUERYSET_NODE_API_PATH = `${BASE_NODE_API_PATH}/queryset`;
```


#### Step2 - client-side API
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
```
export const postQuerySet = async (id: string, http: any) => {
try {
return await http.post(`..${BASE_QUERYSET_NODE_API_PATH}`, {
body: JSON.stringify({
querySetId: id,
}),
});
} catch (e) {
return e;
}
};
```


#### Step3 - server-side API
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.
```
router.post(
{
path: BASE_QUERYSET_NODE_API_PATH,
validate: {
body: schema.any(),
},
options: {
body: {
accepts: 'application/json',
},
},
},
searchRelevanceRoutesService.createQuerySet
);
```
add function definition under the same path `server/routes/search_relevance_route_service.ts`
```
createQuerySet = async (
context: RequestHandlerContext,
req: OpenSearchDashboardsRequest,
res: OpenSearchDashboardsResponseFactory
): Promise<IOpenSearchDashboardsResponse<any>> => {
const body = req.body;
const { data_source_id = '' } = req.params as { data_source_id?: string };
try {
const callWithRequest = getClientBasedOnDataSource(
context,
this.dataSourceEnabled,
req,
data_source_id,
this.client
);

const querysetResponse = await callWithRequest('searchRelevance.createQuerySet', {
body,
});

return res.ok({
body: {
ok: true,
resp: querysetResponse,
},
});
} catch (err) {
return res.ok({
body: {
ok: false,
resp: err.message,
},
});
}
};
```

add server-side API routing under `server/clusters/search_relevance_plugin.ts` to make calls to backend APIs.
```
searchRelevance.createQuerySet = ca({
url: {
fmt: `${SEARCH_RELEVANCE_QUERY_SET_API}`,
},
method: 'POST',
});
```

### Simple page testing
This page testing is not recommended after we have a page/workflow.
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.


To verify your api connection, your directory should look like this:

```md
.
├── dashboards-search-relevance
│ └── public
│ └── components
│ └── api
│ └── search_relevance_testing_page.tsx
│ └── app.tsx

```
#### step1 - change your API calls
The original testing page `search_relevance_testing_page.tsx` is pointing to postQuerySet function. you can replace with your new function.
```
const result = await postQuerySet(querySetId, http);
```

#### step2 - change your application landing page
You can change your landing page to point to your testing page under `app.tsx`.
Currently, the application is pointing to ExperimentPage if user opted in for the new version or QueryCompareHome.

By replacing ~~`<ExperimentPage application={application} chrome={chrome} />`~~
with `<QuerySetTester http={http} />`

- spin up your backend cluster, for example for search-relevance plugin, run `./gradlew run`
- 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`
- now, you able to open a page `http://localhost:5603/{uuid}`
- you can now test your api and your backend api response should be returned and rendered after click.
- more detailed can be found from this [issue](https://github.com/opensearch-project/dashboards-search-relevance/pull/490)





26 changes: 26 additions & 0 deletions server/common/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
ILegacyClusterClient,
OpenSearchDashboardsRequest,
RequestHandlerContext,
} from '../../../../src/core/server';

export function getClientBasedOnDataSource(
context: RequestHandlerContext,
dataSourceEnabled: boolean,
request: OpenSearchDashboardsRequest,
dataSourceId: string,
client: ILegacyClusterClient
): (endpoint: string, clientParams?: Record<string, any>) => any {
if (dataSourceEnabled && dataSourceId && dataSourceId.trim().length != 0) {
// client for remote cluster
return context.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI;
} else {
// fall back to default local cluster
return client.asScoped(request).callAsCurrentUser;
}
}
10 changes: 4 additions & 6 deletions server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
CoreStart,
Logger,
Plugin,
PluginInitializerContext
PluginInitializerContext,
} from '../../../src/core/server';
import {
defineRoutes,
registerSearchRelevanceRoutes,
SearchRelevanceRoutesService
SearchRelevanceRoutesService,
} from './routes';

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

export class SearchRelevancePlugin
implements Plugin<SearchRelevancePluginSetup, SearchRelevancePluginStart>
{
implements Plugin<SearchRelevancePluginSetup, SearchRelevancePluginStart> {
private readonly globalConfig$;
private readonly config$: Observable<SearchRelevancePluginConfigType>;
private readonly logger: Logger;
Expand All @@ -46,8 +45,7 @@ export class SearchRelevancePlugin
this.metricsService = new MetricsService(this.logger.get('metrics-service'));
}

public async setup(core: CoreSetup, {dataSource}: SearchRelevancePluginSetupDependencies) {

public async setup(core: CoreSetup, { dataSource }: SearchRelevancePluginSetupDependencies) {
const dataSourceEnabled = !!dataSource;
this.logger.debug('SearchRelevance: Setup');

Expand Down
8 changes: 6 additions & 2 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import { IRouter, OpenSearchServiceSetup } from '../../../../src/core/server';
import { registerDslRoute } from './dsl_route';
import { registerMetricsRoute } from './metrics_route';

export function defineRoutes(router: IRouter, openSearchServiceSetup: OpenSearchServiceSetup, dataSourceEnabled: boolean) {
export function defineRoutes(
router: IRouter,
openSearchServiceSetup: OpenSearchServiceSetup,
dataSourceEnabled: boolean
) {
registerDslRoute(router, openSearchServiceSetup, dataSourceEnabled);
registerMetricsRoute(router);
}

export * from './search_relevance_rourte_servce';
export * from './search_relevance_route_service';
Loading
Loading