Skip to content

Commit

Permalink
Add initial Flow Framework APIs; connect to Workflow List (#86)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Feb 28, 2024
1 parent a17484e commit 0362c6b
Show file tree
Hide file tree
Showing 16 changed files with 775 additions and 172 deletions.
25 changes: 22 additions & 3 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,26 @@

export const PLUGIN_ID = 'flow-framework';

/**
* BACKEND/CLUSTER APIs
*/
export const FLOW_FRAMEWORK_API_ROUTE_PREFIX = '/_plugins/_flow_framework';
export const FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX = `${FLOW_FRAMEWORK_API_ROUTE_PREFIX}/workflow`;
export const FLOW_FRAMEWORK_SEARCH_WORKFLOWS_ROUTE = `${FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX}/_search`;

/**
* NODE APIs
*/
export const BASE_NODE_API_PATH = '/api/flow_framework';
export const BASE_INDICES_NODE_API_PATH = `${BASE_NODE_API_PATH}/indices`;
export const SEARCH_INDICES_PATH = `${BASE_INDICES_NODE_API_PATH}/search`;
export const FETCH_INDICES_PATH = `${BASE_INDICES_NODE_API_PATH}/fetch`;

// OpenSearch node APIs
export const BASE_OPENSEARCH_NODE_API_PATH = `${BASE_NODE_API_PATH}/opensearch`;
export const CAT_INDICES_NODE_API_PATH = `${BASE_OPENSEARCH_NODE_API_PATH}/catIndices`;

// Flow Framework node APIs
export const BASE_WORKFLOW_NODE_API_PATH = `${BASE_NODE_API_PATH}/workflow`;
export const GET_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}`;
export const SEARCH_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/search`;
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`;
4 changes: 4 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ export enum WORKFLOW_STATE {
IN_PROGRESS = 'In progress',
NOT_STARTED = 'Not started',
}

export type WorkflowDict = {
[workflowId: string]: Workflow;
};
4 changes: 1 addition & 3 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ function replaceActiveTab(activeTab: string, props: WorkflowDetailProps) {
export function WorkflowDetail(props: WorkflowDetailProps) {
const { workflows } = useSelector((state: AppState) => state.workflows);

const workflow = workflows.find(
(wf) => wf.id === props.match?.params?.workflowId
);
const workflow = workflows[props.match?.params?.workflowId];
const workflowName = workflow ? workflow.name : '';

const tabFromUrl = queryString.parse(useLocation().search)[
Expand Down
16 changes: 9 additions & 7 deletions public/pages/workflows/workflow_list/workflow_list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,24 @@ export function WorkflowList(props: WorkflowListProps) {
const [searchQuery, setSearchQuery] = useState<string>('');
const debounceSearchQuery = debounce((query: string) => {
setSearchQuery(query);
}, 100);
}, 200);

// filters state
const [selectedStates, setSelectedStates] = useState<EuiFilterSelectItem[]>(
getStateOptions()
);
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>(
workflows || []
);
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>([]);

// When a filter selection or search query changes, update the list
// When any filter changes or new workflows are found, update the list
useEffect(() => {
setFilteredWorkflows(
fetchFilteredWorkflows(workflows, selectedStates, searchQuery)
fetchFilteredWorkflows(
Object.values(workflows),
selectedStates,
searchQuery
)
);
}, [selectedStates, searchQuery]);
}, [selectedStates, searchQuery, workflows]);

return (
<EuiFlexGroup direction="column">
Expand Down
12 changes: 9 additions & 3 deletions public/pages/workflows/workflows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
EuiSpacer,
} from '@elastic/eui';
import queryString from 'query-string';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { BREADCRUMBS } from '../../utils';
import { getCore } from '../../services';
import { WorkflowList } from './workflow_list';
import { NewWorkflow } from './new_workflow';
import { AppState } from '../../store';
import { AppState, searchWorkflows } from '../../store';

export interface WorkflowsRouterProps {}

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

const tabFromUrl = queryString.parse(useLocation().search)[
Expand All @@ -61,7 +62,7 @@ export function Workflows(props: WorkflowsProps) {
!selectedTabId ||
!Object.values(WORKFLOWS_TAB).includes(selectedTabId)
) {
if (workflows?.length > 0) {
if (Object.keys(workflows).length > 0) {
setSelectedTabId(WORKFLOWS_TAB.MANAGE);
replaceActiveTab(WORKFLOWS_TAB.MANAGE, props);
} else {
Expand All @@ -78,6 +79,11 @@ export function Workflows(props: WorkflowsProps) {
]);
});

// On initial render: fetch all workflows
useEffect(() => {
dispatch(searchWorkflows({ query: { match_all: {} } }));
}, []);

return (
<EuiPage>
<EuiPageBody>
Expand Down
75 changes: 68 additions & 7 deletions public/route_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,47 @@
*/

import { CoreStart, HttpFetchError } from '../../../src/core/public';
import { FETCH_INDICES_PATH, SEARCH_INDICES_PATH } from '../common';
import {
CREATE_WORKFLOW_NODE_API_PATH,
DELETE_WORKFLOW_NODE_API_PATH,
CAT_INDICES_NODE_API_PATH,
GET_WORKFLOW_NODE_API_PATH,
GET_WORKFLOW_STATE_NODE_API_PATH,
SEARCH_WORKFLOWS_NODE_API_PATH,
} from '../common';

/**
* A simple client-side service interface containing all of the available node API functions.
* Exposed in services.ts.
* Example function call: getRouteService().getWorkflow(<workflow-id>)
*
* Used in redux by wrapping them in async thunk functions which mutate redux state when executed.
*/
export interface RouteService {
searchIndex: (indexName: string, body: {}) => Promise<any | HttpFetchError>;
fetchIndices: (pattern: string) => Promise<any | HttpFetchError>;
getWorkflow: (workflowId: string) => Promise<any | HttpFetchError>;
searchWorkflows: (body: {}) => Promise<any | HttpFetchError>;
getWorkflowState: (workflowId: string) => Promise<any | HttpFetchError>;
createWorkflow: (body: {}) => Promise<any | HttpFetchError>;
deleteWorkflow: (workflowId: string) => Promise<any | HttpFetchError>;
catIndices: (pattern: string) => Promise<any | HttpFetchError>;
}

export function configureRoutes(core: CoreStart): RouteService {
return {
searchIndex: async (indexName: string, body: {}) => {
getWorkflow: async (workflowId: string) => {
try {
const response = await core.http.get<{ respString: string }>(
`${GET_WORKFLOW_NODE_API_PATH}/${workflowId}`
);
return response;
} catch (e: any) {
return e as HttpFetchError;
}
},
searchWorkflows: async (body: {}) => {
try {
const response = await core.http.post<{ respString: string }>(
`${SEARCH_INDICES_PATH}/${indexName}`,
SEARCH_WORKFLOWS_NODE_API_PATH,
{
body: JSON.stringify(body),
}
Expand All @@ -26,10 +54,43 @@ export function configureRoutes(core: CoreStart): RouteService {
return e as HttpFetchError;
}
},
fetchIndices: async (pattern: string) => {
getWorkflowState: async (workflowId: string) => {
try {
const response = await core.http.get<{ respString: string }>(
`${GET_WORKFLOW_STATE_NODE_API_PATH}/${workflowId}`
);
return response;
} catch (e: any) {
return e as HttpFetchError;
}
},
createWorkflow: async (body: {}) => {
try {
const response = await core.http.post<{ respString: string }>(
`${FETCH_INDICES_PATH}/${pattern}`
CREATE_WORKFLOW_NODE_API_PATH,
{
body: JSON.stringify(body),
}
);
return response;
} catch (e: any) {
return e as HttpFetchError;
}
},
deleteWorkflow: async (workflowId: string) => {
try {
const response = await core.http.delete<{ respString: string }>(
`${DELETE_WORKFLOW_NODE_API_PATH}/${workflowId}`
);
return response;
} catch (e: any) {
return e as HttpFetchError;
}
},
catIndices: async (pattern: string) => {
try {
const response = await core.http.get<{ respString: string }>(
`${CAT_INDICES_NODE_API_PATH}/${pattern}`
);
return response;
} catch (e: any) {
Expand Down
29 changes: 20 additions & 9 deletions public/store/reducers/opensearch_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { getRouteService } from '../../services';
import { Index } from '../../../common';
import { HttpFetchError } from '../../../../../src/core/public';

const initialState = {
loading: false,
Expand All @@ -14,15 +15,23 @@ const initialState = {
};

const OPENSEARCH_PREFIX = 'opensearch';
const FETCH_INDICES_ACTION = `${OPENSEARCH_PREFIX}/fetchIndices`;
const CAT_INDICES_ACTION = `${OPENSEARCH_PREFIX}/catIndices`;

export const fetchIndices = createAsyncThunk(
FETCH_INDICES_ACTION,
async (pattern?: string) => {
export const catIndices = createAsyncThunk(
CAT_INDICES_ACTION,
async (pattern: string, { rejectWithValue }) => {
// defaulting to fetch everything except system indices (starting with '.')
const patternString = pattern || '*,-.*';
const response = getRouteService().fetchIndices(patternString);
return response;
const response: any | HttpFetchError = await getRouteService().catIndices(
patternString
);
if (response instanceof HttpFetchError) {
return rejectWithValue(
'Error running cat indices: ' + response.body.message
);
} else {
return response;
}
}
);

Expand All @@ -32,18 +41,20 @@ const opensearchSlice = createSlice({
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchIndices.pending, (state, action) => {
.addCase(catIndices.pending, (state, action) => {
state.loading = true;
state.errorMessage = '';
})
.addCase(fetchIndices.fulfilled, (state, action) => {
.addCase(catIndices.fulfilled, (state, action) => {
const indicesMap = new Map<string, Index>();
action.payload.forEach((index: Index) => {
indicesMap.set(index.name, index);
});
state.indices = Object.fromEntries(indicesMap.entries());
state.loading = false;
state.errorMessage = '';
})
.addCase(fetchIndices.rejected, (state, action) => {
.addCase(catIndices.rejected, (state, action) => {
state.errorMessage = action.payload as string;
state.loading = false;
});
Expand Down
Loading

0 comments on commit 0362c6b

Please sign in to comment.