Skip to content

Commit 83835c2

Browse files
authored
Add server-side workflow template library (#112)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent 616b1cb commit 83835c2

19 files changed

+347
-198
lines changed

common/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const SEARCH_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/se
2929
export const GET_WORKFLOW_STATE_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/state`;
3030
export const CREATE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/create`;
3131
export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/delete`;
32+
export const GET_PRESET_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/presets`;
3233

3334
/**
3435
* MISCELLANEOUS

common/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55

66
export * from './constants';
77
export * from './interfaces';
8+
export * from './utils';
89
export * from '../public/component_types';
910
export * from '../public/utils';

common/interfaces.ts

+14-15
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ export type WorkspaceFlowState = {
4040

4141
export type TemplateNode = {
4242
id: string;
43-
inputs: {};
43+
type: string;
44+
previous_node_inputs?: Map<string, any>;
45+
user_inputs?: Map<string, any>;
4446
};
4547

4648
export type TemplateEdge = {
@@ -49,32 +51,30 @@ export type TemplateEdge = {
4951
};
5052

5153
export type TemplateFlow = {
52-
userParams: {};
54+
user_params?: Map<string, any>;
5355
nodes: TemplateNode[];
54-
edges: TemplateEdge[];
56+
edges?: TemplateEdge[];
5557
};
5658

5759
export type TemplateFlows = {
5860
provision: TemplateFlow;
59-
ingest: TemplateFlow;
60-
query: TemplateFlow;
6161
};
6262

63-
export type UseCaseTemplate = {
64-
type: string;
63+
// A stateless template of a workflow
64+
export type WorkflowTemplate = {
6565
name: string;
6666
description: string;
67-
userInputs: {};
67+
use_case: USE_CASE;
68+
// TODO: finalize on version type when that is implemented
69+
// https://github.com/opensearch-project/flow-framework/issues/526
70+
version: any;
6871
workflows: TemplateFlows;
6972
};
7073

71-
export type Workflow = {
74+
// An instance of a workflow based on a workflow template
75+
export type Workflow = WorkflowTemplate & {
7276
// won't exist until created in backend
7377
id?: string;
74-
name: string;
75-
useCase: string;
76-
template: UseCaseTemplate;
77-
description?: string;
7878
// ReactFlow state may not exist if a workflow is created via API/backend-only.
7979
workspaceFlowState?: WorkspaceFlowState;
8080
// won't exist until created in backend
@@ -86,8 +86,7 @@ export type Workflow = {
8686
};
8787

8888
export enum USE_CASE {
89-
SEMANTIC_SEARCH = 'semantic_search',
90-
CUSTOM = 'custom',
89+
PROVISION = 'PROVISION',
9190
}
9291

9392
/**

common/utils.ts

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import {
7+
WorkspaceFlowState,
8+
ReactFlowComponent,
9+
initComponentData,
10+
TextEmbeddingTransformer,
11+
KnnIndexer,
12+
generateId,
13+
ReactFlowEdge,
14+
TemplateFlows,
15+
WorkflowTemplate,
16+
} from './';
17+
18+
// TODO: implement this and remove hardcoded return values
19+
/**
20+
* Converts a ReactFlow workspace flow to a backend-compatible set of ingest and/or search sub-workflows,
21+
* along with a provision sub-workflow if resources are to be created.
22+
*/
23+
export function toTemplateFlows(
24+
workspaceFlow: WorkspaceFlowState
25+
): TemplateFlows {
26+
return {
27+
provision: {
28+
user_params: {} as Map<string, any>,
29+
nodes: [],
30+
edges: [],
31+
},
32+
};
33+
}
34+
35+
// TODO: implement this and remove hardcoded return values
36+
/**
37+
* Converts a backend set of provision/ingest/search sub-workflows into a UI-compatible set of
38+
* ReactFlow nodes and edges
39+
*/
40+
export function toWorkspaceFlow(
41+
templateFlows: TemplateFlows
42+
): WorkspaceFlowState {
43+
const id1 = generateId('text_embedding_processor');
44+
const id2 = generateId('text_embedding_processor');
45+
const id3 = generateId('knn_index');
46+
const dummyNodes = [
47+
{
48+
id: id1,
49+
position: { x: 0, y: 500 },
50+
data: initComponentData(new TextEmbeddingTransformer().toObj(), id1),
51+
type: 'customComponent',
52+
},
53+
{
54+
id: id2,
55+
position: { x: 0, y: 200 },
56+
data: initComponentData(new TextEmbeddingTransformer().toObj(), id2),
57+
type: 'customComponent',
58+
},
59+
{
60+
id: id3,
61+
position: { x: 500, y: 500 },
62+
data: initComponentData(new KnnIndexer().toObj(), id3),
63+
type: 'customComponent',
64+
},
65+
] as ReactFlowComponent[];
66+
67+
return {
68+
nodes: dummyNodes,
69+
edges: [] as ReactFlowEdge[],
70+
};
71+
}
72+
73+
// TODO: implement this
74+
/**
75+
* Validates the UI workflow state.
76+
* Note we don't have to validate connections since that is done via input/output handlers.
77+
*/
78+
export function validateWorkspaceFlow(
79+
workspaceFlow: WorkspaceFlowState
80+
): boolean {
81+
return true;
82+
}
83+
84+
// TODO: implement this
85+
/**
86+
* Validates the backend template. May be used when parsing persisted templates on server-side,
87+
* or when importing/exporting on the UI.
88+
*/
89+
export function validateWorkflowTemplate(
90+
workflowTemplate: WorkflowTemplate
91+
): boolean {
92+
return true;
93+
}

public/pages/workflow_detail/utils/utils.ts

+4-40
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
import {
77
WorkspaceFlowState,
8-
UseCaseTemplate,
98
Workflow,
10-
USE_CASE,
119
ReactFlowComponent,
10+
toTemplateFlows,
11+
validateWorkspaceFlow,
1212
} from '../../../../common';
1313

1414
export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
@@ -19,12 +19,12 @@ export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
1919
nodes: processNodes(curFlowState.nodes),
2020
};
2121

