Skip to content

Commit c09e13b

Browse files
ohltylergithub-actions[bot]
authored andcommitted
Parse and persist UI metadata on editor page (#125)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> (cherry picked from commit ce28197)
1 parent f0195f1 commit c09e13b

File tree

15 files changed

+97
-231
lines changed

15 files changed

+97
-231
lines changed

.github/workflows/build-and-test.yml

-4
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ jobs:
4545
su `id -un 1000` -c "source $NVM_DIR/nvm.sh && nvm use && node -v && yarn -v &&
4646
cd ./plugins/dashboards-flow-framework &&
4747
whoami && yarn osd bootstrap && yarn build && yarn run test:jest --coverage"
48-
- name: Uploads coverage
49-
uses: codecov/codecov-action@v1
5048
5149
# TODO: once github actions supports windows and macos docker containers, we can
5250
# merge these in to the above step's matrix, including adding windows support
@@ -98,6 +96,4 @@ jobs:
9896
run: |
9997
cd OpenSearch-Dashboards/plugins/dashboards-flow-framework
10098
yarn run test:jest --coverage
101-
- name: Uploads coverage
102-
uses: codecov/codecov-action@v1
10399

codecov.yml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# disable tracking status entirely until UT is added.
2+
# tracking issue: https://github.com/opensearch-project/dashboards-flow-framework/issues/95
3+
coverage:
4+
# displays different colors depending on below, between, or above the range
5+
range: 50..90
6+
status:
7+
project:
8+
enabled: no
9+
default:
10+
target: auto
11+
# allows 5% coverage reduction without failing
12+
threshold: 5%
13+
patch: no
14+
changes: no
15+
16+
# disable comments in PRs
17+
comment: no

package.json

+1-7
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@
1313
"osd": "../../scripts/use_node ../../scripts/osd",
1414
"opensearch": "../../scripts/use_node ../../scripts/opensearch",
1515
"lint:es": "../../scripts/use_node ../../scripts/eslint -c eslintrc.json",
16-
"lint:es:precommit": "yarn lint:es common/* public/* server/*",
1716
"test:jest": "../../node_modules/.bin/jest --config ./test/jest.config.js",
1817
"build": "yarn plugin-helpers build && echo Renaming artifact to $npm_package_config_plugin_zip_name-$npm_package_config_plugin_version.zip && mv ./build/$npm_package_config_plugin_name*.zip ./build/$npm_package_config_plugin_zip_name-$npm_package_config_plugin_version.zip"
1918
},
2019
"repository": {
2120
"type": "git",
2221
"url": "https://github.com/opensearch-project/dashboards-flow-framework.git"
2322
},
24-
"pre-commit": [
25-
"lint:es:precommit"
26-
],
2723
"lint-staged": {
2824
"*.{ts,tsx,js,jsx,json,css,md}": [
2925
"prettier --write",
@@ -35,8 +31,6 @@
3531
"reactflow": "^11.8.3",
3632
"yup": "^1.3.2"
3733
},
38-
"devDependencies": {
39-
"pre-commit": "^1.2.2"
40-
},
34+
"devDependencies": {},
4135
"resolutions": {}
4236
}

public/component_types/indexer/indexer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ export class Indexer extends BaseComponent {
3131
this.fields = [
3232
{
3333
label: 'Index Name',
34-
name: 'indexName',
34+
id: 'indexName',
3535
type: 'select',
3636
},
3737
];
3838
this.createFields = [
3939
{
4040
label: 'Index Name',
41-
name: 'indexName',
41+
id: 'indexName',
4242
type: 'string',
4343
},
4444
// {
4545
// label: 'Mappings',
46-
// name: 'indexMappings',
46+
// id: 'indexMappings',
4747
// type: 'json',
4848
// placeholder: 'Enter an index mappings JSON blob...',
4949
// },

public/component_types/indexer/knn_indexer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class KnnIndexer extends Indexer {
1919
// TODO: finalize what to expose / what to have for defaults here
2020
// {
2121
// label: 'K-NN Settings',
22-
// name: 'knnSettings',
22+
// id: 'knnSettings',
2323
// type: 'json',
2424
// placeholder: 'Enter K-NN settings JSON blob...',
2525
// },

public/component_types/interfaces.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface IComponentInput {
4242
export interface IComponentField {
4343
label: string;
4444
type: FieldType;
45-
name: string;
45+
id: string;
4646
value?: FieldValue;
4747
placeholder?: string;
4848
helpText?: string;

public/component_types/transformer/text_embedding_transformer.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class TextEmbeddingTransformer extends MLTransformer {
1717
this.createFields = [
1818
{
1919
label: 'Model ID',
20-
name: 'modelId',
20+
id: 'modelId',
2121
type: 'select',
2222
selectType: 'model',
2323
helpText: 'The deployed text embedding model to use for embedding.',
@@ -26,7 +26,7 @@ export class TextEmbeddingTransformer extends MLTransformer {
2626
},
2727
{
2828
label: 'Input Field',
29-
name: 'inputField',
29+
id: 'inputField',
3030
type: 'string',
3131
helpText:
3232
'The name of the field from which to obtain text for generating text embeddings.',
@@ -36,7 +36,7 @@ export class TextEmbeddingTransformer extends MLTransformer {
3636

3737
{
3838
label: 'Vector Field',
39-
name: 'vectorField',
39+
id: 'vectorField',
4040
type: 'string',
4141
helpText:
4242
' The name of the vector field in which to store the generated text embeddings.',

public/pages/workflow_detail/component_details/input_fields/select_field.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function SelectField(props: SelectFieldProps) {
4848
}
4949
}, [models]);
5050

51-
const formField = `${props.componentId}.${props.field.name}`;
51+
const formField = `${props.componentId}.${props.field.id}`;
5252
const { errors, touched } = useFormikContext<WorkspaceFormValues>();
5353

5454
return (
@@ -84,7 +84,7 @@ export function SelectField(props: SelectFieldProps) {
8484
}}
8585
isInvalid={isFieldInvalid(
8686
props.componentId,
87-
props.field.name,
87+
props.field.id,
8888
errors,
8989
touched
9090
)}

public/pages/workflow_detail/component_details/input_fields/text_field.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ interface TextFieldProps {
2424
* An input field for a component where users input plaintext
2525
*/
2626
export function TextField(props: TextFieldProps) {
27-
const formField = `${props.componentId}.${props.field.name}`;
27+
const formField = `${props.componentId}.${props.field.id}`;
2828
const { errors, touched } = useFormikContext<WorkspaceFormValues>();
2929

3030
return (
@@ -44,10 +44,10 @@ export function TextField(props: TextFieldProps) {
4444
) : undefined
4545
}
4646
helpText={props.field.helpText || undefined}
47-
error={getFieldError(props.componentId, props.field.name, errors)}
47+
error={getFieldError(props.componentId, props.field.id, errors)}
4848
isInvalid={isFieldInvalid(
4949
props.componentId,
50-
props.field.name,
50+
props.field.id,
5151
errors,
5252
touched
5353
)}

public/pages/workflow_detail/utils/index.ts

-6
This file was deleted.

public/pages/workflow_detail/utils/utils.ts

-27
This file was deleted.

public/pages/workflow_detail/workspace/resizable_workspace.tsx

+30-24
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
DEFAULT_NEW_WORKFLOW_DESCRIPTION,
3535
USE_CASE,
3636
WORKFLOW_STATE,
37+
processNodes,
3738
} from '../../../../common';
3839
import {
3940
AppState,
@@ -47,7 +48,6 @@ import {
4748
} from '../../../store';
4849
import { Workspace } from './workspace';
4950
import { ComponentDetails } from '../component_details';
50-
import { processNodes } from '../utils';
5151

5252
// styling
5353
import './workspace-styles.scss';
@@ -147,30 +147,36 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
147147
// Metadata fields (name/description/use_case/etc.) may not exist if the user
148148
// cold reloads the page on a new, unsaved workflow.
149149
useEffect(() => {
150-
let workflowCopy = { ...props.workflow } as Workflow;
151-
if (!workflowCopy.ui_metadata || !workflowCopy.ui_metadata.workspaceFlow) {
152-
workflowCopy.ui_metadata = {
153-
...(workflowCopy.ui_metadata || {}),
154-
workspaceFlow: toWorkspaceFlow(workflowCopy.workflows),
155-
};
156-
console.debug(
157-
`There is no saved UI flow for workflow: ${workflowCopy.name}. Generating a default one.`
158-
);
159-
}
150+
if (props.workflow) {
151+
let workflowCopy = { ...props.workflow } as Workflow;
152+
if (
153+
!workflowCopy.ui_metadata ||
154+
!workflowCopy.ui_metadata.workspaceFlow
155+
) {
156+
workflowCopy.ui_metadata = {
157+
...(workflowCopy.ui_metadata || {}),
158+
workspaceFlow: toWorkspaceFlow(workflowCopy.workflows),
159+
};
160+
console.debug(
161+
`There is no saved UI flow for workflow: ${workflowCopy.name}. Generating a default one.`
162+
);
163+
}
160164

161-
// TODO: tune some of the defaults, like use_case and version as these will change
162-
workflowCopy = {
163-
...workflowCopy,
164-
name: workflowCopy.name || DEFAULT_NEW_WORKFLOW_NAME,
165-
description: workflowCopy.description || DEFAULT_NEW_WORKFLOW_DESCRIPTION,
166-
use_case: workflowCopy.use_case || USE_CASE.PROVISION,
167-
version: workflowCopy.version || {
168-
template: '1.0.0',
169-
compatibility: ['2.12.0', '3.0.0'],
170-
},
171-
};
165+
// TODO: tune some of the defaults, like use_case and version as these will change
166+
workflowCopy = {
167+
...workflowCopy,
168+
name: workflowCopy.name || DEFAULT_NEW_WORKFLOW_NAME,
169+
description:
170+
workflowCopy.description || DEFAULT_NEW_WORKFLOW_DESCRIPTION,
171+
use_case: workflowCopy.use_case || USE_CASE.PROVISION,
172+
version: workflowCopy.version || {
173+
template: '1.0.0',
174+
compatibility: ['2.12.0', '3.0.0'],
175+
},
176+
};
172177

173-
setWorkflow(workflowCopy);
178+
setWorkflow(workflowCopy);
179+
}
174180
}, [props.workflow]);
175181

176182
// Hook to updated the selected ReactFlow component
@@ -260,7 +266,7 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
260266
let curFlowState = reactFlowInstance.toObject() as WorkspaceFlowState;
261267
curFlowState = {
262268
...curFlowState,
263-
nodes: processNodes(curFlowState.nodes),
269+
nodes: processNodes(curFlowState.nodes, formikProps.values),
264270
};
265271
if (validateWorkspaceFlow(curFlowState)) {
266272
setFlowValidOnSubmit(true);

public/utils/utils.ts

+35-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
IComponentField,
1616
WorkspaceFormValues,
1717
WORKFLOW_STATE,
18+
ReactFlowComponent,
1819
} from '../../common';
1920

2021
// Append 16 random characters
@@ -48,18 +49,27 @@ export function initComponentData(
4849
export function componentDataToFormik(data: IComponentData): FormikValues {
4950
const formikValues = {} as FormikValues;
5051
data.createFields?.forEach((field) => {
51-
formikValues[field.name] = field.value || getInitialValue(field.type);
52+
formikValues[field.id] = field.value || getInitialValue(field.type);
5253
});
5354
return formikValues;
5455
}
5556

57+
// TODO: below, we are hardcoding to only persisting and validating create fields.
58+
// If we support both, we will need to dynamically update.
59+
// Injecting the current form values into the component data
5660
export function formikToComponentData(
57-
data: IComponentData,
58-
values: FormikValues
61+
origData: IComponentData,
62+
formValues: FormikValues
5963
): IComponentData {
60-
// TODO: populate data.fields with updated values based on the formik values
61-
// We will need this when submitting to the backend.
62-
return data;
64+
return {
65+
...origData,
66+
createFields: origData.createFields?.map(
67+
(createField: IComponentField) => ({
68+
...createField,
69+
value: formValues[createField.id],
70+
})
71+
),
72+
} as IComponentData;
6373
}
6474

6575
// Helper fn to get an initial value based on the field type
@@ -97,6 +107,24 @@ export function getFieldError(
97107
return errors[componentId]?.[fieldName] as string | undefined;
98108
}
99109

110+
// Process the raw ReactFlow nodes.
111+
// De-select them all, and propagate the form data to the internal node data
112+
export function processNodes(
113+
nodes: ReactFlowComponent[],
114+
formValues: WorkspaceFormValues
115+
): ReactFlowComponent[] {
116+
return nodes.map((node: ReactFlowComponent) => {
117+
return {
118+
...node,
119+
selected: false,
120+
data: formikToComponentData(
121+
{ ...node.data, selected: false },
122+
formValues[node.id]
123+
),
124+
};
125+
});
126+
}
127+
100128
/*
101129
**************** Yup (validation) utils **********************
102130
*/
@@ -106,7 +134,7 @@ export function getFieldError(
106134
export function getComponentSchema(data: IComponentData): ObjectSchema<any> {
107135
const schemaObj = {} as { [key: string]: Schema };
108136
data.createFields?.forEach((field) => {
109-
schemaObj[field.name] = getFieldSchema(field);
137+
schemaObj[field.id] = getFieldSchema(field);
110138
});
111139
return yup.object(schemaObj);
112140
}

server/routes/helpers.ts

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function toWorkflowObj(workflowHit: any): Workflow {
3737
description: hitSource.description || '',
3838
version: hitSource.version,
3939
workflows: hitSource.workflows,
40+
ui_metadata: hitSource.ui_metadata,
4041
lastUpdated: hitSource.last_updated_time,
4142
lastLaunched: hitSource.last_provisioned_time,
4243
} as Workflow;

0 commit comments

Comments
 (0)