diff --git a/public/component_types/base_component.tsx b/public/component_types/base_component.tsx
new file mode 100644
index 00000000..1e6dcd4f
--- /dev/null
+++ b/public/component_types/base_component.tsx
@@ -0,0 +1,15 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * A base component class.
+ */
+export abstract class BaseComponent {
+ // Persist a standard toObj() fn that all component classes can use. This is necessary
+ // so we have standard JS Object when serializing comoponent state in redux.
+ toObj() {
+ return Object.assign({}, this);
+ }
+}
diff --git a/public/component_types/indices/knn_index.ts b/public/component_types/indices/knn_index.ts
index 9255b47d..b66e17c0 100644
--- a/public/component_types/indices/knn_index.ts
+++ b/public/component_types/indices/knn_index.ts
@@ -4,6 +4,7 @@
*/
import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../utils';
+import { BaseComponent } from '../base_component';
import {
IComponent,
IComponentField,
@@ -15,7 +16,7 @@ import {
/**
* A k-NN index UI component
*/
-export class KnnIndex implements IComponent {
+export class KnnIndex extends BaseComponent implements IComponent {
type: COMPONENT_CLASS;
label: string;
description: string;
@@ -30,6 +31,7 @@ export class KnnIndex implements IComponent {
outputs: IComponentOutput[];
constructor() {
+ super();
this.type = COMPONENT_CLASS.KNN_INDEX;
this.label = 'k-NN Index';
this.description = 'A k-NN Index to be used as a vector store';
diff --git a/public/component_types/processors/text_embedding_processor.ts b/public/component_types/processors/text_embedding_processor.ts
index 2c3fa078..c6f3961a 100644
--- a/public/component_types/processors/text_embedding_processor.ts
+++ b/public/component_types/processors/text_embedding_processor.ts
@@ -4,6 +4,7 @@
*/
import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../utils';
+import { BaseComponent } from '../base_component';
import {
IComponent,
IComponentField,
@@ -15,7 +16,9 @@ import {
/**
* A text embedding processor UI component
*/
-export class TextEmbeddingProcessor implements IComponent {
+export class TextEmbeddingProcessor
+ extends BaseComponent
+ implements IComponent {
type: COMPONENT_CLASS;
label: string;
description: string;
@@ -29,6 +32,7 @@ export class TextEmbeddingProcessor implements IComponent {
outputs: IComponentOutput[];
constructor() {
+ super();
this.type = COMPONENT_CLASS.TEXT_EMBEDDING_PROCESSOR;
this.label = 'Text Embedding Processor';
this.description =
diff --git a/public/pages/workflow_detail/components/header.tsx b/public/pages/workflow_detail/components/header.tsx
index 4c70ef20..1ee8c1db 100644
--- a/public/pages/workflow_detail/components/header.tsx
+++ b/public/pages/workflow_detail/components/header.tsx
@@ -3,15 +3,22 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
+import React, { useContext } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
import { EuiPageHeader, EuiButton } from '@elastic/eui';
import { Workflow } from '../../../../common';
+import { generateUseCaseTemplate } from '../utils';
+import { rfContext, AppState, removeDirty } from '../../../store';
interface WorkflowDetailHeaderProps {
workflow?: Workflow;
}
export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
+ const dispatch = useDispatch();
+ const { reactFlowInstance } = useContext(rfContext);
+ const isDirty = useSelector((state: AppState) => state.workspace.isDirty);
+
return (
{}}>
Prototype
,
- {}}>
+ {
+ // @ts-ignore
+ generateUseCaseTemplate(props.workflow, reactFlowInstance);
+ dispatch(removeDirty());
+ }}
+ >
Save
,
]}
diff --git a/public/pages/workflow_detail/utils/index.ts b/public/pages/workflow_detail/utils/index.ts
new file mode 100644
index 00000000..079132ce
--- /dev/null
+++ b/public/pages/workflow_detail/utils/index.ts
@@ -0,0 +1,6 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export * from './utils';
diff --git a/public/pages/workflow_detail/utils/utils.ts b/public/pages/workflow_detail/utils/utils.ts
new file mode 100644
index 00000000..d8a6f107
--- /dev/null
+++ b/public/pages/workflow_detail/utils/utils.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { UseCaseTemplate, Workflow } from '../../../../common';
+
+export function generateUseCaseTemplate(
+ workflow: Workflow,
+ rfInstance: any
+): UseCaseTemplate {
+ let useCaseTemplate = {} as UseCaseTemplate;
+
+ return useCaseTemplate;
+}
diff --git a/public/pages/workflow_detail/workspace/workspace.tsx b/public/pages/workflow_detail/workspace/workspace.tsx
index 1acd61a2..dceb0a06 100644
--- a/public/pages/workflow_detail/workspace/workspace.tsx
+++ b/public/pages/workflow_detail/workspace/workspace.tsx
@@ -4,6 +4,7 @@
*/
import React, { useRef, useContext, useCallback, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
import ReactFlow, {
Controls,
Background,
@@ -12,7 +13,7 @@ import ReactFlow, {
addEdge,
} from 'reactflow';
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
-import { rfContext } from '../../../store';
+import { rfContext, setDirty } from '../../../store';
import { IComponent, Workflow } from '../../../../common';
import { generateId } from '../../../utils';
import { getCore } from '../../../services';
@@ -30,6 +31,7 @@ const nodeTypes = { customComponent: WorkspaceComponent };
// TODO: probably have custom edge types here too
export function Workspace(props: WorkspaceProps) {
+ const dispatch = useDispatch();
const reactFlowWrapper = useRef(null);
const { reactFlowInstance, setReactFlowInstance } = useContext(rfContext);
@@ -39,10 +41,8 @@ export function Workspace(props: WorkspaceProps) {
const onConnect = useCallback(
(params) => {
setEdges((eds) => addEdge(params, eds));
+ dispatch(setDirty());
},
- // TODO: add customized logic to prevent connections based on the node's
- // allowed inputs. If allowed, update that node state as well with the added
- // connection details.
[setEdges]
);
diff --git a/public/store/reducers/workflows_reducer.ts b/public/store/reducers/workflows_reducer.ts
index 77d43b94..49bf56d3 100644
--- a/public/store/reducers/workflows_reducer.ts
+++ b/public/store/reducers/workflows_reducer.ts
@@ -18,19 +18,19 @@ const dummyNodes = [
{
id: generateId('text_embedding_processor'),
position: { x: 0, y: 500 },
- data: new TextEmbeddingProcessor(),
+ data: new TextEmbeddingProcessor().toObj(),
type: 'customComponent',
},
{
id: generateId('text_embedding_processor'),
position: { x: 0, y: 200 },
- data: new TextEmbeddingProcessor(),
+ data: new TextEmbeddingProcessor().toObj(),
type: 'customComponent',
},
{
id: generateId('knn_index'),
position: { x: 500, y: 500 },
- data: new KnnIndex(),
+ data: new KnnIndex().toObj(),
type: 'customComponent',
},
] as ReactFlowComponent[];
diff --git a/public/store/reducers/workspace_reducer.ts b/public/store/reducers/workspace_reducer.ts
index 2eb6748e..7bfbd6dc 100644
--- a/public/store/reducers/workspace_reducer.ts
+++ b/public/store/reducers/workspace_reducer.ts
@@ -4,20 +4,9 @@
*/
import { createSlice } from '@reduxjs/toolkit';
-import { IComponent } from '../../../common';
-import { KnnIndex, TextEmbeddingProcessor } from '../../component_types';
-
-// TODO: should be fetched from server-side. This will be the list of all
-// available components that the framework offers. This will be used in the component
-// library to populate the available components to drag-and-drop into the workspace.
-const dummyComponents = [
- new TextEmbeddingProcessor(),
- new KnnIndex(),
-] as IComponent[];
const initialState = {
isDirty: false,
- components: dummyComponents,
};
const workspaceSlice = createSlice({
@@ -30,12 +19,8 @@ const workspaceSlice = createSlice({
removeDirty(state) {
state.isDirty = false;
},
- setComponents(state, action) {
- state.components = action.payload;
- state.isDirty = true;
- },
},
});
export const workspaceReducer = workspaceSlice.reducer;
-export const { setDirty, removeDirty, setComponents } = workspaceSlice.actions;
+export const { setDirty, removeDirty } = workspaceSlice.actions;