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] Onboard get workflow API #134

Merged
merged 1 commit into from
Apr 16, 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
4 changes: 0 additions & 4 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ export type Index = {
*/

export type ReactFlowComponent = Node<IComponentData>;

// TODO: we may not need this re-defined type here at all, if we don't add
// any special fields/configuration for an edge. Currently this
// is the same as the default Edge type.
export type ReactFlowEdge = Edge<{}> & {};

type ReactFlowViewport = {
Expand Down
2 changes: 0 additions & 2 deletions public/component_types/indexer/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export class Indexer extends BaseComponent {
{
id: 'transformer',
label: 'Transformer',
// TODO: may need to change to be looser. it should be able to take
// in other component types
baseClass: COMPONENT_CLASS.TRANSFORMER,
acceptMultiple: false,
},
Expand Down
2 changes: 0 additions & 2 deletions public/component_types/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../utils';
*/
export type FieldType = 'string' | 'json' | 'select';
export type SelectType = 'model';
// TODO: this may expand to more types in the future. Formik supports 'any' so we can too.
// For now, limiting scope to expected types.
export type FieldValue = string | {};
export type ComponentFormValues = FormikValues;
export type WorkspaceFormValues = {
Expand Down
5 changes: 2 additions & 3 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { getCore } from '../../services';
import { WorkflowDetailHeader } from './components';
import {
AppState,
getWorkflow,
searchModels,
searchWorkflows,
useAppDispatch,
} from '../../store';
import { ResizableWorkspace } from './workspace';
Expand Down Expand Up @@ -109,8 +109,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
// - fetch available models as their IDs may be used when building flows
useEffect(() => {
if (!isNewWorkflow) {
// TODO: can optimize to only fetch a single workflow
dispatch(searchWorkflows(FETCH_ALL_QUERY_BODY));
dispatch(getWorkflow(workflowId));
}
dispatch(searchModels(FETCH_ALL_QUERY_BODY));
}, []);
Expand Down
22 changes: 16 additions & 6 deletions public/pages/workflow_detail/workspace/resizable_workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
WORKFLOW_STATE,
processNodes,
reduceToTemplate,
ReactFlowEdge,
} from '../../../../common';
import { validateWorkspaceFlow, toTemplateFlows } from '../utils';
import {
Expand Down Expand Up @@ -133,8 +134,13 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
* - open the panel if a node is selected and the panel is closed
* - it is assumed that only one node can be selected at once
*/
// TODO: make more typesafe
function onSelectionChange({ nodes, edges }) {
function onSelectionChange({
nodes,
edges,
}: {
nodes: ReactFlowComponent[];
edges: ReactFlowEdge[];
}) {
if (nodes && nodes.length > 0) {
setSelectedComponent(nodes[0]);
if (!isDetailsPanelOpen) {
Expand Down Expand Up @@ -276,7 +282,6 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
} as Workflow;
processWorkflowFn(updatedWorkflow);
} else {
// TODO: bubble up flow error?
setFlowValidOnSubmit(false);
setIsSaving(false);
}
Expand Down Expand Up @@ -336,7 +341,10 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
setIsDeprovisioning(false);
});
} else {
// TODO: this case should not happen
// This case should not happen
console.debug(
'Deprovisioning triggered on an invalid workflow. Ignoring.'
);
}
}}
>
Expand All @@ -360,7 +368,10 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
setIsProvisioning(false);
});
} else {
// TODO: this case should not happen
// This case should not happen
console.debug(
'Provisioning triggered on an invalid workflow. Ignoring.'
);
}
}}
>
Expand All @@ -370,7 +381,6 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) {
fill={false}
disabled={!isSaveable || isLoadingGlobal || isDeprovisionable}
isLoading={isSaving}
// TODO: if props.isNewWorkflow is true, clear the workflow cache if saving is successful.
onClick={() => {
setIsSaving(true);
dispatch(removeDirty());
Expand Down
10 changes: 8 additions & 2 deletions public/pages/workflow_detail/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { setDirty, useAppDispatch } from '../../../store';
import {
IComponentData,
ReactFlowComponent,
ReactFlowEdge,
Workflow,
} from '../../../../common';
import {
Expand All @@ -41,8 +42,13 @@ interface WorkspaceProps {
readonly: boolean;
onNodesChange: (nodes: ReactFlowComponent[]) => void;
id: string;
// TODO: make more typesafe
onSelectionChange: ({ nodes, edges }) => void;
onSelectionChange: ({
nodes,
edges,
}: {
nodes: ReactFlowComponent[];
edges: ReactFlowEdge[];
}) => void;
}

const nodeTypes = {
Expand Down
11 changes: 5 additions & 6 deletions public/store/reducers/workflows_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,11 @@ const workflowsSlice = createSlice({
// Fulfilled states: mutate state depending on the action type
// and payloads
.addCase(getWorkflow.fulfilled, (state, action) => {
// TODO: add logic to mutate state
// const workflow = action.payload;
// state.workflows = {
// ...state.workflows,
// [workflow.id]: workflow,
// };
const { workflow } = action.payload;
state.workflows = {
...state.workflows,
[workflow.id]: workflow,
};
state.loading = false;
state.errorMessage = '';
})
Expand Down
2 changes: 0 additions & 2 deletions public/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ export function getComponentSchema(data: IComponentData): ObjectSchema<any> {
return yup.object(schemaObj);
}

// TODO: finalize validations for different field types. May need
// to refer to some backend implementations or OpenSearch documentation
function getFieldSchema(field: IComponentField): Schema {
let baseSchema: Schema;
switch (field.type) {
Expand Down
21 changes: 17 additions & 4 deletions server/routes/flow_framework_routes_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
getWorkflowStateFromResponse,
getWorkflowsFromResponses,
isIgnorableError,
toWorkflowObj,
} from './helpers';

/**
Expand Down Expand Up @@ -152,7 +153,9 @@ export class FlowFrameworkRoutesService {
this.client = client;
}

// TODO: test e2e
// TODO: can remove or simplify if we can fetch all data from a single API call. Tracking issue:
// https://github.com/opensearch-project/flow-framework/issues/171
// Current implementation is making two calls and combining results via helper fn
getWorkflow = async (
context: RequestHandlerContext,
req: OpenSearchDashboardsRequest,
Expand All @@ -163,9 +166,19 @@ export class FlowFrameworkRoutesService {
const response = await this.client
.asScoped(req)
.callAsCurrentUser('flowFramework.getWorkflow', { workflow_id });
console.log('response from get workflow: ', response);
// TODO: format response
return res.ok({ body: response });
const workflow = toWorkflowObj(response, workflow_id);

const stateResponse = await this.client
.asScoped(req)
.callAsCurrentUser('flowFramework.getWorkflowState', { workflow_id });
const state = getWorkflowStateFromResponse(
stateResponse.state as typeof WORKFLOW_STATE
);
const workflowWithState = {
...workflow,
state,
};
return res.ok({ body: { workflow: workflowWithState } });
} catch (err: any) {
return generateCustomError(res, err);
}
Expand Down
13 changes: 7 additions & 6 deletions server/routes/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,10 @@ export function isIgnorableError(error: any): boolean {
return error.body?.error?.type === INDEX_NOT_FOUND_EXCEPTION;
}

function toWorkflowObj(workflowHit: any): Workflow {
// TODO: update schema parsing after hit schema has been updated.
// https://github.com/opensearch-project/flow-framework/issues/546
const hitSource = workflowHit.fields.filter[0];
// Convert backend workflow into frontend workflow obj
export function toWorkflowObj(hitSource: any, id: string): Workflow {
return {
id: workflowHit._id,
id,
name: hitSource.name,
use_case: hitSource.use_case,
description: hitSource.description || '',
Expand All @@ -59,7 +57,10 @@ export function getWorkflowsFromResponses(
): WorkflowDict {
const workflowDict = {} as WorkflowDict;
workflowHits.forEach((workflowHit: any) => {
workflowDict[workflowHit._id] = toWorkflowObj(workflowHit);
// TODO: update schema parsing after hit schema has been updated.
// https://github.com/opensearch-project/flow-framework/issues/546
const hitSource = workflowHit.fields.filter[0];
workflowDict[workflowHit._id] = toWorkflowObj(hitSource, workflowHit._id);
const workflowStateHit = workflowStateHits.find(
(workflowStateHit) => workflowStateHit._id === workflowHit._id
);
Expand Down
Loading