Skip to content

Commit 0362c6b

Browse files
authored
Add initial Flow Framework APIs; connect to Workflow List (#86)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent a17484e commit 0362c6b

16 files changed

+775
-172
lines changed

common/constants.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,26 @@
55

66
export const PLUGIN_ID = 'flow-framework';
77

8+
/**
9+
* BACKEND/CLUSTER APIs
10+
*/
11+
export const FLOW_FRAMEWORK_API_ROUTE_PREFIX = '/_plugins/_flow_framework';
12+
export const FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX = `${FLOW_FRAMEWORK_API_ROUTE_PREFIX}/workflow`;
13+
export const FLOW_FRAMEWORK_SEARCH_WORKFLOWS_ROUTE = `${FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX}/_search`;
14+
15+
/**
16+
* NODE APIs
17+
*/
818
export const BASE_NODE_API_PATH = '/api/flow_framework';
9-
export const BASE_INDICES_NODE_API_PATH = `${BASE_NODE_API_PATH}/indices`;
10-
export const SEARCH_INDICES_PATH = `${BASE_INDICES_NODE_API_PATH}/search`;
11-
export const FETCH_INDICES_PATH = `${BASE_INDICES_NODE_API_PATH}/fetch`;
19+
20+
// OpenSearch node APIs
21+
export const BASE_OPENSEARCH_NODE_API_PATH = `${BASE_NODE_API_PATH}/opensearch`;
22+
export const CAT_INDICES_NODE_API_PATH = `${BASE_OPENSEARCH_NODE_API_PATH}/catIndices`;
23+
24+
// Flow Framework node APIs
25+
export const BASE_WORKFLOW_NODE_API_PATH = `${BASE_NODE_API_PATH}/workflow`;
26+
export const GET_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}`;
27+
export const SEARCH_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/search`;
28+
export const GET_WORKFLOW_STATE_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/state`;
29+
export const CREATE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/create`;
30+
export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/delete`;

common/interfaces.ts

+4
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,7 @@ export enum WORKFLOW_STATE {
102102
IN_PROGRESS = 'In progress',
103103
NOT_STARTED = 'Not started',
104104
}
105+
106+
export type WorkflowDict = {
107+
[workflowId: string]: Workflow;
108+
};

public/pages/workflow_detail/workflow_detail.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ function replaceActiveTab(activeTab: string, props: WorkflowDetailProps) {
4949
export function WorkflowDetail(props: WorkflowDetailProps) {
5050
const { workflows } = useSelector((state: AppState) => state.workflows);
5151

52-
const workflow = workflows.find(
53-
(wf) => wf.id === props.match?.params?.workflowId
54-
);
52+
const workflow = workflows[props.match?.params?.workflowId];
5553
const workflowName = workflow ? workflow.name : '';
5654

5755
const tabFromUrl = queryString.parse(useLocation().search)[

public/pages/workflows/workflow_list/workflow_list.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,24 @@ export function WorkflowList(props: WorkflowListProps) {
3939
const [searchQuery, setSearchQuery] = useState<string>('');
4040
const debounceSearchQuery = debounce((query: string) => {
4141
setSearchQuery(query);
42-
}, 100);
42+
}, 200);
4343

4444
// filters state
4545
const [selectedStates, setSelectedStates] = useState<EuiFilterSelectItem[]>(
4646
getStateOptions()
4747
);
48-
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>(
49-
workflows || []
50-
);
48+
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>([]);
5149

52-
// When a filter selection or search query changes, update the list
50+
// When any filter changes or new workflows are found, update the list
5351
useEffect(() => {
5452
setFilteredWorkflows(
55-
fetchFilteredWorkflows(workflows, selectedStates, searchQuery)
53+
fetchFilteredWorkflows(
54+
Object.values(workflows),
55+
selectedStates,
56+
searchQuery
57+
)
5658
);
57-
}, [selectedStates, searchQuery]);
59+
}, [selectedStates, searchQuery, workflows]);
5860

5961
return (
6062
<EuiFlexGroup direction="column">

public/pages/workflows/workflows.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import {
1414
EuiSpacer,
1515
} from '@elastic/eui';
1616
import queryString from 'query-string';
17-
import { useSelector } from 'react-redux';
17+
import { useDispatch, useSelector } from 'react-redux';
1818
import { BREADCRUMBS } from '../../utils';
1919
import { getCore } from '../../services';
2020
import { WorkflowList } from './workflow_list';
2121
import { NewWorkflow } from './new_workflow';
22-
import { AppState } from '../../store';
22+
import { AppState, searchWorkflows } from '../../store';
2323

2424
export interface WorkflowsRouterProps {}
2525

@@ -47,6 +47,7 @@ function replaceActiveTab(activeTab: string, props: WorkflowsProps) {
4747
* to get started on a new workflow.
4848
*/
4949
export function Workflows(props: WorkflowsProps) {
50+
const dispatch = useDispatch();
5051
const { workflows } = useSelector((state: AppState) => state.workflows);
5152

5253
const tabFromUrl = queryString.parse(useLocation().search)[
@@ -61,7 +62,7 @@ export function Workflows(props: WorkflowsProps) {
6162
!selectedTabId ||
6263
!Object.values(WORKFLOWS_TAB).includes(selectedTabId)
6364
) {
64-
if (workflows?.length > 0) {
65+
if (Object.keys(workflows).length > 0) {
6566
setSelectedTabId(WORKFLOWS_TAB.MANAGE);
6667
replaceActiveTab(WORKFLOWS_TAB.MANAGE, props);
6768
} else {
@@ -78,6 +79,11 @@ export function Workflows(props: WorkflowsProps) {
7879
]);
7980
});
8081

82+
// On initial render: fetch all workflows
83+
useEffect(() => {
84+
dispatch(searchWorkflows({ query: { match_all: {} } }));
85+
}, []);
86+
8187
return (
8288
<EuiPage>
8389
<EuiPageBody>

public/route_service.ts

+68-7
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,47 @@
44
*/
55

66
import { CoreStart, HttpFetchError } from '../../../src/core/public';
7-
import { FETCH_INDICES_PATH, SEARCH_INDICES_PATH } from '../common';
7+
import {
8+
CREATE_WORKFLOW_NODE_API_PATH,
9+
DELETE_WORKFLOW_NODE_API_PATH,
10+
CAT_INDICES_NODE_API_PATH,
11+
GET_WORKFLOW_NODE_API_PATH,
12+
GET_WORKFLOW_STATE_NODE_API_PATH,
13+
SEARCH_WORKFLOWS_NODE_API_PATH,
14+
} from '../common';
815

16+
/**
17+
* A simple client-side service interface containing all of the available node API functions.
18+
* Exposed in services.ts.
19+
* Example function call: getRouteService().getWorkflow(<workflow-id>)
20+
*
21+
* Used in redux by wrapping them in async thunk functions which mutate redux state when executed.
22+
*/
923
export interface RouteService {
10-
searchIndex: (indexName: string, body: {}) => Promise<any | HttpFetchError>;
11-
fetchIndices: (pattern: string) => Promise<any | HttpFetchError>;
24+
getWorkflow: (workflowId: string) => Promise<any | HttpFetchError>;
25+
searchWorkflows: (body: {}) => Promise<any | HttpFetchError>;
26+
getWorkflowState: (workflowId: string) => Promise<any | HttpFetchError>;
27+
createWorkflow: (body: {}) => Promise<any | HttpFetchError>;
28+
deleteWorkflow: (workflowId: string) => Promise<any | HttpFetchError>;
29+
catIndices: (pattern: string) => Promise<any | HttpFetchError>;
1230
}
1331

1432
export function configureRoutes(core: CoreStart): RouteService {
1533
return {
16-
searchIndex: async (indexName: string, body: {}) => {
34+
getWorkflow: async (workflowId: string) => {
35+
try {
36+
const response = await core.http.get<{ respString: string }>(
37+
`${GET_WORKFLOW_NODE_API_PATH}/${workflowId}`
38+
);
39+
return response;
40+
} catch (e: any) {
41+
return e as HttpFetchError;
42+
}
43+
},
44+
searchWorkflows: async (body: {}) => {
1745
try {
1846
const response = await core.http.post<{ respString: string }>(
19-
`${SEARCH_INDICES_PATH}/${indexName}`,
47+
SEARCH_WORKFLOWS_NODE_API_PATH,
2048
{
2149
body: JSON.stringify(body),
2250
}
@@ -26,10 +54,43 @@ export function configureRoutes(core: CoreStart): RouteService {
2654
return e as HttpFetchError;
2755
}
2856
},
29-
fetchIndices: async (pattern: string) => {
57+
getWorkflowState: async (workflowId: string) => {
58+
try {
59+
const response = await core.http.get<{ respString: string }>(
60+
`${GET_WORKFLOW_STATE_NODE_API_PATH}/${workflowId}`
61+
);
62+
return response;
63+
} catch (e: any) {
64+
return e as HttpFetchError;
65+
}
66+
},
67+
createWorkflow: async (body: {}) => {
3068
try {
3169
const response = await core.http.post<{ respString: string }>(
32-
`${FETCH_INDICES_PATH}/${pattern}`
70+
CREATE_WORKFLOW_NODE_API_PATH,
71+
{
72+
body: JSON.stringify(body),
73+
}
74+
);
75+
return response;
76+
} catch (e: any) {
77+
return e as HttpFetchError;
78+
}
79+
},
80+
deleteWorkflow: async (workflowId: string) => {
81+
try {
82+
const response = await core.http.delete<{ respString: string }>(
83+
`${DELETE_WORKFLOW_NODE_API_PATH}/${workflowId}`
84+
);
85+
return response;
86+
} catch (e: any) {
87+
return e as HttpFetchError;
88+
}
89+
},
90+
catIndices: async (pattern: string) => {
91+
try {
92+
const response = await core.http.get<{ respString: string }>(
93+
`${CAT_INDICES_NODE_API_PATH}/${pattern}`
3394
);
3495
return response;
3596
} catch (e: any) {

public/store/reducers/opensearch_reducer.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
77
import { getRouteService } from '../../services';
88
import { Index } from '../../../common';
9+
import { HttpFetchError } from '../../../../../src/core/public';
910

1011
const initialState = {
1112
loading: false,
@@ -14,15 +15,23 @@ const initialState = {
1415
};
1516

1617
const OPENSEARCH_PREFIX = 'opensearch';
17-
const FETCH_INDICES_ACTION = `${OPENSEARCH_PREFIX}/fetchIndices`;
18+
const CAT_INDICES_ACTION = `${OPENSEARCH_PREFIX}/catIndices`;
1819

19-
export const fetchIndices = createAsyncThunk(
20-
FETCH_INDICES_ACTION,
21-
async (pattern?: string) => {
20+
export const catIndices = createAsyncThunk(
21+
CAT_INDICES_ACTION,
22+
async (pattern: string, { rejectWithValue }) => {
2223
// defaulting to fetch everything except system indices (starting with '.')
2324
const patternString = pattern || '*,-.*';
24-
const response = getRouteService().fetchIndices(patternString);
25-
return response;
25+
const response: any | HttpFetchError = await getRouteService().catIndices(
26+
patternString
27+
);
28+
if (response instanceof HttpFetchError) {
29+
return rejectWithValue(
30+
'Error running cat indices: ' + response.body.message
31+
);
32+
} else {
33+
return response;
34+
}
2635
}
2736
);
2837

@@ -32,18 +41,20 @@ const opensearchSlice = createSlice({
3241
reducers: {},
3342
extraReducers: (builder) => {
3443
builder
35-
.addCase(fetchIndices.pending, (state, action) => {
44+
.addCase(catIndices.pending, (state, action) => {
3645
state.loading = true;
46+
state.errorMessage = '';
3747
})
38-
.addCase(fetchIndices.fulfilled, (state, action) => {
48+
.addCase(catIndices.fulfilled, (state, action) => {
3949
const indicesMap = new Map<string, Index>();
4050
action.payload.forEach((index: Index) => {
4151
indicesMap.set(index.name, index);
4252
});
4353
state.indices = Object.fromEntries(indicesMap.entries());
4454
state.loading = false;
55+
state.errorMessage = '';
4556
})
46-
.addCase(fetchIndices.rejected, (state, action) => {
57+
.addCase(catIndices.rejected, (state, action) => {
4758
state.errorMessage = action.payload as string;
4859
state.loading = false;
4960
});

0 commit comments

Comments
 (0)