Skip to content
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
16 changes: 16 additions & 0 deletions packages/react-ui/setup-tests.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
import { TextDecoder, TextEncoder } from 'util';
Object.assign(global, { TextDecoder, TextEncoder });

jest.mock('react-markdown', () => ({
__esModule: true,
default: jest.fn().mockImplementation(({ children }) => children),
}));

jest.mock('@/app/lib/api', () => ({
API_BASE_URL: 'http://localhost:3000',
api: {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
patch: jest.fn(),
},
}));
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { LeftSideBarType } from '@/app/features/builder/builder-hooks';
import {
Button,
cn,
Expand All @@ -13,6 +12,7 @@ import { t } from 'i18next';
import { EllipsisVertical, History, Workflow } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { LeftSideBarType } from '../builder-types';

const ICON_SIZE_SMALL = 16;
const ICON_SIZE_LARGE = 24;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { BuilderHeaderActionBar } from '@/app/features/builder/builder-header/builder-header-action-bar';
import { SideMenuCollapsed } from '@/app/features/builder/builder-header/side-menu-collapsed';
import {
LeftSideBarType,
useBuilderStateContext,
} from '@/app/features/builder/builder-hooks';
import { useBuilderStateContext } from '@/app/features/builder/builder-hooks';
import { LeftSideBarType } from '../builder-types';

import { ExpandSideMenu } from '@/app/features/builder/builder-header/expand-side-menu';
import { WorkflowOverview } from '@/app/features/builder/builder-header/workflow-overview/workflow-overview';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { FlowVersionState } from '@openops/shared';
import { t } from 'i18next';
import React from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { LeftSideBarType } from '../builder-types';

import { SEARCH_PARAMS } from '@/app/constants/search-params';
import {
LeftSideBarType,
useBuilderStateContext,
useSwitchToDraft,
} from '@/app/features/builder/builder-hooks';
Expand Down
184 changes: 10 additions & 174 deletions packages/react-ui/src/app/features/builder/builder-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
AI_CHAT_CONTAINER_SIZES,
AiChatContainerSizeState,
INTERNAL_ERROR_TOAST,
toast,
} from '@openops/components/ui';
Expand All @@ -10,11 +9,8 @@ import { create, StateCreator, useStore } from 'zustand';
import { devtools } from 'zustand/middleware';

import { flowsApi } from '@/app/features/flows/lib/flows-api';
import { PromiseQueue } from '@/app/lib/promise-queue';
import { BlockProperty } from '@openops/blocks-framework';
import {
Flow,
flowHelper,
FlowOperationRequest,
FlowOperationType,
FlowRun,
Expand All @@ -24,10 +20,17 @@ import {
TriggerType,
} from '@openops/shared';
import { flowRunUtils } from '../flow-runs/lib/flow-run-utils';
import { aiChatApi } from './ai-chat/lib/chat-api';
import {
BuilderInitialState,
BuilderState,
InsertMentionHandler,
LeftSideBarType,
MidpanelAction,
MidpanelState,
RightSideBarType,
} from './builder-types';
import { DataSelectorSizeState } from './data-selector/data-selector-size-togglers';

const flowUpdatesQueue = new PromiseQueue();
import { updateFlowVersion } from './update-flow-version';

export const BuilderStateContext = createContext<BuilderStore | null>(null);

Expand Down Expand Up @@ -56,103 +59,6 @@ export function useSafeBuilderStateContext<T>(
return result;
}

export enum LeftSideBarType {
RUNS = 'runs',
VERSIONS = 'versions',
RUN_DETAILS = 'run-details',
MENU = 'menu',
TREE_VIEW = 'tree-view',
NONE = 'none',
}

export enum RightSideBarType {
NONE = 'none',
BLOCK_SETTINGS = 'block-settings',
}

type InsertMentionHandler = (propertyPath: string) => void;

export type MidpanelState = {
showDataSelector: boolean;
dataSelectorSize: DataSelectorSizeState;
showAiChat: boolean;
aiContainerSize: AiChatContainerSizeState;
aiChatProperty?: BlockProperty & {
inputName: `settings.input.${string}`;
};
codeToInject?: string;
};

type MidpanelAction =
| { type: 'FOCUS_INPUT_WITH_MENTIONS' }
| { type: 'DATASELECTOR_MIMIZE_CLICK' }
| { type: 'DATASELECTOR_DOCK_CLICK' }
| { type: 'DATASELECTOR_EXPAND_CLICK' }
| { type: 'AICHAT_CLOSE_CLICK' }
| { type: 'AICHAT_MIMIZE_CLICK' }
| { type: 'AICHAT_DOCK_CLICK' }
| { type: 'AICHAT_EXPAND_CLICK' }
| { type: 'PANEL_CLICK_AWAY' }
| {
type: 'GENERATE_WITH_AI_CLICK';
property?: BlockProperty & { inputName: `settings.input.${string}` };
}
| { type: 'ADD_CODE_TO_INJECT'; code: string }
| { type: 'CLEAN_CODE_TO_INJECT' };

export type BuilderState = {
flow: Flow;
flowVersion: FlowVersion;

readonly: boolean;
loopsIndexes: Record<string, number>;
run: FlowRun | null;
leftSidebar: LeftSideBarType;
rightSidebar: RightSideBarType;
selectedStep: string | null;
canExitRun: boolean;
activeDraggingStep: string | null;
saving: boolean;
refreshBlockFormSettings: boolean;
refreshSettings: () => void;
exitRun: () => void;
exitStepSettings: () => void;
renameFlowClientSide: (newName: string) => void;
moveToFolderClientSide: (folderId: string) => void;
setRun: (run: FlowRun, flowVersion: FlowVersion) => void;
setLeftSidebar: (leftSidebar: LeftSideBarType) => void;
setRightSidebar: (rightSidebar: RightSideBarType) => void;
applyOperation: (
operation: FlowOperationRequest,
onError: () => void,
) => void;
removeStepSelection: () => void;
selectStepByName: (stepName: string, openRightSideBar?: boolean) => void;
startSaving: () => void;
setActiveDraggingStep: (stepName: string | null) => void;
setFlow: (flow: Flow) => void;
exitBlockSelector: () => void;
setVersion: (flowVersion: FlowVersion) => void;
setVersionUpdateTimestamp: (updateTimestamp: string) => void;
insertMention: InsertMentionHandler | null;
setReadOnly: (readOnly: boolean) => void;
setInsertMentionHandler: (handler: InsertMentionHandler | null) => void;
setLoopIndex: (stepName: string, index: number) => void;
canUndo: boolean;
setCanUndo: (canUndo: boolean) => void;
canRedo: boolean;
setCanRedo: (canUndo: boolean) => void;
dynamicPropertiesAuthReconnectCounter: number;
refreshDynamicPropertiesForAuth: () => void;
midpanelState: MidpanelState;
applyMidpanelAction: (midpanelAction: MidpanelAction) => void;
};

export type BuilderInitialState = Pick<
BuilderState,
'flow' | 'flowVersion' | 'readonly' | 'run' | 'canExitRun'
>;

export type BuilderStore = ReturnType<typeof createBuilderStore>;
export const createBuilderStore = (initialState: BuilderInitialState) =>
create<BuilderState>(
Expand Down Expand Up @@ -597,73 +503,3 @@ const applyMidpanelAction = (state: BuilderState, action: MidpanelAction) => {
midpanelState: { ...state.midpanelState, ...newMidpanelState },
};
};

async function deleteChatRequest(flowVersion: FlowVersion, stepName: string) {
try {
const stepDetails = flowHelper.getStep(flowVersion, stepName);
const blockName = stepDetails?.settings?.blockName;
const chat = await aiChatApi.open(flowVersion.flowId, blockName, stepName);
await aiChatApi.delete(chat.chatId);
} catch (err) {
console.error(err);
}
}

const updateFlowVersion = (
state: BuilderState,
operation: FlowOperationRequest,
onError: () => void,
set: (
partial:
| BuilderState
| Partial<BuilderState>
| ((state: BuilderState) => BuilderState | Partial<BuilderState>),
replace?: boolean | undefined,
) => void,
) => {
const newFlowVersion = flowHelper.apply(state.flowVersion, operation);
if (
operation.type === FlowOperationType.DELETE_ACTION &&
operation.request.name === state.selectedStep
) {
set({ selectedStep: undefined });
set({ rightSidebar: RightSideBarType.NONE });
deleteChatRequest(state.flowVersion, operation.request.name);
}

if (operation.type === FlowOperationType.DUPLICATE_ACTION) {
set({
selectedStep: flowHelper.getStep(
newFlowVersion,
operation.request.stepName,
)?.nextAction?.name,
});
}

const updateRequest = async () => {
set({ saving: true });
try {
const updatedFlowVersion = await flowsApi.update(
state.flow.id,
operation,
);
set((state) => {
return {
flowVersion: {
...state.flowVersion,
id: updatedFlowVersion.version.id,
state: updatedFlowVersion.version.state,
updated: updatedFlowVersion.version.updated,
},
saving: flowUpdatesQueue.size() !== 0,
};
});
} catch (error) {
console.error(error);
flowUpdatesQueue.halt();
onError();
}
};
flowUpdatesQueue.add(updateRequest);
return { flowVersion: newFlowVersion };
};
106 changes: 106 additions & 0 deletions packages/react-ui/src/app/features/builder/builder-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { BlockProperty } from '@openops/blocks-framework';
import { AiChatContainerSizeState } from '@openops/components/ui';
import {
Flow,
FlowOperationRequest,
FlowRun,
FlowVersion,
} from '@openops/shared';
import { DataSelectorSizeState } from './data-selector/data-selector-size-togglers';

export enum LeftSideBarType {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all types moved from builder-hooks & updated other files to reference this. We get circular dependency issue on build if we don't extract out the types

RUNS = 'runs',
VERSIONS = 'versions',
RUN_DETAILS = 'run-details',
MENU = 'menu',
TREE_VIEW = 'tree-view',
NONE = 'none',
}

export enum RightSideBarType {
NONE = 'none',
BLOCK_SETTINGS = 'block-settings',
}

export type InsertMentionHandler = (propertyPath: string) => void;

export type MidpanelState = {
showDataSelector: boolean;
dataSelectorSize: DataSelectorSizeState;
showAiChat: boolean;
aiContainerSize: AiChatContainerSizeState;
aiChatProperty?: BlockProperty & {
inputName: `settings.input.${string}`;
};
codeToInject?: string;
};

export type MidpanelAction =
| { type: 'FOCUS_INPUT_WITH_MENTIONS' }
| { type: 'DATASELECTOR_MIMIZE_CLICK' }
| { type: 'DATASELECTOR_DOCK_CLICK' }
| { type: 'DATASELECTOR_EXPAND_CLICK' }
| { type: 'AICHAT_CLOSE_CLICK' }
| { type: 'AICHAT_MIMIZE_CLICK' }
| { type: 'AICHAT_DOCK_CLICK' }
| { type: 'AICHAT_EXPAND_CLICK' }
| { type: 'PANEL_CLICK_AWAY' }
| {
type: 'GENERATE_WITH_AI_CLICK';
property?: BlockProperty & { inputName: `settings.input.${string}` };
}
| { type: 'ADD_CODE_TO_INJECT'; code: string }
| { type: 'CLEAN_CODE_TO_INJECT' };

export type BuilderState = {
flow: Flow;
flowVersion: FlowVersion;

readonly: boolean;
loopsIndexes: Record<string, number>;
run: FlowRun | null;
leftSidebar: LeftSideBarType;
rightSidebar: RightSideBarType;
selectedStep: string | null;
canExitRun: boolean;
activeDraggingStep: string | null;
saving: boolean;
refreshBlockFormSettings: boolean;
refreshSettings: () => void;
exitRun: () => void;
exitStepSettings: () => void;
renameFlowClientSide: (newName: string) => void;
moveToFolderClientSide: (folderId: string) => void;
setRun: (run: FlowRun, flowVersion: FlowVersion) => void;
setLeftSidebar: (leftSidebar: LeftSideBarType) => void;
setRightSidebar: (rightSidebar: RightSideBarType) => void;
applyOperation: (
operation: FlowOperationRequest,
onError: () => void,
) => void;
removeStepSelection: () => void;
selectStepByName: (stepName: string, openRightSideBar?: boolean) => void;
startSaving: () => void;
setActiveDraggingStep: (stepName: string | null) => void;
setFlow: (flow: Flow) => void;
exitBlockSelector: () => void;
setVersion: (flowVersion: FlowVersion) => void;
setVersionUpdateTimestamp: (updateTimestamp: string) => void;
insertMention: InsertMentionHandler | null;
setReadOnly: (readOnly: boolean) => void;
setInsertMentionHandler: (handler: InsertMentionHandler | null) => void;
setLoopIndex: (stepName: string, index: number) => void;
canUndo: boolean;
setCanUndo: (canUndo: boolean) => void;
canRedo: boolean;
setCanRedo: (canUndo: boolean) => void;
dynamicPropertiesAuthReconnectCounter: number;
refreshDynamicPropertiesForAuth: () => void;
midpanelState: MidpanelState;
applyMidpanelAction: (midpanelAction: MidpanelAction) => void;
};

export type BuilderInitialState = Pick<
BuilderState,
'flow' | 'flowVersion' | 'readonly' | 'run' | 'canExitRun'
>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { useCallback, useState } from 'react';

import { Action, flowHelper, isNil, Trigger } from '@openops/shared';

import { BuilderState, useBuilderStateContext } from '../builder-hooks';
import { useBuilderStateContext } from '../builder-hooks';

import { BuilderState } from '../builder-types';
import { DataSelectorNode } from './data-selector-node';
import {
DataSelectorSizeState,
Expand Down
Loading
Loading