diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx index ff1cd77d..57c2439c 100644 --- a/public/pages/workflow_detail/tools/query/query.tsx +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -10,9 +10,11 @@ import { useFormikContext } from 'formik'; import { EuiCodeEditor, EuiComboBox, + EuiContextMenu, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, + EuiPopover, EuiSmallButton, EuiSmallButtonEmpty, EuiText, @@ -20,8 +22,9 @@ import { import { CONFIG_STEP, customStringify, - FETCH_ALL_QUERY, + QUERY_PRESETS, QueryParam, + QueryPreset, SearchResponse, SearchResponseVerbose, WorkflowFormValues, @@ -47,6 +50,12 @@ interface QueryProps { hasSearchPipeline: boolean; hasIngestResources: boolean; selectedStep: CONFIG_STEP; + queryRequest: string; + setQueryRequest: (queryRequest: string) => void; + queryResponse: SearchResponse | undefined; + setQueryResponse: (queryResponse: SearchResponse | undefined) => void; + queryParams: QueryParam[]; + setQueryParams: (queryParams: QueryParam[]) => void; } const SEARCH_OPTIONS = [ @@ -66,28 +75,11 @@ export function Query(props: QueryProps) { const dispatch = useAppDispatch(); const dataSourceId = getDataSourceId(); const dataSourceVersion = useDataSourceVersion(dataSourceId); - const { loading } = useSelector((state: AppState) => state.opensearch); - - // Form state const { values } = useFormikContext(); - // query response state - const [queryResponse, setQueryResponse] = useState< - SearchResponse | undefined - >(undefined); - - // Standalone / sandboxed search request state. Users can test things out - // without updating the base form / persisted value. - // Update if the parent form values are changed, or if a newly-created search pipeline is detected. - const [tempRequest, setTempRequest] = useState(''); - useEffect(() => { - if (!isEmpty(values?.search?.request)) { - setTempRequest(values?.search?.request); - } else { - setTempRequest(customStringify(FETCH_ALL_QUERY)); - } - }, [values?.search?.request]); + // popover state + const [popoverOpen, setPopoverOpen] = useState(false); // state for if to execute search w/ or w/o any configured search pipeline. // default based on if there is an available search pipeline or not. @@ -96,22 +88,16 @@ export function Query(props: QueryProps) { setIncludePipeline(props.hasSearchPipeline); }, [props.hasSearchPipeline]); - // query params state - const [queryParams, setQueryParams] = useState([]); - - // Do a few things when the request is changed: - // 1. Check if there is a new set of query parameters, and if so, - // reset the form. - // 2. Clear any stale results + // Check if there is a new set of query parameters, and if so, reset the form useEffect(() => { - const placeholders = getPlaceholdersFromQuery(tempRequest); + const placeholders = getPlaceholdersFromQuery(props.queryRequest); if ( !containsSameValues( placeholders, - queryParams.map((queryParam) => queryParam.name) + props.queryParams.map((queryParam) => queryParam.name) ) ) { - setQueryParams( + props.setQueryParams( placeholders.map((placeholder) => ({ name: placeholder, type: 'Text', @@ -119,8 +105,7 @@ export function Query(props: QueryProps) { })) ); } - setQueryResponse(undefined); - }, [tempRequest]); + }, [props.queryRequest]); // empty states const noSearchIndex = isEmpty(values?.search?.index?.name); @@ -192,7 +177,7 @@ export function Query(props: QueryProps) { fill={true} isLoading={loading} disabled={ - containsEmptyValues(queryParams) || + containsEmptyValues(props.queryParams) || isEmpty(indexToSearch) } onClick={() => { @@ -200,7 +185,10 @@ export function Query(props: QueryProps) { searchIndex({ apiBody: { index: indexToSearch, - body: injectParameters(queryParams, tempRequest), + body: injectParameters( + props.queryParams, + props.queryRequest + ), searchPipeline: includePipeline ? values?.search?.pipelineName : '_none', @@ -230,11 +218,11 @@ export function Query(props: QueryProps) { setSearchPipelineErrors({ errors: {} }); } - setQueryResponse(resp); + props.setQueryResponse(resp); } ) .catch((error: any) => { - setQueryResponse(undefined); + props.setQueryResponse(undefined); setSearchPipelineErrors({ errors: {} }); console.error('Error running query: ', error); }); @@ -250,20 +238,63 @@ export function Query(props: QueryProps) { Query - {props.selectedStep === CONFIG_STEP.SEARCH && - !isEmpty(values?.search?.request) && - values?.search?.request !== tempRequest && ( - - { - setTempRequest(values?.search?.request); - }} + + + {props.selectedStep === CONFIG_STEP.SEARCH && + !isEmpty(values?.search?.request) && + values?.search?.request !== props.queryRequest && ( + + { + props.setQueryRequest(values?.search?.request); + }} + > + Revert to original query + + + )} + + setPopoverOpen(!popoverOpen)} + data-testid="inspectorQueryPresetButton" + iconSide="right" + iconType="arrowDown" + > + Query samples + + } + isOpen={popoverOpen} + closePopover={() => setPopoverOpen(false)} + anchorPosition="downLeft" > - Revert to original query - + ({ + name: preset.name, + onClick: () => { + props.setQueryRequest(preset.query); + setPopoverOpen(false); + }, + }) + ), + }, + ]} + /> + - )} + + @@ -272,13 +303,16 @@ export function Query(props: QueryProps) { theme="textmate" width="100%" height={'100%'} - value={tempRequest} + value={props.queryRequest} onChange={(input) => { - setTempRequest(input); + props.setQueryRequest(input); }} + // format the JSON on blur onBlur={() => { try { - setTempRequest(customStringify(JSON.parse(tempRequest))); + props.setQueryRequest( + customStringify(JSON.parse(props.queryRequest)) + ); } catch (error) {} }} readOnly={false} @@ -299,8 +333,8 @@ export function Query(props: QueryProps) { * This may return nothing if the list of params are empty */} @@ -311,7 +345,8 @@ export function Query(props: QueryProps) { Results - {queryResponse === undefined || isEmpty(queryResponse) ? ( + {props.queryResponse === undefined || + isEmpty(props.queryResponse) ? ( No results} titleSize="s" @@ -324,7 +359,7 @@ export function Query(props: QueryProps) { } /> ) : ( - + )} diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index 86f8955c..428e813b 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -5,7 +5,7 @@ import React, { ReactNode, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { AppState } from '../../../store'; +import { useFormikContext } from 'formik'; import { isEmpty } from 'lodash'; import { EuiFlexGroup, @@ -15,11 +15,17 @@ import { EuiTabs, EuiText, } from '@elastic/eui'; +import { AppState } from '../../../store'; import { CONFIG_STEP, + customStringify, + FETCH_ALL_QUERY, INSPECTOR_TAB_ID, INSPECTOR_TABS, + QueryParam, + SearchResponse, Workflow, + WorkflowFormValues, } from '../../../../common'; import { Resources } from './resources'; import { Query } from './query'; @@ -56,6 +62,27 @@ export function Tools(props: ToolsProps) { const [curErrorMessages, setCurErrorMessages] = useState< (string | ReactNode)[] >([]); + const { values } = useFormikContext(); + + // Standalone / sandboxed search request state. Users can test things out + // without updating the base form / persisted value. + // Update if the parent form values are changed, or if a newly-created search pipeline is detected. + const [queryRequest, setQueryRequest] = useState(''); + useEffect(() => { + if (!isEmpty(values?.search?.request)) { + setQueryRequest(values?.search?.request); + } else { + setQueryRequest(customStringify(FETCH_ALL_QUERY)); + } + }, [values?.search?.request]); + + // query response state + const [queryResponse, setQueryResponse] = useState< + SearchResponse | undefined + >(undefined); + + // query params state + const [queryParams, setQueryParams] = useState([]); // Propagate any errors coming from opensearch API calls, including ingest/search pipeline verbose calls. useEffect(() => { @@ -160,6 +187,12 @@ export function Tools(props: ToolsProps) { props.workflow )} selectedStep={props.selectedStep} + queryRequest={queryRequest} + setQueryRequest={setQueryRequest} + queryResponse={queryResponse} + setQueryResponse={setQueryResponse} + queryParams={queryParams} + setQueryParams={setQueryParams} /> )} {props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && (