/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Any modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import React, { useState, useEffect } from 'react'; import { i18n } from '@osd/i18n'; import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; import { BrowserRouter as Router } from 'react-router-dom'; import { EuiButton, EuiPage, EuiPageBody, EuiPageContent, EuiPageContentBody, EuiPageHeader, EuiTitle, EuiText, EuiFlexGrid, EuiFlexItem, EuiCheckbox, EuiSpacer, EuiCode, EuiComboBox, EuiFormLabel, } from '@elastic/eui'; import { CoreStart } from '../../../../src/core/public'; import { mountReactNode } from '../../../../src/core/public/utils'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; import { PLUGIN_ID, PLUGIN_NAME, IMyStrategyResponse, SERVER_SEARCH_ROUTE_PATH, } from '../../common'; import { DataPublicPluginStart, IndexPattern, IndexPatternField, isCompleteResponse, isErrorResponse, } from '../../../../src/plugins/data/public'; interface SearchExamplesAppDeps { basename: string; notifications: CoreStart['notifications']; http: CoreStart['http']; savedObjectsClient: CoreStart['savedObjects']['client']; navigation: NavigationPublicPluginStart; data: DataPublicPluginStart; } function formatFieldToComboBox(field?: IndexPatternField | null) { if (!field) return []; return formatFieldsToComboBox([field]); } function formatFieldsToComboBox(fields?: IndexPatternField[]) { if (!fields) return []; return fields?.map((field) => { return { label: field.displayName || field.name, }; }); } export const SearchExamplesApp = ({ http, basename, notifications, savedObjectsClient, navigation, data, }: SearchExamplesAppDeps) => { const { IndexPatternSelect } = data.ui; const [getCool, setGetCool] = useState(false); const [timeTook, setTimeTook] = useState(); const [indexPattern, setIndexPattern] = useState(); const [numericFields, setNumericFields] = useState(); const [selectedField, setSelectedField] = useState(); // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. useEffect(() => { const setDefaultIndexPattern = async () => { const defaultIndexPattern = await data.indexPatterns.getDefault(); setIndexPattern(defaultIndexPattern); }; setDefaultIndexPattern(); }, [data]); // Update the fields list every time the index pattern is modified. useEffect(() => { const fields = indexPattern?.fields.filter( (field) => field.type === 'number' && field.aggregatable ); setNumericFields(fields); setSelectedField(fields?.length ? fields[0] : null); }, [indexPattern]); const doAsyncSearch = async (strategy?: string) => { if (!indexPattern || !selectedField) return; // Constuct the query portion of the search request const query = data.query.getOpenSearchQuery(indexPattern); // Constuct the aggregations portion of the search request by using the `data.search.aggs` service. const aggs = [{ type: 'avg', params: { field: selectedField.name } }]; const aggsDsl = data.search.aggs.createAggConfigs(indexPattern, aggs).toDsl(); const request = { params: { index: indexPattern.title, body: { aggs: aggsDsl, query, }, }, // Add a custom request parameter to be consumed by `MyStrategy`. ...(strategy ? { get_cool: getCool } : {}), }; // Submit the search request using the `data.search` service. const searchSubscription$ = data.search .search(request, { strategy, }) .subscribe({ next: (response) => { if (isCompleteResponse(response)) { setTimeTook(response.rawResponse.took); const avgResult: number | undefined = response.rawResponse.aggregations ? response.rawResponse.aggregations[1].value : undefined; const message = ( Searched {response.rawResponse.hits.total} documents.
The average of {selectedField.name} is {avgResult ? Math.floor(avgResult) : 0}.
Is it Cool? {String((response as IMyStrategyResponse).cool)}
); notifications.toasts.addSuccess({ title: 'Query result', text: mountReactNode(message), }); searchSubscription$.unsubscribe(); } else if (isErrorResponse(response)) { // TODO: Make response error status clearer notifications.toasts.addWarning('An error has occurred'); searchSubscription$.unsubscribe(); } }, error: () => { notifications.toasts.addDanger('Failed to run search'); }, }); }; const onClickHandler = () => { doAsyncSearch(); }; const onMyStrategyClickHandler = () => { doAsyncSearch('myStrategy'); }; const onServerClickHandler = async () => { if (!indexPattern || !selectedField) return; try { const response = await http.get(SERVER_SEARCH_ROUTE_PATH, { query: { index: indexPattern.title, field: selectedField.name, }, }); notifications.toasts.addSuccess(`Server returned ${JSON.stringify(response)}`); } catch (e) { notifications.toasts.addDanger('Failed to run search'); } }; if (!indexPattern) return null; return ( <>

Index Pattern { const newIndexPattern = await data.indexPatterns.get(newIndexPatternId); setIndexPattern(newIndexPattern); }} isClearable={false} /> Numeric Fields { const field = indexPattern.getFieldByName(option[0].label); setSelectedField(field || null); }} sortMatchesBy="startsWith" />

Searching OpenSearch using data.search

If you want to fetch data from OpenSearch, you can use the different services provided by the data plugin. These help you get the index pattern and search bar configuration, format them into a DSL query and send it to OpenSearch.

Writing a custom search strategy

If you want to do some pre or post processing on the server, you might want to create a custom search strategy. This example uses such a strategy, passing in custom input and receiving custom output back. } checked={getCool} onChange={(event) => setGetCool(event.target.checked)} />

Using search on the server

You can also run your search request from the server, without registering a search strategy. This request does not take the configuration of{' '} TopNavMenu into account, but you could pass those down to the server as well.
); };