Skip to content

Commit

Permalink
Clean up conversion between UI flow and template sub-workflows
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Mar 13, 2024
1 parent 54b4d45 commit da14028
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 93 deletions.
1 change: 1 addition & 0 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

export * from './constants';
export * from './interfaces';
export * from './utils';
export * from '../public/component_types';
export * from '../public/utils';
70 changes: 70 additions & 0 deletions common/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
WorkspaceFlowState,
ReactFlowComponent,
initComponentData,
TextEmbeddingTransformer,
KnnIndexer,
generateId,
ReactFlowEdge,
TemplateFlows,
} from './';

// TODO: implement this and remove hardcoded return values
/**
* Converts a ReactFlow workspace flow to a backend-compatible set of ingest and/or search sub-workflows,
* along with a provision sub-workflow if resources are to be created.
*/
export function toTemplateFlows(
workspaceFlow: WorkspaceFlowState
): TemplateFlows {
return {
provision: {
user_params: {} as Map<string, any>,
nodes: [],
edges: [],
},
};
}

// TODO: implement this and remove hardcoded return values
/**
* Converts a backend set of provision/ingest/search sub-workflows into a UI-compatible set of
* ReactFlow nodes and edges
*/
export function toWorkspaceFlow(
templateFlows: TemplateFlows
): WorkspaceFlowState {
const id1 = generateId('text_embedding_processor');
const id2 = generateId('text_embedding_processor');
const id3 = generateId('knn_index');
const dummyNodes = [
{
id: id1,
position: { x: 0, y: 500 },
data: initComponentData(new TextEmbeddingTransformer().toObj(), id1),
type: 'customComponent',
},
{
id: id2,
position: { x: 0, y: 200 },
data: initComponentData(new TextEmbeddingTransformer().toObj(), id2),
type: 'customComponent',
},
{
id: id3,
position: { x: 500, y: 500 },
data: initComponentData(new KnnIndexer().toObj(), id3),
type: 'customComponent',
},
] as ReactFlowComponent[];

return {
nodes: dummyNodes,
edges: [] as ReactFlowEdge[],
};
}
27 changes: 2 additions & 25 deletions public/pages/workflow_detail/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

import {
WorkspaceFlowState,
WorkflowTemplate,
Workflow,
USE_CASE,
ReactFlowComponent,
toTemplateFlows,
} from '../../../../common';

export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
Expand All @@ -24,7 +23,7 @@ export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
const updatedWorkflow = {
...workflow,
workspaceFlowState: curFlowState,
template: generateWorkflowTemplate(curFlowState),
workflows: toTemplateFlows(curFlowState),
} as Workflow;
if (workflow.id) {
// TODO: implement connection to update workflow API
Expand All @@ -43,28 +42,6 @@ function validateFlowState(flowState: WorkspaceFlowState): boolean {
return true;
}

// TODO: implement this
function generateWorkflowTemplate(
flowState: WorkspaceFlowState
): WorkflowTemplate {
return {
name: 'example-name',
description: 'example description',
use_case: USE_CASE.PROVISION,
version: {
template: '1.0.0',
compatibility: ['3.0.0'],
},
workflows: {
provision: {
userParams: {},
nodes: [],
edges: [],
},
},
} as WorkflowTemplate;
}

// Process the raw ReactFlow nodes to only persist the fields we need
function processNodes(nodes: ReactFlowComponent[]): ReactFlowComponent[] {
return nodes
Expand Down
19 changes: 11 additions & 8 deletions public/pages/workflow_detail/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
IComponentData,
ReactFlowComponent,
Workflow,
toWorkspaceFlow,
} from '../../../../common';
import { generateId, initComponentData } from '../../../utils';
import { getCore } from '../../../services';
import { WorkspaceComponent } from '../workspace_component';
import { DeletableEdge } from '../workspace_edge';

Expand Down Expand Up @@ -119,16 +119,19 @@ export function Workspace(props: WorkspaceProps) {
// Initialization. Set the nodes and edges to an existing workflow,
// if applicable.
useEffect(() => {
const workflow = props.workflow;
const workflow = { ...props.workflow };
if (workflow) {
if (workflow.workspaceFlowState) {
setNodes(workflow.workspaceFlowState.nodes);
setEdges(workflow.workspaceFlowState.edges);
} else {
getCore().notifications.toasts.addWarning(
`There is no configured UI flow for workflow: ${workflow.name}`
if (!workflow.workspaceFlowState) {
// No existing workspace state. This could be due to it being a backend-only-created
// workflow, or a new, unsaved workflow
// @ts-ignore
workflow.workspaceFlowState = toWorkspaceFlow(workflow.workflows);
console.debug(
`There is no saved UI flow for workflow: ${workflow.name}. Generating a default one.`
);
}
setNodes(workflow.workspaceFlowState.nodes);
setEdges(workflow.workspaceFlowState.edges);
}
}, [props.workflow]);

Expand Down
51 changes: 28 additions & 23 deletions public/pages/workflows/new_workflow/new_workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
EuiFlexGrid,
EuiFlexGroup,
EuiFieldSearch,
EuiLoadingSpinner,
} from '@elastic/eui';
import { useDispatch, useSelector } from 'react-redux';
import { UseCase } from './use_case';
Expand All @@ -24,15 +25,15 @@ import { getWorkflowPresets } from '../../../store/reducers';
interface NewWorkflowProps {}

/**
* TODO: may rename this later on.
*
* Contains the searchable library of templated workflows based
* on a variety of use cases. Can click on them to load in a pre-configured
* workflow for users to start with.
*/
export function NewWorkflow(props: NewWorkflowProps) {
const dispatch = useDispatch();
const { presetWorkflows } = useSelector((state: AppState) => state.presets);
const { presetWorkflows, loading } = useSelector(
(state: AppState) => state.presets
);
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>([]);

// search bar state
Expand Down Expand Up @@ -65,26 +66,30 @@ export function NewWorkflow(props: NewWorkflowProps) {
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={3} gutterSize="l">
{filteredWorkflows.map((workflow: Workflow, index) => {
return (
<EuiFlexItem key={index}>
<UseCase
title={workflow.name}
description={workflow.description || ''}
onClick={() =>
dispatch(
cacheWorkflow({
...workflow,
name: processWorkflowName(workflow.name),
})
)
}
/>
</EuiFlexItem>
);
})}
</EuiFlexGrid>
{loading ? (
<EuiLoadingSpinner size="xl" />
) : (
<EuiFlexGrid columns={3} gutterSize="l">
{filteredWorkflows.map((workflow: Workflow, index) => {
return (
<EuiFlexItem key={index}>
<UseCase
title={workflow.name}
description={workflow.description || ''}
onClick={() =>
dispatch(
cacheWorkflow({
...workflow,
name: processWorkflowName(workflow.name),
})
)
}
/>
</EuiFlexItem>
);
})}
</EuiFlexGrid>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
Expand Down
37 changes: 0 additions & 37 deletions public/store/reducers/workflows_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,6 @@ import {
import { HttpFetchError } from '../../../../../src/core/public';
import { getRouteService } from '../../services';

// TODO: remove hardcoded dummy node data below after fetching from server side,
// and workflow data model interface is more defined.
const id1 = generateId('text_embedding_processor');
const id2 = generateId('text_embedding_processor');
const id3 = generateId('knn_index');
const dummyNodes = [
{
id: id1,
position: { x: 0, y: 500 },
data: initComponentData(new TextEmbeddingTransformer().toObj(), id1),
type: 'customComponent',
},
{
id: id2,
position: { x: 0, y: 200 },
data: initComponentData(new TextEmbeddingTransformer().toObj(), id2),
type: 'customComponent',
},
{
id: id3,
position: { x: 500, y: 500 },
data: initComponentData(new KnnIndexer().toObj(), id3),
type: 'customComponent',
},
] as ReactFlowComponent[];

const initialState = {
loading: false,
errorMessage: '',
Expand Down Expand Up @@ -196,17 +170,6 @@ const workflowsSlice = createSlice({
})
.addCase(searchWorkflows.fulfilled, (state, action) => {
const { workflows } = action.payload as { workflows: WorkflowDict };

// TODO: remove hardcoded workspace flow state. For testing purposes only
Object.entries(workflows).forEach(([workflowId, workflow]) => {
workflows[workflowId] = {
...workflows[workflowId],
workspaceFlowState: {
nodes: dummyNodes,
edges: [] as ReactFlowEdge[],
},
};
});
state.workflows = workflows;
state.loading = false;
state.errorMessage = '';
Expand Down

0 comments on commit da14028

Please sign in to comment.