22-
const isValid = validateFlowState(curFlowState);
22+
const isValid = validateWorkspaceFlow(curFlowState);
2323
if (isValid) {
2424
const updatedWorkflow = {
2525
...workflow,
2626
workspaceFlowState: curFlowState,
27-
template: generateUseCaseTemplate(curFlowState),
27+
workflows: toTemplateFlows(curFlowState),
2828
} as Workflow;
2929
if (workflow.id) {
3030
// TODO: implement connection to update workflow API
@@ -36,42 +36,6 @@ export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
3636
}
3737
}
3838

39-
// TODO: implement this. Need more info on UX side to finalize what we need
40-
// to persist, what validation to do, etc.
41-
// Note we don't have to validate connections since that is done via input/output handlers.
42-
function validateFlowState(flowState: WorkspaceFlowState): boolean {
43-
return true;
44-
}
45-
46-
// TODO: implement this
47-
function generateUseCaseTemplate(
48-
flowState: WorkspaceFlowState
49-
): UseCaseTemplate {
50-
return {
51-
name: 'example-name',
52-
description: 'example description',
53-
type: USE_CASE.SEMANTIC_SEARCH,
54-
userInputs: {},
55-
workflows: {
56-
provision: {
57-
userParams: {},
58-
nodes: [],
59-
edges: [],
60-
},
61-
ingest: {
62-
userParams: {},
63-
nodes: [],
64-
edges: [],
65-
},
66-
query: {
67-
userParams: {},
68-
nodes: [],
69-
edges: [],
70-
},
71-
},
72-
} as UseCaseTemplate;
73-
}
74-
7539
// Process the raw ReactFlow nodes to only persist the fields we need
7640
function processNodes(nodes: ReactFlowComponent[]): ReactFlowComponent[] {
7741
return nodes

public/pages/workflow_detail/workflow_detail.tsx

+9-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,15 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
105105
// TODO: can optimize to only fetch a single workflow
106106
dispatch(searchWorkflows({ query: { match_all: {} } }));
107107
}
108-
window.onbeforeunload = (e) =>
109-
isDirty || isNewWorkflow ? true : undefined;
108+
109+
// TODO: below has the following issue:
110+
// 1. user starts to create new unsaved workflow changes
111+
// 2. user navigates to other parts of the plugin without refreshing - no warning happens
112+
// 3. user refreshes at any later time: if isDirty is still true, shows browser warning
113+
// tune to only handle the check if still on the workflow details page, or consider adding a check / warning
114+
// if navigating away from the details page without refreshing (where it is currently not being triggered)
115+
// window.onbeforeunload = (e) =>
116+
// isDirty || isNewWorkflow ? true : undefined;
110117
}, []);
111118

112119
const tabs = [

public/pages/workflow_detail/workspace/workspace.tsx

+11-8
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import {
2121
IComponentData,
2222
ReactFlowComponent,
2323
Workflow,
24+
toWorkspaceFlow,
2425
} from '../../../../common';
2526
import { generateId, initComponentData } from '../../../utils';
26-
import { getCore } from '../../../services';
2727
import { WorkspaceComponent } from '../workspace_component';
2828
import { DeletableEdge } from '../workspace_edge';
2929

@@ -119,16 +119,19 @@ export function Workspace(props: WorkspaceProps) {
119119
// Initialization. Set the nodes and edges to an existing workflow,
120120
// if applicable.
121121
useEffect(() => {
122-
const workflow = props.workflow;
122+
const workflow = { ...props.workflow };
123123
if (workflow) {
124-
if (workflow.workspaceFlowState) {
125-
setNodes(workflow.workspaceFlowState.nodes);
126-
setEdges(workflow.workspaceFlowState.edges);
127-
} else {
128-
getCore().notifications.toasts.addWarning(
129-
`There is no configured UI flow for workflow: ${workflow.name}`
124+
if (!workflow.workspaceFlowState) {
125+
// No existing workspace state. This could be due to it being a backend-only-created
126+
// workflow, or a new, unsaved workflow
127+
// @ts-ignore
128+
workflow.workspaceFlowState = toWorkspaceFlow(workflow.workflows);
129+
console.debug(
130+
`There is no saved UI flow for workflow: ${workflow.name}. Generating a default one.`
130131
);
131132
}
133+
setNodes(workflow.workspaceFlowState.nodes);
134+
setEdges(workflow.workspaceFlowState.edges);
132135
}
133136
}, [props.workflow]);
134137

0 commit comments

Comments
 (0)