Skip to content

Commit 49c4375

Browse files
Persist state across Inspector tab switches; add presets dropdown (#671)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> (cherry picked from commit 21ddd80) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent cefeb41 commit 49c4375

File tree

2 files changed

+124
-56
lines changed

2 files changed

+124
-56
lines changed

public/pages/workflow_detail/tools/query/query.tsx

+90-55
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,21 @@ import { useFormikContext } from 'formik';
1010
import {
1111
EuiCodeEditor,
1212
EuiComboBox,
13+
EuiContextMenu,
1314
EuiEmptyPrompt,
1415
EuiFlexGroup,
1516
EuiFlexItem,
17+
EuiPopover,
1618
EuiSmallButton,
1719
EuiSmallButtonEmpty,
1820
EuiText,
1921
} from '@elastic/eui';
2022
import {
2123
CONFIG_STEP,
2224
customStringify,
23-
FETCH_ALL_QUERY,
25+
QUERY_PRESETS,
2426
QueryParam,
27+
QueryPreset,
2528
SearchResponse,
2629
SearchResponseVerbose,
2730
WorkflowFormValues,
@@ -47,6 +50,12 @@ interface QueryProps {
4750
hasSearchPipeline: boolean;
4851
hasIngestResources: boolean;
4952
selectedStep: CONFIG_STEP;
53+
queryRequest: string;
54+
setQueryRequest: (queryRequest: string) => void;
55+
queryResponse: SearchResponse | undefined;
56+
setQueryResponse: (queryResponse: SearchResponse | undefined) => void;
57+
queryParams: QueryParam[];
58+
setQueryParams: (queryParams: QueryParam[]) => void;
5059
}
5160

5261
const SEARCH_OPTIONS = [
@@ -66,28 +75,11 @@ export function Query(props: QueryProps) {
6675
const dispatch = useAppDispatch();
6776
const dataSourceId = getDataSourceId();
6877
const dataSourceVersion = useDataSourceVersion(dataSourceId);
69-
7078
const { loading } = useSelector((state: AppState) => state.opensearch);
71-
72-
// Form state
7379
const { values } = useFormikContext<WorkflowFormValues>();
7480

75-
// query response state
76-
const [queryResponse, setQueryResponse] = useState<
77-
SearchResponse | undefined
78-
>(undefined);
79-
80-
// Standalone / sandboxed search request state. Users can test things out
81-
// without updating the base form / persisted value.
82-
// Update if the parent form values are changed, or if a newly-created search pipeline is detected.
83-
const [tempRequest, setTempRequest] = useState<string>('');
84-
useEffect(() => {
85-
if (!isEmpty(values?.search?.request)) {
86-
setTempRequest(values?.search?.request);
87-
} else {
88-
setTempRequest(customStringify(FETCH_ALL_QUERY));
89-
}
90-
}, [values?.search?.request]);
81+
// popover state
82+
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
9183

9284
// state for if to execute search w/ or w/o any configured search pipeline.
9385
// default based on if there is an available search pipeline or not.
@@ -96,31 +88,24 @@ export function Query(props: QueryProps) {
9688
setIncludePipeline(props.hasSearchPipeline);
9789
}, [props.hasSearchPipeline]);
9890

99-
// query params state
100-
const [queryParams, setQueryParams] = useState<QueryParam[]>([]);
101-
102-
// Do a few things when the request is changed:
103-
// 1. Check if there is a new set of query parameters, and if so,
104-
// reset the form.
105-
// 2. Clear any stale results
91+
// Check if there is a new set of query parameters, and if so, reset the form
10692
useEffect(() => {
107-
const placeholders = getPlaceholdersFromQuery(tempRequest);
93+
const placeholders = getPlaceholdersFromQuery(props.queryRequest);
10894
if (
10995
!containsSameValues(
11096
placeholders,
111-
queryParams.map((queryParam) => queryParam.name)
97+
props.queryParams.map((queryParam) => queryParam.name)
11298
)
11399
) {
114-
setQueryParams(
100+
props.setQueryParams(
115101
placeholders.map((placeholder) => ({
116102
name: placeholder,
117103
type: 'Text',
118104
value: '',
119105
}))
120106
);
121107
}
122-
setQueryResponse(undefined);
123-
}, [tempRequest]);
108+
}, [props.queryRequest]);
124109

125110
// empty states
126111
const noSearchIndex = isEmpty(values?.search?.index?.name);
@@ -192,15 +177,18 @@ export function Query(props: QueryProps) {
192177
fill={true}
193178
isLoading={loading}
194179
disabled={
195-
containsEmptyValues(queryParams) ||
180+
containsEmptyValues(props.queryParams) ||
196181
isEmpty(indexToSearch)
197182
}
198183
onClick={() => {
199184
dispatch(
200185
searchIndex({
201186
apiBody: {
202187
index: indexToSearch,
203-
body: injectParameters(queryParams, tempRequest),
188+
body: injectParameters(
189+
props.queryParams,
190+
props.queryRequest
191+
),
204192
searchPipeline: includePipeline
205193
? values?.search?.pipelineName
206194
: '_none',
@@ -230,11 +218,11 @@ export function Query(props: QueryProps) {
230218
setSearchPipelineErrors({ errors: {} });
231219
}
232220

233-
setQueryResponse(resp);
221+
props.setQueryResponse(resp);
234222
}
235223
)
236224
.catch((error: any) => {
237-
setQueryResponse(undefined);
225+
props.setQueryResponse(undefined);
238226
setSearchPipelineErrors({ errors: {} });
239227
console.error('Error running query: ', error);
240228
});
@@ -250,20 +238,63 @@ export function Query(props: QueryProps) {
250238
<EuiFlexItem grow={false}>
251239
<EuiText size="s">Query</EuiText>
252240
</EuiFlexItem>
253-
{props.selectedStep === CONFIG_STEP.SEARCH &&
254-
!isEmpty(values?.search?.request) &&
255-
values?.search?.request !== tempRequest && (
256-
<EuiFlexItem grow={false} style={{ marginBottom: '0px' }}>
257-
<EuiSmallButtonEmpty
258-
disabled={false}
259-
onClick={() => {
260-
setTempRequest(values?.search?.request);
261-
}}
241+
<EuiFlexItem grow={false}>
242+
<EuiFlexGroup direction="row" gutterSize="s">
243+
{props.selectedStep === CONFIG_STEP.SEARCH &&
244+
!isEmpty(values?.search?.request) &&
245+
values?.search?.request !== props.queryRequest && (
246+
<EuiFlexItem
247+
grow={false}
248+
style={{ marginBottom: '0px' }}
249+
>
250+
<EuiSmallButtonEmpty
251+
disabled={false}
252+
onClick={() => {
253+
props.setQueryRequest(values?.search?.request);
254+
}}
255+
>
256+
Revert to original query
257+
</EuiSmallButtonEmpty>
258+
</EuiFlexItem>
259+
)}
260+
<EuiFlexItem grow={false}>
261+
<EuiPopover
262+
button={
263+
<EuiSmallButton
264+
onClick={() => setPopoverOpen(!popoverOpen)}
265+
data-testid="inspectorQueryPresetButton"
266+
iconSide="right"
267+
iconType="arrowDown"
268+
>
269+
Query samples
270+
</EuiSmallButton>
271+
}
272+
isOpen={popoverOpen}
273+
closePopover={() => setPopoverOpen(false)}
274+
anchorPosition="downLeft"
262275
>
263-
Revert to original query
264-
</EuiSmallButtonEmpty>
276+
<EuiContextMenu
277+
size="s"
278+
initialPanelId={0}
279+
panels={[
280+
{
281+
id: 0,
282+
items: QUERY_PRESETS.map(
283+
(preset: QueryPreset) => ({
284+
name: preset.name,
285+
onClick: () => {
286+
props.setQueryRequest(preset.query);
287+
setPopoverOpen(false);
288+
},
289+
})
290+
),
291+
},
292+
]}
293+
/>
294+
</EuiPopover>
265295
</EuiFlexItem>
266-
)}
296+
</EuiFlexGroup>
297+
</EuiFlexItem>
267298
</EuiFlexGroup>
268299
</EuiFlexItem>
269300
<EuiFlexItem grow={true}>
@@ -272,13 +303,16 @@ export function Query(props: QueryProps) {
272303
theme="textmate"
273304
width="100%"
274305
height={'100%'}
275-
value={tempRequest}
306+
value={props.queryRequest}
276307
onChange={(input) => {
277-
setTempRequest(input);
308+
props.setQueryRequest(input);
278309
}}
310+
// format the JSON on blur
279311
onBlur={() => {
280312
try {
281-
setTempRequest(customStringify(JSON.parse(tempRequest)));
313+
props.setQueryRequest(
314+
customStringify(JSON.parse(props.queryRequest))
315+
);
282316
} catch (error) {}
283317
}}
284318
readOnly={false}
@@ -299,8 +333,8 @@ export function Query(props: QueryProps) {
299333
* This may return nothing if the list of params are empty
300334
*/}
301335
<QueryParamsList
302-
queryParams={queryParams}
303-
setQueryParams={setQueryParams}
336+
queryParams={props.queryParams}
337+
setQueryParams={props.setQueryParams}
304338
/>
305339
</EuiFlexItem>
306340
</EuiFlexGroup>
@@ -311,7 +345,8 @@ export function Query(props: QueryProps) {
311345
<EuiText size="m">Results</EuiText>
312346
</EuiFlexItem>
313347
<EuiFlexItem>
314-
{queryResponse === undefined || isEmpty(queryResponse) ? (
348+
{props.queryResponse === undefined ||
349+
isEmpty(props.queryResponse) ? (
315350
<EuiEmptyPrompt
316351
title={<h2>No results</h2>}
317352
titleSize="s"
@@ -324,7 +359,7 @@ export function Query(props: QueryProps) {
324359
}
325360
/>
326361
) : (
327-
<Results response={queryResponse} />
362+
<Results response={props.queryResponse} />
328363
)}
329364
</EuiFlexItem>
330365
</EuiFlexGroup>

public/pages/workflow_detail/tools/tools.tsx

+34-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import React, { ReactNode, useEffect, useState } from 'react';
77
import { useSelector } from 'react-redux';
8-
import { AppState } from '../../../store';
8+
import { useFormikContext } from 'formik';
99
import { isEmpty } from 'lodash';
1010
import {
1111
EuiFlexGroup,
@@ -15,11 +15,17 @@ import {
1515
EuiTabs,
1616
EuiText,
1717
} from '@elastic/eui';
18+
import { AppState } from '../../../store';
1819
import {
1920
CONFIG_STEP,
21+
customStringify,
22+
FETCH_ALL_QUERY,
2023
INSPECTOR_TAB_ID,
2124
INSPECTOR_TABS,
25+
QueryParam,
26+
SearchResponse,
2227
Workflow,
28+
WorkflowFormValues,
2329
} from '../../../../common';
2430
import { Resources } from './resources';
2531
import { Query } from './query';
@@ -56,6 +62,27 @@ export function Tools(props: ToolsProps) {
5662
const [curErrorMessages, setCurErrorMessages] = useState<
5763
(string | ReactNode)[]
5864
>([]);
65+
const { values } = useFormikContext<WorkflowFormValues>();
66+
67+
// Standalone / sandboxed search request state. Users can test things out
68+
// without updating the base form / persisted value.
69+
// Update if the parent form values are changed, or if a newly-created search pipeline is detected.
70+
const [queryRequest, setQueryRequest] = useState<string>('');
71+
useEffect(() => {
72+
if (!isEmpty(values?.search?.request)) {
73+
setQueryRequest(values?.search?.request);
74+
} else {
75+
setQueryRequest(customStringify(FETCH_ALL_QUERY));
76+
}
77+
}, [values?.search?.request]);
78+
79+
// query response state
80+
const [queryResponse, setQueryResponse] = useState<
81+
SearchResponse | undefined
82+
>(undefined);
83+
84+
// query params state
85+
const [queryParams, setQueryParams] = useState<QueryParam[]>([]);
5986

6087
// Propagate any errors coming from opensearch API calls, including ingest/search pipeline verbose calls.
6188
useEffect(() => {
@@ -160,6 +187,12 @@ export function Tools(props: ToolsProps) {
160187
props.workflow
161188
)}
162189
selectedStep={props.selectedStep}
190+
queryRequest={queryRequest}
191+
setQueryRequest={setQueryRequest}
192+
queryResponse={queryResponse}
193+
setQueryResponse={setQueryResponse}
194+
queryParams={queryParams}
195+
setQueryParams={setQueryParams}
163196
/>
164197
)}
165198
{props.selectedTabId === INSPECTOR_TAB_ID.ERRORS && (

0 commit comments

Comments
 (0)