Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] Add initial Flow Framework APIs; connect to Workflow List #87

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading