Skip to content
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

Add standalone form state for query editing modal #450

Merged
merged 3 commits into from
Oct 30, 2024
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
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>
);
}
Loading