Skip to content

Commit

Permalink
Add a preset workflow library
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 616b1cb commit 54b4d45
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 104 deletions.
1 change: 1 addition & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const SEARCH_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/se
export const GET_WORKFLOW_STATE_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/state`;
export const CREATE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/create`;
export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/delete`;
export const GET_PRESET_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/presets`;

/**
* MISCELLANEOUS
Expand Down
27 changes: 13 additions & 14 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export type WorkspaceFlowState = {

export type TemplateNode = {
id: string;
inputs: {};
type: string;
previous_node_inputs?: Map<string, any>;
user_inputs?: Map<string, any>;
};

export type TemplateEdge = {
Expand All @@ -49,32 +51,30 @@ export type TemplateEdge = {
};

export type TemplateFlow = {
userParams: {};
user_params?: Map<string, any>;
nodes: TemplateNode[];
edges: TemplateEdge[];
};

export type TemplateFlows = {
provision: TemplateFlow;
ingest: TemplateFlow;
query: TemplateFlow;
};

export type UseCaseTemplate = {
type: string;
// A stateless template of a workflow
export type WorkflowTemplate = {
name: string;
description: string;
userInputs: {};
use_case: USE_CASE;
// TODO: finalize on version type when that is implemented
// https://github.com/opensearch-project/flow-framework/issues/526
version: any;
workflows: TemplateFlows;
};

export type Workflow = {
// An instance of a workflow based on a workflow template
export type Workflow = WorkflowTemplate & {
// won't exist until created in backend
id?: string;
name: string;
useCase: string;
template: UseCaseTemplate;
description?: string;
// ReactFlow state may not exist if a workflow is created via API/backend-only.
workspaceFlowState?: WorkspaceFlowState;
// won't exist until created in backend
Expand All @@ -86,8 +86,7 @@ export type Workflow = {
};

export enum USE_CASE {
SEMANTIC_SEARCH = 'semantic_search',
CUSTOM = 'custom',
PROVISION = 'PROVISION',
}

/**
Expand Down
27 changes: 10 additions & 17 deletions public/pages/workflow_detail/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {
WorkspaceFlowState,
UseCaseTemplate,
WorkflowTemplate,
Workflow,
USE_CASE,
ReactFlowComponent,
Expand All @@ -24,7 +24,7 @@ export function saveWorkflow(workflow: Workflow, rfInstance: any): void {
const updatedWorkflow = {
...workflow,
workspaceFlowState: curFlowState,
template: generateUseCaseTemplate(curFlowState),
template: generateWorkflowTemplate(curFlowState),
} as Workflow;
if (workflow.id) {
// TODO: implement connection to update workflow API
Expand All @@ -44,32 +44,25 @@ function validateFlowState(flowState: WorkspaceFlowState): boolean {
}

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

// Process the raw ReactFlow nodes to only persist the fields we need
Expand Down
11 changes: 9 additions & 2 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,15 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
// TODO: can optimize to only fetch a single workflow
dispatch(searchWorkflows({ query: { match_all: {} } }));
}
window.onbeforeunload = (e) =>
isDirty || isNewWorkflow ? true : undefined;

// TODO: below has the following issue:
// 1. user starts to create new unsaved workflow changes
// 2. user navigates to other parts of the plugin without refreshing - no warning happens
// 3. user refreshes at any later time: if isDirty is still true, shows browser warning
// tune to only handle the check if still on the workflow details page, or consider adding a check / warning
// if navigating away from the details page without refreshing (where it is currently not being triggered)
// window.onbeforeunload = (e) =>
// isDirty || isNewWorkflow ? true : undefined;
}, []);

const tabs = [
Expand Down
22 changes: 14 additions & 8 deletions public/pages/workflows/new_workflow/new_workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import {
EuiFlexGroup,
EuiFieldSearch,
} from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { UseCase } from './use_case';
import { getPresetWorkflows } from './presets';
import {
DEFAULT_NEW_WORKFLOW_NAME,
START_FROM_SCRATCH_WORKFLOW_NAME,
Workflow,
} from '../../../../common';
import { cacheWorkflow } from '../../../store';
import { AppState, cacheWorkflow } from '../../../store';
import { getWorkflowPresets } from '../../../store/reducers';

interface NewWorkflowProps {}

Expand All @@ -32,18 +32,24 @@ interface NewWorkflowProps {}
*/
export function NewWorkflow(props: NewWorkflowProps) {
const dispatch = useDispatch();
// preset workflow state
const presetWorkflows = getPresetWorkflows();
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>(
getPresetWorkflows()
);
const { presetWorkflows } = useSelector((state: AppState) => state.presets);
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>([]);

// search bar state
const [searchQuery, setSearchQuery] = useState<string>('');
const debounceSearchQuery = debounce((query: string) => {
setSearchQuery(query);
}, 200);

// initial state
useEffect(() => {
dispatch(getWorkflowPresets());
}, []);

useEffect(() => {
setFilteredWorkflows(presetWorkflows);
}, [presetWorkflows]);

// When search query updated, re-filter preset list
useEffect(() => {
setFilteredWorkflows(fetchFilteredWorkflows(presetWorkflows, searchQuery));
Expand Down
63 changes: 0 additions & 63 deletions public/pages/workflows/new_workflow/presets.tsx

This file was deleted.

12 changes: 12 additions & 0 deletions public/route_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
GET_WORKFLOW_NODE_API_PATH,
GET_WORKFLOW_STATE_NODE_API_PATH,
SEARCH_WORKFLOWS_NODE_API_PATH,
GET_PRESET_WORKFLOWS_NODE_API_PATH,
} from '../common';

/**
Expand All @@ -26,6 +27,7 @@ export interface RouteService {
getWorkflowState: (workflowId: string) => Promise<any | HttpFetchError>;
createWorkflow: (body: {}) => Promise<any | HttpFetchError>;
deleteWorkflow: (workflowId: string) => Promise<any | HttpFetchError>;
getWorkflowPresets: () => Promise<any | HttpFetchError>;
catIndices: (pattern: string) => Promise<any | HttpFetchError>;
}

Expand Down Expand Up @@ -87,6 +89,16 @@ export function configureRoutes(core: CoreStart): RouteService {
return e as HttpFetchError;
}
},
getWorkflowPresets: async () => {
try {
const response = await core.http.get<{ respString: string }>(
GET_PRESET_WORKFLOWS_NODE_API_PATH
);
return response;
} catch (e: any) {
return e as HttpFetchError;
}
},
catIndices: async (pattern: string) => {
try {
const response = await core.http.get<{ respString: string }>(
Expand Down
1 change: 1 addition & 0 deletions public/store/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
export * from './workspace_reducer';
export * from './opensearch_reducer';
export * from './workflows_reducer';
export * from './presets_reducer';
58 changes: 58 additions & 0 deletions public/store/reducers/presets_reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { Workflow } from '../../../common';
import { HttpFetchError } from '../../../../../src/core/public';
import { getRouteService } from '../../services';

const initialState = {
loading: false,
errorMessage: '',
presetWorkflows: [] as Workflow[],
};

const PRESET_ACTION_PREFIX = 'presets';
const GET_WORKFLOW_PRESETS_ACTION = `${PRESET_ACTION_PREFIX}/getPresets`;

export const getWorkflowPresets = createAsyncThunk(
GET_WORKFLOW_PRESETS_ACTION,
async (_, { rejectWithValue }) => {
const response:
| any
| HttpFetchError = await getRouteService().getWorkflowPresets();
if (response instanceof HttpFetchError) {
return rejectWithValue(
'Error getting workflow presets: ' + response.body.message
);
} else {
return response;
}
}
);

const presetsSlice = createSlice({
name: 'presets',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(getWorkflowPresets.pending, (state, action) => {
state.loading = true;
state.errorMessage = '';
})
.addCase(getWorkflowPresets.fulfilled, (state, action) => {
state.presetWorkflows = action.payload.workflowTemplates;
state.loading = false;
state.errorMessage = '';
})
.addCase(getWorkflowPresets.rejected, (state, action) => {
state.errorMessage = action.payload as string;
state.loading = false;
});
},
});

export const presetsReducer = presetsSlice.reducer;
1 change: 1 addition & 0 deletions public/store/reducers/workflows_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
initComponentData,
WORKFLOW_STATE,
WorkflowDict,
WorkflowTemplate,
} from '../../../common';
import { HttpFetchError } from '../../../../../src/core/public';
import { getRouteService } from '../../services';
Expand Down
2 changes: 2 additions & 0 deletions public/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import {
workspaceReducer,
opensearchReducer,
workflowsReducer,
presetsReducer,
} from './reducers';

const rootReducer = combineReducers({
workspace: workspaceReducer,
workflows: workflowsReducer,
presets: presetsReducer,
opensearch: opensearchReducer,
});

Expand Down
Loading

0 comments on commit 54b4d45

Please sign in to comment.