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

Refactor and organize public/utils #184

Merged
merged 7 commits into from
Jun 18, 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
23 changes: 13 additions & 10 deletions public/pages/workflow_detail/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import React, { useEffect, useState } from 'react';
import {
EuiPageHeader,
EuiButton,
Expand All @@ -12,8 +12,8 @@ import {
EuiText,
} from '@elastic/eui';
import {
DEFAULT_NEW_WORKFLOW_NAME,
DEFAULT_NEW_WORKFLOW_STATE,
WORKFLOW_STATE,
Workflow,
} from '../../../../common';

Expand All @@ -22,22 +22,25 @@ interface WorkflowDetailHeaderProps {
}

export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
function getTitle() {
return props.workflow ? props.workflow.name : DEFAULT_NEW_WORKFLOW_NAME;
}
// workflow state
const [workflowName, setWorkflowName] = useState<string>('');
const [workflowState, setWorkflowState] = useState<WORKFLOW_STATE>('');

function getState() {
return props.workflow ? props.workflow.state : DEFAULT_NEW_WORKFLOW_STATE;
}
useEffect(() => {
if (props.workflow) {
setWorkflowName(props.workflow.name);
setWorkflowState(props.workflow.state || DEFAULT_NEW_WORKFLOW_STATE);
}
}, [props.workflow]);

return (
<EuiPageHeader
style={{ marginTop: '-8px' }}
pageTitle={
<EuiFlexGroup direction="row" alignItems="flexEnd" gutterSize="m">
<EuiFlexItem grow={false}>{getTitle()}</EuiFlexItem>
<EuiFlexItem grow={false}>{workflowName}</EuiFlexItem>
<EuiFlexItem grow={false} style={{ marginBottom: '10px' }}>
<EuiText size="m">{getState()}</EuiText>
<EuiText size="m">{workflowState}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
}
Expand Down
1 change: 0 additions & 1 deletion public/pages/workflow_detail/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/

export * from './workflow_to_template_utils';
export * from './data_extractor_utils';
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ import {
useAppDispatch,
} from '../../../store';
import { getCore } from '../../../services';
import { formikToUiConfig, reduceToTemplate } from '../../../utils';
import { configToTemplateFlows } from '../utils';
import {
formikToUiConfig,
reduceToTemplate,
configToTemplateFlows,
} from '../../../utils';

// styling
import '../workspace/workspace-styles.scss';
Expand Down
111 changes: 111 additions & 0 deletions public/utils/config_to_form_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { FormikValues } from 'formik';
import {
WorkflowConfig,
WorkflowFormValues,
IngestConfig,
SearchConfig,
ProcessorsConfig,
IndexConfig,
IProcessorConfig,
ConfigFieldType,
ConfigFieldValue,
ModelFormValue,
} from '../../common';

/*
**************** Config -> formik utils **********************
*/

export function uiConfigToFormik(config: WorkflowConfig): WorkflowFormValues {
const formikValues = {} as WorkflowFormValues;
formikValues['ingest'] = ingestConfigToFormik(config.ingest);
formikValues['search'] = searchConfigToFormik(config.search);
return formikValues;
}

function ingestConfigToFormik(
ingestConfig: IngestConfig | undefined
): FormikValues {
let ingestFormikValues = {} as FormikValues;
if (ingestConfig) {
ingestFormikValues['enrich'] = processorsConfigToFormik(
ingestConfig.enrich
);
ingestFormikValues['index'] = indexConfigToFormik(ingestConfig.index);
}
return ingestFormikValues;
}

function processorsConfigToFormik(
processorsConfig: ProcessorsConfig
): FormikValues {
let formValues = {} as FormikValues;
processorsConfig.processors.forEach((processorConfig) => {
formValues[processorConfig.id] = processorConfigToFormik(processorConfig);
});
return formValues;
}

export function processorConfigToFormik(
processorConfig: IProcessorConfig
): FormikValues {
const fieldValues = {} as FormikValues;
processorConfig.fields.forEach((field) => {
fieldValues[field.id] = field.value || getInitialValue(field.type);
});
return fieldValues;
}

function indexConfigToFormik(indexConfig: IndexConfig): FormikValues {
let formValues = {} as FormikValues;
formValues['name'] =
indexConfig.name.value || getInitialValue(indexConfig.name.type);
return formValues;
}

function searchConfigToFormik(
searchConfig: SearchConfig | undefined
): FormikValues {
let searchFormikValues = {} as FormikValues;
if (searchConfig) {
// TODO: implement for request
searchFormikValues['request'] = {};
searchFormikValues['enrichRequest'] = processorsConfigToFormik(
searchConfig.enrichRequest
);
searchFormikValues['enrichResponse'] = processorsConfigToFormik(
searchConfig.enrichResponse
);
}
return searchFormikValues;
}

// Helper fn to get an initial value based on the field type
export function getInitialValue(fieldType: ConfigFieldType): ConfigFieldValue {
switch (fieldType) {
case 'string': {
return '';
}
case 'select': {
return '';
}
case 'model': {
return {
id: '',
category: undefined,
algorithm: undefined,
} as ModelFormValue;
}
case 'map': {
return [];
}
case 'json': {
return {};
}
}
}
114 changes: 114 additions & 0 deletions public/utils/config_to_schema_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Schema, ObjectSchema } from 'yup';
import * as yup from 'yup';
import {
WorkflowConfig,
WorkflowSchema,
IngestConfig,
SearchConfig,
ProcessorsConfig,
WorkflowSchemaObj,
IConfigField,
IndexConfig,
} from '../../common';

/*
**************** Schema / validation utils **********************
*/

export function uiConfigToSchema(config: WorkflowConfig): WorkflowSchema {
const schemaObj = {} as WorkflowSchemaObj;
schemaObj['ingest'] = ingestConfigToSchema(config.ingest);
schemaObj['search'] = searchConfigToSchema(config.search);
return yup.object(schemaObj) as WorkflowSchema;
}

function ingestConfigToSchema(
ingestConfig: IngestConfig | undefined
): ObjectSchema<any> {
const ingestSchemaObj = {} as { [key: string]: Schema };
if (ingestConfig) {
// TODO: implement for the other sub-categories
ingestSchemaObj['enrich'] = processorsConfigToSchema(ingestConfig.enrich);
ingestSchemaObj['index'] = indexConfigToSchema(ingestConfig.index);
}
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);
});
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);
return yup.object(indexSchemaObj);
}

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

