Skip to content

Commit

Permalink
Add standalone form state for query editing modal (#450)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler authored Oct 30, 2024
1 parent b8f1857 commit ba0dc6a
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 68 deletions.
7 changes: 6 additions & 1 deletion common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,14 @@ export type WorkflowSchema = ObjectSchema<WorkflowSchemaObj>;
export type IngestDocsFormValues = {
docs: FormikValues;
};
export type IngestDocsSchemaObj = WorkflowSchemaObj;
export type IngestDocsSchema = WorkflowSchema;

// Form / schema interfaces for the request query sub-form
export type RequestFormValues = {
request: ConfigFieldValue;
};
export type RequestSchema = WorkflowSchema;

/**
********** WORKSPACE TYPES/INTERFACES **********
*/
Expand Down
6 changes: 3 additions & 3 deletions public/pages/workflow_detail/workflow_detail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,9 @@ describe('WorkflowDetail Page with skip ingestion option (Hybrid Search Workflow
});
const searchQueryPresetButton = getByTestId('searchQueryPresetButton');
expect(searchQueryPresetButton).toBeInTheDocument();
const searchQueryCloseButton = getByTestId('searchQueryCloseButton');
expect(searchQueryCloseButton).toBeInTheDocument();
userEvent.click(searchQueryCloseButton);
const updateSearchQueryButton = getByTestId('updateSearchQueryButton');
expect(updateSearchQueryButton).toBeInTheDocument();
userEvent.click(updateSearchQueryButton);

// Add request processor
const addRequestProcessorButton = await waitFor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
IConfigField,
IndexMappings,
IngestDocsFormValues,
IngestDocsSchema,
isVectorSearchUseCase,
SearchHit,
SOURCE_OPTIONS,
Expand Down Expand Up @@ -76,7 +77,7 @@ export function SourceDataModal(props: SourceDataProps) {
docs: getFieldSchema({
type: 'jsonArray',
} as IConfigField),
});
}) as IngestDocsSchema;

// persist standalone values. update / initialize when it is first opened
const [tempDocs, setTempDocs] = useState<string>('[]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import { useFormikContext } from 'formik';
import React, { useEffect, useState } from 'react';
import { Formik, getIn, useFormikContext } from 'formik';
import * as yup from 'yup';
import { isEmpty } from 'lodash';
import {
EuiSmallButton,
EuiContextMenu,
Expand All @@ -18,10 +20,14 @@ import {
} from '@elastic/eui';
import { JsonField } from '../input_fields';
import {
IConfigField,
QUERY_PRESETS,
QueryPreset,
RequestFormValues,
RequestSchema,
WorkflowFormValues,
} from '../../../../../common';
import { getFieldSchema, getInitialValue } from '../../../../utils';

interface EditQueryModalProps {
queryFieldPath: string;
Expand All @@ -33,75 +39,131 @@ interface EditQueryModalProps {
* a set of pre-defined queries targeted for different use cases.
*/
export function EditQueryModal(props: EditQueryModalProps) {
// sub-form values/schema
const requestFormValues = {
request: getInitialValue('json'),
} as RequestFormValues;
const requestFormSchema = yup.object({
request: getFieldSchema({
type: 'json',
} as IConfigField),
}) as RequestSchema;

// persist standalone values. update / initialize when it is first opened
const [tempRequest, setTempRequest] = useState<string>('{}');
const [tempErrors, setTempErrors] = useState<boolean>(false);

// Form state
const { setFieldValue, setFieldTouched } = useFormikContext<
const { values, setFieldValue, setFieldTouched } = useFormikContext<
WorkflowFormValues
>();

// popover state
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);

return (
<EuiModal
onClose={() => props.setModalOpen(false)}
style={{ width: '70vw' }}
data-testid="editQueryModal"
<Formik
enableReinitialize={false}
initialValues={requestFormValues}
validationSchema={requestFormSchema}
onSubmit={(values) => {}}
validate={(values) => {}}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Edit query`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody data-testid="editQueryModalBody">
<EuiPopover
button={
<EuiSmallButton
onClick={() => setPopoverOpen(!popoverOpen)}
data-testid="searchQueryPresetButton"
>
Choose from a preset
</EuiSmallButton>
}
isOpen={popoverOpen}
closePopover={() => setPopoverOpen(false)}
anchorPosition="downLeft"
>
<EuiContextMenu
size="s"
initialPanelId={0}
panels={[
{
id: 0,
items: QUERY_PRESETS.map((preset: QueryPreset) => ({
name: preset.name,
onClick: () => {
setFieldValue(props.queryFieldPath, preset.query);
setFieldTouched(props.queryFieldPath, true);
setPopoverOpen(false);
},
})),
},
]}
/>
</EuiPopover>
<EuiSpacer size="s" />
<JsonField
label="Query"
fieldPath={props.queryFieldPath}
editorHeight="25vh"
readOnly={false}
/>
</EuiModalBody>
<EuiModalFooter>
<EuiSmallButton
onClick={() => props.setModalOpen(false)}
data-testid="searchQueryCloseButton"
fill={false}
color="primary"
>
Close
</EuiSmallButton>
</EuiModalFooter>
</EuiModal>
{(formikProps) => {
// override to parent form value when changes detected
useEffect(() => {
formikProps.setFieldValue(
'request',
getIn(values, props.queryFieldPath)
);
}, [getIn(values, props.queryFieldPath)]);

// update tempRequest when form changes are detected
useEffect(() => {
setTempRequest(getIn(formikProps.values, 'request'));
}, [getIn(formikProps.values, 'request')]);

// update tempErrors if errors detected
useEffect(() => {
setTempErrors(!isEmpty(formikProps.errors));
}, [formikProps.errors]);

return (
<EuiModal
onClose={() => props.setModalOpen(false)}
style={{ width: '70vw' }}
data-testid="editQueryModal"
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Edit query`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody data-testid="editQueryModalBody">
<EuiPopover
button={
<EuiSmallButton
onClick={() => setPopoverOpen(!popoverOpen)}
data-testid="searchQueryPresetButton"
>
Choose from a preset
</EuiSmallButton>
}
isOpen={popoverOpen}
closePopover={() => setPopoverOpen(false)}
anchorPosition="downLeft"
>
<EuiContextMenu
size="s"
initialPanelId={0}
panels={[
{
id: 0,
items: QUERY_PRESETS.map((preset: QueryPreset) => ({
name: preset.name,
onClick: () => {
formikProps.setFieldValue('request', preset.query);
setPopoverOpen(false);
},
})),
},
]}
/>
</EuiPopover>
<EuiSpacer size="s" />
<JsonField
label="Query"
fieldPath={'request'}
editorHeight="25vh"
readOnly={false}
/>
</EuiModalBody>
<EuiModalFooter>
<EuiSmallButton
onClick={() => props.setModalOpen(false)}
fill={false}
color="primary"
data-testid="cancelSearchQueryButton"
>
Cancel
</EuiSmallButton>
<EuiSmallButton
onClick={() => {
setFieldValue(props.queryFieldPath, tempRequest);
setFieldTouched(props.queryFieldPath, true);
props.setModalOpen(false);
}}
isDisabled={tempErrors} // blocking update until valid input is given
fill={true}
color="primary"
data-testid="updateSearchQueryButton"
>
Update
</EuiSmallButton>
</EuiModalFooter>
</EuiModal>
);
}}
</Formik>
);
}

0 comments on commit ba0dc6a

Please sign in to comment.