Skip to content

Commit

Permalink
Integrate query into form (not persisted in config) (#188) (#193)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
(cherry picked from commit 8afac97)

Co-authored-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
opensearch-trigger-bot[bot] and ohltyler committed Jun 20, 2024
1 parent d3dde08 commit ba936c6
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 100 deletions.
2 changes: 1 addition & 1 deletion common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type IngestConfig = {
};

export type SearchConfig = {
request: IConfig;
request: {};
enrichRequest: ProcessorsConfig;
enrichResponse: ProcessorsConfig;
};
Expand Down
7 changes: 6 additions & 1 deletion public/pages/workflow_detail/resizable_workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
// ingest state
const [ingestDocs, setIngestDocs] = useState<string>('');

// query state
const [query, setQuery] = useState<string>('');

// Temp UI config state. For persisting changes to the UI config that may
// not be saved in the backend (e.g., adding / removing an ingest processor)
const [uiConfig, setUiConfig] = useState<WorkflowConfig | undefined>(
Expand Down Expand Up @@ -123,7 +126,7 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
// Initialize the form state based on the current UI config
useEffect(() => {
if (uiConfig) {
const initFormValues = uiConfigToFormik(uiConfig, ingestDocs);
const initFormValues = uiConfigToFormik(uiConfig, ingestDocs, query);
const initFormSchema = uiConfigToSchema(uiConfig);
setFormValues(initFormValues);
setFormSchema(initFormSchema);
Expand Down Expand Up @@ -187,6 +190,8 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
setQueryResponse={setQueryResponse}
ingestDocs={ingestDocs}
setIngestDocs={setIngestDocs}
query={query}
setQuery={setQuery}
/>
</EuiResizablePanel>
<EuiResizableButton />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,30 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useEffect, useState } from 'react';
import {
EuiCodeEditor,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiTitle,
} from '@elastic/eui';
import { Field, FieldProps } from 'formik';
import React, { useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { useFormikContext } from 'formik';
import { IConfigField, WorkspaceFormValues } from '../../../../../common';
import { JsonField } from '../input_fields';

interface ConfigureSearchRequestProps {
setQuery: (query: {}) => void;
setQuery: (query: string) => void;
onFormChange: () => void;
}

/**
* Input component for configuring a search request
*/
export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
const indexFieldPath = 'ingest.index.name';

// query state
const [queryStr, setQueryStr] = useState<string>('{}');
const { values } = useFormikContext<WorkspaceFormValues>();

// Hook to listen when the query form value changes.
// Try to set the query request if possible
useEffect(() => {
try {
const query = JSON.parse(queryStr);
props.setQuery(query);
} catch (e) {
props.setQuery({});
if (values?.search?.request) {
props.setQuery(values.search.request);
}
}, [queryStr]);
}, [values?.search?.request]);

return (
<EuiFlexGroup direction="column">
Expand All @@ -43,46 +35,20 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
<h2>Configure query</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<Field name={indexFieldPath}>
{({ field, form }: FieldProps) => {
return (
// TODO: make this dynamic depending on if ingest is defined or not.
// 1/ (incomplete) if no ingest, make this a dropdown to select existing indices
// 2/ (complete) if ingest, show the defined index from ingest config, make it readonly
<EuiFormRow key={indexFieldPath} label={'Retrieval index'}>
<EuiFieldText
{...field}
compressed={false}
value={field.value}
readOnly={true}
/>
</EuiFormRow>
);
}}
</Field>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
height="25vh"
value={queryStr}
onChange={(input) => {
setQueryStr(input);
}}
readOnly={false}
setOptions={{
fontSize: '14px',
}}
aria-label="Code Editor"
tabSize={2}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={false}>
<JsonField
// We want to integrate query into the form, but not persist in the config.
// So, we create the ConfigField explicitly inline, instead of pulling
// from the config.
field={
{
label: 'Define query',
} as IConfigField
}
fieldPath={'search.request'}
onFormChange={props.onFormChange}
editorHeight="25vh"
/>
</EuiFlexItem>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { WorkflowConfig } from '../../../../../common';
interface SearchInputsProps {
uiConfig: WorkflowConfig;
setUiConfig: (uiConfig: WorkflowConfig) => void;
setQuery: (query: {}) => void;
setQuery: (query: string) => void;
onFormChange: () => void;
}

Expand All @@ -24,7 +24,10 @@ export function SearchInputs(props: SearchInputsProps) {
return (
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
<ConfigureSearchRequest setQuery={props.setQuery} />
<ConfigureSearchRequest
setQuery={props.setQuery}
onFormChange={props.onFormChange}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiHorizontalRule margin="none" />
Expand Down
18 changes: 12 additions & 6 deletions public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface WorkflowInputsProps {
setQueryResponse: (queryResponse: string) => void;
ingestDocs: string;
setIngestDocs: (docs: string) => void;
query: string;
setQuery: (query: string) => void;
}

export enum STEP {
Expand All @@ -77,9 +79,6 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
// ingest state
const [ingestProvisioned, setIngestProvisioned] = useState<boolean>(false);

// query state
const [query, setQuery] = useState<{}>({});

// maintain global states
const onIngest = selectedStep === STEP.INGEST;
const onIngestAndProvisioned = onIngest && ingestProvisioned;
Expand Down Expand Up @@ -145,6 +144,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
await validateForm()
.then(async (validationResults: {}) => {
if (Object.keys(validationResults).length > 0) {
// TODO: may want to persist more fine-grained form validation (ingest vs. search)
// For example, running an ingest should be possible, even with some
// invalid query or search processor config. And vice versa.
console.error('Form invalid');
} else {
const updatedConfig = formikToUiConfig(
Expand Down Expand Up @@ -206,11 +208,15 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
async function validateAndRunQuery(): Promise<boolean> {
let success = false;
try {
if (!isEmpty(query)) {
let queryObj = {};
try {
queryObj = JSON.parse(props.query);
} catch (e) {}
if (!isEmpty(queryObj)) {
success = await validateAndUpdateWorkflow();
if (success) {
const indexName = values.ingest.index.name;
dispatch(searchIndex({ index: indexName, body: query }))
dispatch(searchIndex({ index: indexName, body: props.query }))
.unwrap()
.then(async (resp) => {
const hits = resp.hits.hits;
Expand Down Expand Up @@ -290,7 +296,7 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
<SearchInputs
uiConfig={props.uiConfig}
setUiConfig={props.setUiConfig}
setQuery={setQuery}
setQuery={props.setQuery}
onFormChange={props.onFormChange}
/>
)}
Expand Down
6 changes: 1 addition & 5 deletions public/pages/workflows/new_workflow/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,7 @@ function fetchEmptyMetadata(): UIState {
},
},
search: {
request: {
id: 'request',
name: 'Request',
fields: [],
},
request: {},
enrichRequest: {
processors: [],
},
Expand Down
11 changes: 6 additions & 5 deletions public/utils/config_to_form_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import {
// and can be extremely large. so we pass that as a standalone field
export function uiConfigToFormik(
config: WorkflowConfig,
ingestDocs: string
ingestDocs: string,
query: string
): WorkflowFormValues {
const formikValues = {} as WorkflowFormValues;
formikValues['ingest'] = ingestConfigToFormik(config.ingest, ingestDocs);
formikValues['search'] = searchConfigToFormik(config.search);
formikValues['search'] = searchConfigToFormik(config.search, query);
return formikValues;
}

Expand Down Expand Up @@ -81,12 +82,12 @@ function indexConfigToFormik(indexConfig: IndexConfig): FormikValues {
}

function searchConfigToFormik(
searchConfig: SearchConfig | undefined
searchConfig: SearchConfig | undefined,
query: string
): FormikValues {
let searchFormikValues = {} as FormikValues;
if (searchConfig) {
// TODO: implement for request
searchFormikValues['request'] = {};
searchFormikValues['request'] = query || getInitialValue('json');
searchFormikValues['enrichRequest'] = processorsConfigToFormik(
searchConfig.enrichRequest
);
Expand Down
37 changes: 22 additions & 15 deletions public/utils/config_to_schema_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,6 @@ function ingestConfigToSchema(
return yup.object(ingestSchemaObj);
}

function processorsConfigToSchema(processorsConfig: ProcessorsConfig): Schema {
const processorsSchemaObj = {} as { [key: string]: Schema };
processorsConfig.processors.forEach((processorConfig) => {
const processorSchemaObj = {} as { [key: string]: Schema };
processorConfig.fields.forEach((field) => {
processorSchemaObj[field.id] = getFieldSchema(field.type);
});
processorsSchemaObj[processorConfig.id] = yup.object(processorSchemaObj);
});

return yup.object(processorsSchemaObj);
}

function indexConfigToSchema(indexConfig: IndexConfig): Schema {
const indexSchemaObj = {} as { [key: string]: Schema };
indexSchemaObj['name'] = getFieldSchema(indexConfig.name.type);
Expand All @@ -60,15 +47,35 @@ function indexConfigToSchema(indexConfig: IndexConfig): Schema {
return yup.object(indexSchemaObj);
}

// TODO: implement this
function searchConfigToSchema(
searchConfig: SearchConfig | undefined
): ObjectSchema<any> {
const searchSchemaObj = {} as { [key: string]: Schema };

if (searchConfig) {
searchSchemaObj['request'] = getFieldSchema('json');
searchSchemaObj['enrichRequest'] = processorsConfigToSchema(
searchConfig.enrichRequest
);
searchSchemaObj['enrichResponse'] = processorsConfigToSchema(
searchConfig.enrichResponse
);
}
return yup.object(searchSchemaObj);
}

function processorsConfigToSchema(processorsConfig: ProcessorsConfig): Schema {
const processorsSchemaObj = {} as { [key: string]: Schema };
processorsConfig.processors.forEach((processorConfig) => {
const processorSchemaObj = {} as { [key: string]: Schema };
processorConfig.fields.forEach((field) => {
processorSchemaObj[field.id] = getFieldSchema(field.type);
});
processorsSchemaObj[processorConfig.id] = yup.object(processorSchemaObj);
});

return yup.object(processorsSchemaObj);
}

/*
**************** Yup (validation) utils **********************
*/
Expand Down
4 changes: 0 additions & 4 deletions public/utils/config_to_workspace_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ import { generateId } from './utils';
**************** Config -> workspace utils **********************
*/

/*
**************** ReactFlow workspace utils **********************
*/

const PARENT_NODE_HEIGHT = 350;
const NODE_HEIGHT_Y = 70;
const NODE_WIDTH = 300; // based off of the value set in reactflow-styles.scss
Expand Down

0 comments on commit ba936c6

Please sign in to comment.