return yup.object(searchSchemaObj);
}

/*
**************** Yup (validation) utils **********************
*/

function getFieldSchema(field: IConfigField): Schema {
let baseSchema: Schema;
switch (field.type) {
case 'string':
case 'select': {
baseSchema = yup.string().min(1, 'Too short').max(70, 'Too long');
break;
}
case 'model': {
baseSchema = yup.object().shape({
id: yup.string().min(1, 'Too short').max(70, 'Too long').required(),
category: yup.string().required(),
});
break;
}
case 'map': {
baseSchema = yup.array().of(
yup.object().shape({
key: yup.string().min(1, 'Too short').max(70, 'Too long').required(),
value: yup
.string()
.min(1, 'Too short')
.max(70, 'Too long')
.required(),
})
);
break;
}
case 'json': {
baseSchema = yup.object().json();
break;
}
}

// TODO: make optional schema if we support optional fields in the future
// return field.optional
// ? baseSchema.optional()
// : baseSchema.required('Required');

return baseSchema.required('Required');
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ import {
MLInferenceProcessor,
MapFormValue,
IngestProcessor,
Workflow,
WorkflowTemplate,
CreateSearchPipelineNode,
SearchProcessor,
IngestConfig,
SearchConfig,
CreateSearchPipelineNode,
} from '../../../../common';
import { generateId, processorConfigToFormik } from '../../../utils';
} from '../../common';
import { processorConfigToFormik } from './config_to_form_utils';
import { generateId } from './utils';

/**
* Given a WorkflowConfig with fully populated input values,
* generate a backend-compatible set of sub-workflows.
/*
**************** Config -> template utils **********************
*/

export function configToTemplateFlows(config: WorkflowConfig): TemplateFlows {
Expand Down Expand Up @@ -228,3 +230,17 @@ function indexConfigToTemplateNode(
},
};
}

// Helper fn to remove state-related fields from a workflow and have a stateless template
// to export and/or pass around, use when updating, etc.
export function reduceToTemplate(workflow: Workflow): WorkflowTemplate {
const {
id,
lastUpdated,
lastLaunched,
state,
resourcesCreated,
...workflowTemplate
} = workflow;
return workflowTemplate;
}
Loading
Loading