Skip to content

Commit

Permalink
Add interim state besides existing index option
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Oct 29, 2024
1 parent cafdd56 commit f17cb53
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 130 deletions.
10 changes: 7 additions & 3 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ export type IndexConfig = {
settings: IConfigField;
};

// TODO: may expand to just IndexConfig (including mappings/settings info)
// if we want to persist this for users using some existing index,
// and want to pass that index config around.
export type SearchIndexConfig = {
name: IConfigField;
};
Expand Down Expand Up @@ -113,6 +110,13 @@ export type WorkflowSchemaObj = {
};
export type WorkflowSchema = ObjectSchema<WorkflowSchemaObj>;

// Form / schema interfaces for the ingest docs sub-form
export type IngestDocsFormValues = {
docs: FormikValues;
};
export type IngestDocsSchemaObj = WorkflowSchemaObj;
export type IngestDocsSchema = WorkflowSchema;

/**
********** WORKSPACE TYPES/INTERFACES **********
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
SearchHit,
Workflow,
WorkflowConfig,
WorkspaceFormValues,
WorkflowFormValues,
customStringify,
isVectorSearchUseCase,
toFormattedDate,
Expand All @@ -44,7 +44,7 @@ interface SourceDataProps {
export function SourceData(props: SourceDataProps) {
const dispatch = useAppDispatch();
const dataSourceId = getDataSourceId();
const { values, setFieldValue } = useFormikContext<WorkspaceFormValues>();
const { values, setFieldValue } = useFormikContext<WorkflowFormValues>();

// empty/populated docs state
let docs = [];
Expand Down Expand Up @@ -236,7 +236,7 @@ export function SourceData(props: SourceDataProps) {
// only be executed for workflows coming from preset vector search use cases.
function getProcessorInfo(
uiConfig: WorkflowConfig,
values: WorkspaceFormValues
values: WorkflowFormValues
): {
processorId: string | undefined;
inputMapEntry: MapEntry | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useFormikContext } from 'formik';
import { Formik, getIn, useFormikContext } from 'formik';
import * as yup from 'yup';
import {
EuiSmallButton,
EuiCompressedFilePicker,
Expand All @@ -22,8 +23,14 @@ import {
EuiCompressedSuperSelect,
} from '@elastic/eui';
import { JsonField } from '../input_fields';
import { SOURCE_OPTIONS, WorkspaceFormValues } from '../../../../../common';
import {
IConfigField,
IngestDocsFormValues,
SOURCE_OPTIONS,
WorkflowFormValues,
} from '../../../../../common';
import { AppState } from '../../../../store';
import { getFieldSchema, getInitialValue } from '../../../../utils';

interface SourceDataProps {
selectedOption: SOURCE_OPTIONS;
Expand All @@ -37,119 +44,180 @@ interface SourceDataProps {
* Modal for configuring the source data for ingest.
*/
export function SourceDataModal(props: SourceDataProps) {
const { setFieldValue } = useFormikContext<WorkspaceFormValues>();
const { values, setFieldValue } = useFormikContext<WorkflowFormValues>();
const indices = useSelector((state: AppState) => state.opensearch.indices);

// files state. when a file is read, update the form value.
const fileReader = new FileReader();
fileReader.onload = (e) => {
if (e.target) {
setFieldValue('ingest.docs', e.target.result);
}
};
// sub-form values/schema
const docsFormValues = {
docs: getInitialValue('jsonArray'),
} as IngestDocsFormValues;
const docsFormSchema = yup.object({
docs: getFieldSchema({
type: 'jsonArray',
} as IConfigField),
});

// persist standalone values. update when there is changes detected to the parent form
const [tempDocs, setTempDocs] = useState<string>('[]');
useEffect(() => {
setTempDocs(getIn(values, 'ingest.docs'));
}, [getIn(values, 'ingest.docs')]);

function onClose() {
props.setIsModalOpen(false);
}

function onUpdate() {
// 1. Update the form with the temp docs
setFieldValue('ingest.docs', tempDocs);

props.setIsModalOpen(false);
}

return (
<EuiModal
onClose={() => props.setIsModalOpen(false)}
style={{ width: '70vw' }}
<Formik
enableReinitialize={false}
initialValues={docsFormValues}
validationSchema={docsFormSchema}
onSubmit={(values) => {}}
validate={(values) => {}}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Import data`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<>
<EuiFilterGroup>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.MANUAL}
hasActiveFilters={props.selectedOption === SOURCE_OPTIONS.MANUAL}
onClick={() => props.setSelectedOption(SOURCE_OPTIONS.MANUAL)}
data-testid="manualEditSourceDataButton"
>
Manual
</EuiSmallFilterButton>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.UPLOAD}
hasActiveFilters={props.selectedOption === SOURCE_OPTIONS.UPLOAD}
onClick={() => props.setSelectedOption(SOURCE_OPTIONS.UPLOAD)}
data-testid="uploadSourceDataButton"
>
Upload
</EuiSmallFilterButton>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.EXISTING_INDEX}
hasActiveFilters={
props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX
}
onClick={() =>
props.setSelectedOption(SOURCE_OPTIONS.EXISTING_INDEX)
}
data-testid="selectIndexSourceDataButton"
>
Existing index
</EuiSmallFilterButton>
</EuiFilterGroup>
<EuiSpacer size="m" />
{props.selectedOption === SOURCE_OPTIONS.UPLOAD && (
<>
<EuiCompressedFilePicker
accept="application/json"
multiple={false}
initialPromptText="Upload file"
onChange={(files) => {
if (files && files.length > 0) {
fileReader.readAsText(files[0]);
}
}}
display="default"
/>
<EuiSpacer size="s" />
</>
)}
{props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX && (
<>
<EuiText color="subdued" size="s">
Up to 5 sample documents will be automatically populated.
</EuiText>
<EuiSpacer size="s" />
<EuiCompressedSuperSelect
options={Object.values(indices).map(
(option) =>
({
value: option.name,
inputDisplay: <EuiText size="s">{option.name}</EuiText>,
disabled: false,
} as EuiSuperSelectOption<string>)
{(formikProps) => {
// internal hook to loop back and update tempDocs when form changes are detected
useEffect(() => {
setTempDocs(getIn(formikProps.values, 'docs'));
}, [getIn(formikProps.values, 'docs')]);

return (
<EuiModal onClose={() => onClose()} style={{ width: '70vw' }}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Import data`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<>
<EuiFilterGroup>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.MANUAL}
hasActiveFilters={
props.selectedOption === SOURCE_OPTIONS.MANUAL
}
onClick={() =>
props.setSelectedOption(SOURCE_OPTIONS.MANUAL)
}
data-testid="manualEditSourceDataButton"
>
Manual
</EuiSmallFilterButton>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.UPLOAD}
hasActiveFilters={
props.selectedOption === SOURCE_OPTIONS.UPLOAD
}
onClick={() =>
props.setSelectedOption(SOURCE_OPTIONS.UPLOAD)
}
data-testid="uploadSourceDataButton"
>
Upload
</EuiSmallFilterButton>
<EuiSmallFilterButton
id={SOURCE_OPTIONS.EXISTING_INDEX}
hasActiveFilters={
props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX
}
onClick={() =>
props.setSelectedOption(SOURCE_OPTIONS.EXISTING_INDEX)
}
data-testid="selectIndexSourceDataButton"
>
Existing index
</EuiSmallFilterButton>
</EuiFilterGroup>
<EuiSpacer size="m" />
{props.selectedOption === SOURCE_OPTIONS.UPLOAD && (
<>
<EuiCompressedFilePicker
accept="application/json"
multiple={false}
initialPromptText="Upload file"
onChange={(files) => {
if (files && files.length > 0) {
// create a custom filereader to update form with file values
const fileReader = new FileReader();
fileReader.onload = (e) => {
if (e.target) {
formikProps.setFieldValue(
'docs',
e.target.result as string
);
}
};
fileReader.readAsText(files[0]);
}
}}
display="default"
/>
<EuiSpacer size="s" />
</>
)}
{props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX && (
<>
<EuiText color="subdued" size="s">
Up to 5 sample documents will be automatically populated.
</EuiText>
<EuiSpacer size="s" />
<EuiCompressedSuperSelect
options={Object.values(indices).map(
(option) =>
({
value: option.name,
inputDisplay: (
<EuiText size="s">{option.name}</EuiText>
),
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={props.selectedIndex}
onChange={(option) => {
props.setSelectedIndex(option);
}}
isInvalid={false}
/>
<EuiSpacer size="xs" />
</>
)}
valueOfSelected={props.selectedIndex}
onChange={(option) => {
props.setSelectedIndex(option);
}}
isInvalid={false}
/>
<EuiSpacer size="xs" />
</>
)}
<JsonField
label="Documents to be imported"
fieldPath={'ingest.docs'}
helpText="Documents should be formatted as a valid JSON array."
editorHeight="25vh"
readOnly={false}
/>
</>
</EuiModalBody>
<EuiModalFooter>
<EuiSmallButton
onClick={() => props.setIsModalOpen(false)}
fill={false}
color="primary"
data-testid="closeSourceDataButton"
>
Close
</EuiSmallButton>
</EuiModalFooter>
</EuiModal>
<JsonField
label="Documents to be imported"
fieldPath={'docs'}
helpText="Documents should be formatted as a valid JSON array."
editorHeight="25vh"
readOnly={false}
/>
</>
</EuiModalBody>
<EuiModalFooter>
<EuiSmallButton
onClick={() => onClose()}
fill={false}
color="primary"
data-testid="cancelSourceDataButton"
>
Cancel
</EuiSmallButton>
<EuiSmallButton
onClick={() => onUpdate()}
fill={true}
color="primary"
data-testid="updateSourceDataButton"
>
Update
</EuiSmallButton>
</EuiModalFooter>
</EuiModal>
);
}}
</Formik>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
EuiLink,
EuiText,
} from '@elastic/eui';
import { WorkspaceFormValues, customStringify } from '../../../../../common';
import { WorkflowFormValues, customStringify } from '../../../../../common';
import { camelCaseToTitleString } from '../../../../utils';

interface JsonFieldProps {
Expand All @@ -31,7 +31,7 @@ interface JsonFieldProps {
export function JsonField(props: JsonFieldProps) {
const validate = props.validate !== undefined ? props.validate : true;

const { errors, touched, values } = useFormikContext<WorkspaceFormValues>();
const { errors, touched, values } = useFormikContext<WorkflowFormValues>();

// temp input state. only format when users click out of the code editor
const [jsonStr, setJsonStr] = useState<string>('{}');
Expand Down
Loading

0 comments on commit f17cb53

Please sign in to comment.