Open filters in side panel (#13304)
In this PR: - Open filters in the side panel for **workflows** - Open filters in the side panel for **workflow versions** - Preparation for opening filters in the side panel for **workflow runs** - Add many tests to increase the coverage Remaining to do: - Open filters in the side panel for **workflow runs** - Upon filter creation, open it in the side panel --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
eeade6e94c
commit
924e599cba
+31
@@ -0,0 +1,31 @@
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramBlankEdgeProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramBlankEdge = ({
|
||||
markerStart,
|
||||
markerEnd,
|
||||
sourceY,
|
||||
targetY,
|
||||
}: WorkflowDiagramBlankEdgeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+9
-2
@@ -1,9 +1,12 @@
|
||||
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramBlankEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramBlankEdge';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramCanvasEditableEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasEditableEffect';
|
||||
import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode';
|
||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||
import { WorkflowDiagramDefaultEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeEditable';
|
||||
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
||||
import { WorkflowDiagramFilterEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeEditable';
|
||||
import { WorkflowDiagramFilteringDisabledEdgeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeEditable';
|
||||
import { WorkflowDiagramStepNodeEditable } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeEditable';
|
||||
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
@@ -26,7 +29,11 @@ export const WorkflowDiagramCanvasEditable = ({
|
||||
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
||||
}}
|
||||
edgeTypes={{
|
||||
default: WorkflowDiagramDefaultEdge,
|
||||
blank: WorkflowDiagramBlankEdge,
|
||||
'filtering-disabled--editable':
|
||||
WorkflowDiagramFilteringDisabledEdgeEditable,
|
||||
'empty-filter--editable': WorkflowDiagramDefaultEdgeEditable,
|
||||
'filter--editable': WorkflowDiagramFilterEdgeEditable,
|
||||
}}
|
||||
tagContainerTestId="workflow-visualizer-status"
|
||||
tagColor={tagProps.color}
|
||||
|
||||
+7
-4
@@ -1,10 +1,11 @@
|
||||
import { WorkflowVersionStatus } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramCanvasReadonlyEffect } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasReadonlyEffect';
|
||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||
import { WorkflowDiagramDefaultEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeReadonly';
|
||||
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
||||
import { WorkflowDiagramFilterEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeReadonly';
|
||||
import { WorkflowDiagramFilteringDisabledEdgeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeReadonly';
|
||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||
import { getWorkflowVersionStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowVersionStatusTagProps';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
|
||||
@@ -25,8 +26,10 @@ export const WorkflowDiagramCanvasReadonly = ({
|
||||
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
||||
}}
|
||||
edgeTypes={{
|
||||
default: WorkflowDiagramDefaultEdge,
|
||||
success: WorkflowDiagramSuccessEdge,
|
||||
'filtering-disabled--readonly':
|
||||
WorkflowDiagramFilteringDisabledEdgeReadonly,
|
||||
'empty-filter--readonly': WorkflowDiagramDefaultEdgeReadonly,
|
||||
'filter--readonly': WorkflowDiagramFilterEdgeReadonly,
|
||||
}}
|
||||
tagContainerTestId="workflow-visualizer-status"
|
||||
tagColor={tagProps.color}
|
||||
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
import { WorkflowDiagramEdgeV1 } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV1';
|
||||
import { WorkflowDiagramEdgeV2Empty } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Empty';
|
||||
import { WorkflowDiagramEdgeV2Filter } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Filter';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getStraightPath,
|
||||
} from '@xyflow/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramDefaultEdge = ({
|
||||
source,
|
||||
target,
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
data,
|
||||
}: WorkflowDiagramDefaultEdgeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isWorkflowFilteringEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED,
|
||||
);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
if (!isDefined(data)) {
|
||||
throw new Error('Edge data is not defined');
|
||||
}
|
||||
|
||||
const displayEdgeV1 = !isWorkflowFilteringEnabled && data.isEdgeEditable;
|
||||
const displayEmptyFilters =
|
||||
isWorkflowFilteringEnabled &&
|
||||
data.edgeType === 'default' &&
|
||||
data.isEdgeEditable;
|
||||
const displayFilters =
|
||||
isWorkflowFilteringEnabled && data.edgeType === 'filter';
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
{displayEdgeV1 && (
|
||||
<WorkflowDiagramEdgeV1
|
||||
labelY={labelY}
|
||||
parentStepId={source}
|
||||
nextStepId={target}
|
||||
/>
|
||||
)}
|
||||
{displayEmptyFilters && (
|
||||
<WorkflowDiagramEdgeV2Empty
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
parentStepId={source}
|
||||
nextStepId={target}
|
||||
/>
|
||||
)}
|
||||
{displayFilters && (
|
||||
<WorkflowDiagramEdgeV2Filter
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
stepId={data.stepId}
|
||||
parentStepId={source}
|
||||
nextStepId={target}
|
||||
filterSettings={data.filterSettings}
|
||||
isEdgeEditable={data.isEdgeEditable}
|
||||
/>
|
||||
)}
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getStraightPath,
|
||||
} from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { IconFilter, IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
source,
|
||||
target,
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
}: WorkflowDiagramDefaultEdgeEditableProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
assertWorkflowWithCurrentVersionIsDefined(workflow);
|
||||
|
||||
const { createStep } = useCreateStep({ workflow });
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.nextStepId === target &&
|
||||
workflowInsertStepIds.parentStepId === source;
|
||||
|
||||
const handleCreateFilter = async () => {
|
||||
await createStep({
|
||||
newStepType: 'FILTER',
|
||||
parentStepId: source,
|
||||
nextStepId: target,
|
||||
});
|
||||
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
|
||||
workflowSelectedNodeComponentState,
|
||||
);
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
setWorkflowSelectedNode(source);
|
||||
|
||||
handleCreateFilter();
|
||||
};
|
||||
|
||||
const handleNodeButtonClick = () => {
|
||||
startNodeCreation({
|
||||
parentStepId: source,
|
||||
nextStepId: target,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer
|
||||
shouldDisplay={isSelected || hovered}
|
||||
>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: handleNodeButtonClick,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeReadonlyProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramDefaultEdgeReadonly = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
}: WorkflowDiagramDefaultEdgeReadonlyProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramBaseEdge';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramDefaultEdgeRun = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
data,
|
||||
}: WorkflowDiagramDefaultEdgeRunProps) => {
|
||||
const [edgePath] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<WorkflowRunDiagramBaseEdge
|
||||
edgePath={edgePath}
|
||||
edgeExecutionStatus={data?.edgeExecutionStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div<{
|
||||
labelY?: number;
|
||||
}>`
|
||||
position: absolute;
|
||||
transform: ${({ labelY }) => `translate(${21}px, ${(labelY || 0) - 14}px)`};
|
||||
`;
|
||||
|
||||
const StyledHoverZone = styled.div`
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
transform: translate(-13px, -16px);
|
||||
background: transparent;
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type WorkflowDiagramEdgeV1Props = {
|
||||
labelY?: number;
|
||||
parentStepId: string;
|
||||
nextStepId: string;
|
||||
};
|
||||
|
||||
export const WorkflowDiagramEdgeV1 = ({
|
||||
labelY,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
}: WorkflowDiagramEdgeV1Props) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.parentStepId === parentStepId &&
|
||||
workflowInsertStepIds.nextStepId === nextStepId;
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
labelY={labelY}
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<StyledWrapper
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<StyledHoverZone />
|
||||
{(hovered || isSelected) && (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: () => {
|
||||
startNodeCreation({ parentStepId, nextStepId });
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { assertWorkflowWithCurrentVersionIsDefined } from '@/workflow/utils/assertWorkflowWithCurrentVersionIsDefined';
|
||||
import { WorkflowDiagramEdgeV2EmptyContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2EmptyContent';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
|
||||
|
||||
type WorkflowDiagramEdgeV2EmptyProps = {
|
||||
labelX: number;
|
||||
labelY: number;
|
||||
parentStepId: string;
|
||||
nextStepId: string;
|
||||
};
|
||||
|
||||
export const WorkflowDiagramEdgeV2Empty = ({
|
||||
labelX,
|
||||
labelY,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
}: WorkflowDiagramEdgeV2EmptyProps) => {
|
||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
assertWorkflowWithCurrentVersionIsDefined(workflow);
|
||||
|
||||
const { createStep } = useCreateStep({ workflow });
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
return (
|
||||
<WorkflowDiagramEdgeV2EmptyContent
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
parentStepId={parentStepId}
|
||||
nextStepId={nextStepId}
|
||||
onCreateFilter={() => {
|
||||
return createStep({
|
||||
newStepType: 'FILTER',
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
});
|
||||
}}
|
||||
onCreateNode={() => {
|
||||
startNodeCreation({ parentStepId, nextStepId });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { IconFilter, IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
type WorkflowDiagramEdgeV2EmptyContentProps = {
|
||||
labelX: number;
|
||||
labelY: number;
|
||||
parentStepId: string;
|
||||
nextStepId: string;
|
||||
onCreateFilter: () => Promise<void>;
|
||||
onCreateNode: () => void;
|
||||
};
|
||||
|
||||
export const WorkflowDiagramEdgeV2EmptyContent = ({
|
||||
labelX,
|
||||
labelY,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
onCreateFilter,
|
||||
onCreateNode,
|
||||
}: WorkflowDiagramEdgeV2EmptyContentProps) => {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.nextStepId === nextStepId &&
|
||||
workflowInsertStepIds.parentStepId === parentStepId;
|
||||
|
||||
const handleCreateFilter = async () => {
|
||||
await onCreateFilter();
|
||||
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
|
||||
workflowSelectedNodeComponentState,
|
||||
);
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
setWorkflowSelectedNode(parentStepId);
|
||||
|
||||
handleCreateFilter();
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer
|
||||
shouldDisplay={isSelected || hovered}
|
||||
>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: onCreateNode,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
);
|
||||
};
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { WorkflowDiagramEdgeV2FilterContent } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2FilterContent';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
|
||||
import { FilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type WorkflowDiagramEdgeV2FilterProps = {
|
||||
labelX: number;
|
||||
labelY: number;
|
||||
stepId: string;
|
||||
parentStepId: string;
|
||||
nextStepId: string;
|
||||
filterSettings: FilterSettings;
|
||||
isEdgeEditable: boolean;
|
||||
};
|
||||
|
||||
export const WorkflowDiagramEdgeV2Filter = ({
|
||||
labelX,
|
||||
labelY,
|
||||
stepId,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
filterSettings,
|
||||
isEdgeEditable,
|
||||
}: WorkflowDiagramEdgeV2FilterProps) => {
|
||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
|
||||
const { deleteStep } = useDeleteStep({ workflow });
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
return (
|
||||
<WorkflowDiagramEdgeV2FilterContent
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
stepId={stepId}
|
||||
parentStepId={parentStepId}
|
||||
nextStepId={nextStepId}
|
||||
filterSettings={filterSettings}
|
||||
isEdgeEditable={isEdgeEditable}
|
||||
onDeleteFilter={() => {
|
||||
if (!isDefined(stepId)) {
|
||||
throw new Error(
|
||||
'Step ID must be configured for the edge when rendering a filter',
|
||||
);
|
||||
}
|
||||
|
||||
return deleteStep(stepId);
|
||||
}}
|
||||
onCreateNode={() => {
|
||||
startNodeCreation({ parentStepId: stepId, nextStepId });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-220
@@ -1,220 +0,0 @@
|
||||
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import { FilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowEditActionFilter';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconDotsVertical,
|
||||
IconFilter,
|
||||
IconFilterX,
|
||||
IconPlus,
|
||||
} from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledConfiguredFilterContainer = styled.div`
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
`;
|
||||
|
||||
type WorkflowDiagramEdgeV2FilterContentProps = {
|
||||
labelX: number;
|
||||
labelY: number;
|
||||
stepId: string;
|
||||
parentStepId: string;
|
||||
nextStepId: string;
|
||||
filterSettings: FilterSettings;
|
||||
onDeleteFilter: () => Promise<void>;
|
||||
onCreateNode: () => void;
|
||||
isEdgeEditable: boolean;
|
||||
};
|
||||
|
||||
export const WorkflowDiagramEdgeV2FilterContent = ({
|
||||
labelX,
|
||||
labelY,
|
||||
stepId,
|
||||
parentStepId,
|
||||
nextStepId,
|
||||
onDeleteFilter,
|
||||
onCreateNode,
|
||||
isEdgeEditable,
|
||||
}: WorkflowDiagramEdgeV2FilterContentProps) => {
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const setWorkflowDiagramPanOnDrag = useSetRecoilComponentStateV2(
|
||||
workflowDiagramPanOnDragComponentState,
|
||||
);
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.nextStepId === nextStepId &&
|
||||
(workflowInsertStepIds.parentStepId === parentStepId ||
|
||||
(isNonEmptyString(stepId) &&
|
||||
workflowInsertStepIds.parentStepId === stepId));
|
||||
|
||||
const dropdownId = `${WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}-${parentStepId}-${nextStepId}`;
|
||||
|
||||
const isDropdownOpen = useRecoilComponentValueV2(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
|
||||
const { openWorkflowEditStepInCommandMenu } = useWorkflowCommandMenu();
|
||||
|
||||
const setWorkflowSelectedNode = useSetRecoilComponentStateV2(
|
||||
workflowSelectedNodeComponentState,
|
||||
);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!isEdgeEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
setWorkflowSelectedNode(stepId);
|
||||
|
||||
if (isDefined(workflowVisualizerWorkflowId)) {
|
||||
openWorkflowEditStepInCommandMenu(
|
||||
workflowVisualizerWorkflowId,
|
||||
'Filter',
|
||||
IconFilter,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||
<StyledConfiguredFilterContainer>
|
||||
{hovered || isDropdownOpen || isSelected ? (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
{
|
||||
Icon: IconDotsVertical,
|
||||
onClick: () => {
|
||||
openDropdown({
|
||||
dropdownComponentInstanceIdFromProps: dropdownId,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledConfiguredFilterContainer>
|
||||
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={<div></div>}
|
||||
data-select-disable
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownStrategy="absolute"
|
||||
dropdownOffset={{
|
||||
x: 24,
|
||||
y: 4,
|
||||
}}
|
||||
onOpen={() => {
|
||||
setWorkflowDiagramPanOnDrag(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setWorkflowDiagramPanOnDrag(true);
|
||||
}}
|
||||
dropdownComponents={
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Filter"
|
||||
LeftIcon={IconFilter}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
handleFilterButtonClick();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Remove Filter"
|
||||
LeftIcon={IconFilterX}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
onDeleteFilter();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Add Node"
|
||||
LeftIcon={IconPlus}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
onCreateNode();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
);
|
||||
};
|
||||
+16
-3
@@ -13,10 +13,11 @@ import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/state
|
||||
import { addCreateStepNodes } from '@/workflow/workflow-diagram/utils/addCreateStepNodes';
|
||||
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
|
||||
import { mergeWorkflowDiagrams } from '@/workflow/workflow-diagram/utils/mergeWorkflowDiagrams';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { addEdgeOptions } from '@/workflow/workflow-diagram/utils/addEdgeOptions';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const WorkflowDiagramEffect = ({
|
||||
workflowWithCurrentVersion,
|
||||
@@ -36,6 +37,10 @@ export const WorkflowDiagramEffect = ({
|
||||
workflowLastCreatedStepIdComponentState,
|
||||
);
|
||||
|
||||
const isWorkflowFilteringEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED,
|
||||
);
|
||||
|
||||
const computeAndMergeNewWorkflowDiagram = useRecoilCallback(
|
||||
({ snapshot, set }) => {
|
||||
return (currentVersion: WorkflowVersion) => {
|
||||
@@ -45,7 +50,11 @@ export const WorkflowDiagramEffect = ({
|
||||
);
|
||||
|
||||
const nextWorkflowDiagram = addCreateStepNodes(
|
||||
addEdgeOptions(getWorkflowVersionDiagram(currentVersion)),
|
||||
getWorkflowVersionDiagram({
|
||||
workflowVersion: currentVersion,
|
||||
isWorkflowFilteringEnabled,
|
||||
isEditable: true,
|
||||
}),
|
||||
);
|
||||
|
||||
let mergedWorkflowDiagram = nextWorkflowDiagram;
|
||||
@@ -78,7 +87,11 @@ export const WorkflowDiagramEffect = ({
|
||||
set(workflowDiagramState, mergedWorkflowDiagram);
|
||||
};
|
||||
},
|
||||
[workflowLastCreatedStepIdState, workflowDiagramState],
|
||||
[
|
||||
workflowDiagramState,
|
||||
isWorkflowFilteringEnabled,
|
||||
workflowLastCreatedStepIdState,
|
||||
],
|
||||
);
|
||||
|
||||
const currentVersion = workflowWithCurrentVersion?.currentVersion;
|
||||
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
||||
import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
|
||||
import {
|
||||
WorkflowDiagramEdge,
|
||||
WorkflowDiagramEdgeData,
|
||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getStraightPath,
|
||||
} from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconDotsVertical,
|
||||
IconFilter,
|
||||
IconFilterX,
|
||||
IconPlus,
|
||||
} from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
type WorkflowDiagramFilterEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
const assertFilterEdgeDataOrThrow: (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => asserts data is WorkflowDiagramEdgeData & { edgeType: 'filter' } = (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => {
|
||||
if (data?.edgeType !== 'filter') {
|
||||
throw new Error('Edge data must be of type "filter"');
|
||||
}
|
||||
};
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledConfiguredFilterContainer = styled.div`
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
source,
|
||||
target,
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
data,
|
||||
}: WorkflowDiagramFilterEdgeEditableProps) => {
|
||||
assertFilterEdgeDataOrThrow(data);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||
workflowVisualizerWorkflowIdComponentState,
|
||||
);
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
|
||||
const { deleteStep } = useDeleteStep({ workflow });
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const setWorkflowDiagramPanOnDrag = useSetRecoilComponentStateV2(
|
||||
workflowDiagramPanOnDragComponentState,
|
||||
);
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.nextStepId === source &&
|
||||
(workflowInsertStepIds.parentStepId === target ||
|
||||
(isNonEmptyString(data.stepId) &&
|
||||
workflowInsertStepIds.parentStepId === data.stepId));
|
||||
|
||||
const dropdownId = `${WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}-${source}-${target}`;
|
||||
|
||||
const isDropdownOpen = useRecoilComponentValueV2(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { openWorkflowEditFilterInCommandMenu } =
|
||||
useOpenWorkflowEditFilterInCommandMenu();
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
openWorkflowEditFilterInCommandMenu({
|
||||
stepId: data.stepId,
|
||||
stepName: data.name,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||
<StyledConfiguredFilterContainer>
|
||||
{hovered || isDropdownOpen || isSelected ? (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
{
|
||||
Icon: IconDotsVertical,
|
||||
onClick: () => {
|
||||
openDropdown({
|
||||
dropdownComponentInstanceIdFromProps: dropdownId,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledConfiguredFilterContainer>
|
||||
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={<div></div>}
|
||||
data-select-disable
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownStrategy="absolute"
|
||||
dropdownOffset={{
|
||||
x: 24,
|
||||
y: 4,
|
||||
}}
|
||||
onOpen={() => {
|
||||
setWorkflowDiagramPanOnDrag(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setWorkflowDiagramPanOnDrag(true);
|
||||
}}
|
||||
dropdownComponents={
|
||||
<DropdownContent
|
||||
widthInPixels={GenericDropdownContentWidth.Narrow}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Filter"
|
||||
LeftIcon={IconFilter}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
handleFilterButtonClick();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Remove Filter"
|
||||
LeftIcon={IconFilterX}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
if (!isDefined(data.stepId)) {
|
||||
throw new Error(
|
||||
'Step ID must be configured for the edge when rendering a filter',
|
||||
);
|
||||
}
|
||||
|
||||
return deleteStep(data.stepId);
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Add Node"
|
||||
LeftIcon={IconPlus}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
startNodeCreation({
|
||||
parentStepId: data.stepId,
|
||||
nextStepId: target,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useOpenWorkflowViewFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowViewFilterInCommandMenu';
|
||||
import {
|
||||
WorkflowDiagramEdge,
|
||||
WorkflowDiagramEdgeData,
|
||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getStraightPath,
|
||||
} from '@xyflow/react';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
type WorkflowDiagramFilterEdgeReadonlyProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
const assertFilterEdgeDataOrThrow: (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => asserts data is WorkflowDiagramEdgeData & { edgeType: 'filter' } = (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => {
|
||||
if (data?.edgeType !== 'filter') {
|
||||
throw new Error('Edge data must be of type "filter"');
|
||||
}
|
||||
};
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledConfiguredFilterContainer = styled.div`
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramFilterEdgeReadonly = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
data,
|
||||
}: WorkflowDiagramFilterEdgeReadonlyProps) => {
|
||||
assertFilterEdgeDataOrThrow(data);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const { openWorkflowViewFilterInCommandMenu } =
|
||||
useOpenWorkflowViewFilterInCommandMenu();
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
openWorkflowViewFilterInCommandMenu({
|
||||
stepId: data.stepId,
|
||||
stepName: data.name,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||
<StyledConfiguredFilterContainer>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</StyledConfiguredFilterContainer>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramBaseEdge';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useOpenWorkflowRunFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowRunFilterInCommandMenu';
|
||||
import {
|
||||
WorkflowDiagramEdge,
|
||||
WorkflowDiagramEdgeData,
|
||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import styled from '@emotion/styled';
|
||||
import { EdgeLabelRenderer, EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconFilter } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
type WorkflowDiagramFilterEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
const assertFilterEdgeDataOrThrow: (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => asserts data is WorkflowDiagramEdgeData & { edgeType: 'filter' } = (
|
||||
data: WorkflowDiagramEdgeData | undefined,
|
||||
) => {
|
||||
if (data?.edgeType !== 'filter') {
|
||||
throw new Error('Edge data must be of type "filter"');
|
||||
}
|
||||
};
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledConfiguredFilterContainer = styled.div`
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramFilterEdgeRun = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
data,
|
||||
}: WorkflowDiagramFilterEdgeRunProps) => {
|
||||
assertFilterEdgeDataOrThrow(data);
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const { openWorkflowRunFilterInCommandMenu } =
|
||||
useOpenWorkflowRunFilterInCommandMenu();
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
if (!isDefined(data.runStatus)) {
|
||||
throw new Error('Run status must be set on edge data for workflow runs');
|
||||
}
|
||||
|
||||
openWorkflowRunFilterInCommandMenu({
|
||||
stepId: data.stepId,
|
||||
stepName: data.name,
|
||||
stepExecutionStatus: data.runStatus,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowRunDiagramBaseEdge
|
||||
edgePath={edgePath}
|
||||
edgeExecutionStatus={data.edgeExecutionStatus}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<WorkflowDiagramEdgeV2Container
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||
<StyledConfiguredFilterContainer>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconFilter,
|
||||
onClick: handleFilterButtonClick,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</StyledConfiguredFilterContainer>
|
||||
</WorkflowDiagramEdgeV2VisibilityContainer>
|
||||
</WorkflowDiagramEdgeV2Container>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div<{
|
||||
labelY?: number;
|
||||
}>`
|
||||
position: absolute;
|
||||
transform: ${({ labelY }) => `translate(${21}px, ${(labelY || 0) - 14}px)`};
|
||||
`;
|
||||
|
||||
const StyledHoverZone = styled.div`
|
||||
position: absolute;
|
||||
width: 48px;
|
||||
height: 52px;
|
||||
transform: translate(-13px, -16px);
|
||||
background: transparent;
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
pointer-events: all;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type WorkflowDiagramFilteringDisabledEdgeEditableProps =
|
||||
EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramFilteringDisabledEdgeEditable = ({
|
||||
markerStart,
|
||||
markerEnd,
|
||||
source,
|
||||
sourceY,
|
||||
target,
|
||||
targetY,
|
||||
}: WorkflowDiagramFilteringDisabledEdgeEditableProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath, , labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
workflowInsertStepIds.parentStepId === source &&
|
||||
workflowInsertStepIds.nextStepId === target;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
|
||||
<StyledContainer
|
||||
labelY={labelY}
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<StyledWrapper
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<StyledHoverZone />
|
||||
{(hovered || isSelected) && (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconPlus,
|
||||
onClick: () => {
|
||||
startNodeCreation({
|
||||
parentStepId: source,
|
||||
nextStepId: target,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramFilteringDisabledEdgeReadonlyProps =
|
||||
EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramFilteringDisabledEdgeReadonly = ({
|
||||
markerStart,
|
||||
markerEnd,
|
||||
sourceY,
|
||||
targetY,
|
||||
}: WorkflowDiagramFilteringDisabledEdgeReadonlyProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.border.color.strong }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramBaseEdge';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
import { WorkflowDiagramEdge } from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||
import { EdgeProps, getStraightPath } from '@xyflow/react';
|
||||
|
||||
type WorkflowDiagramFilteringDisabledEdgeRunProps =
|
||||
EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramFilteringDisabledEdgeRun = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
data,
|
||||
}: WorkflowDiagramFilteringDisabledEdgeRunProps) => {
|
||||
const [edgePath] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<WorkflowRunDiagramBaseEdge
|
||||
edgePath={edgePath}
|
||||
edgeExecutionStatus={data?.edgeExecutionStatus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import { EDGE_GREEN_ROUNDED_ARROW_MARKER_WIDTH_PX } from '@/workflow/workflow-diagram/constants/EdgeGreenRoundedArrowMarkerWidthPx';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getStraightPath,
|
||||
} from '@xyflow/react';
|
||||
import { Label } from 'twenty-ui/display';
|
||||
import { CREATE_STEP_NODE_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
||||
|
||||
const StyledLabel = styled(Label)`
|
||||
color: ${({ theme }) => theme.tag.text.turquoise};
|
||||
`;
|
||||
|
||||
type WorkflowDiagramSuccessEdgeProps = EdgeProps;
|
||||
|
||||
export const WorkflowDiagramSuccessEdge = ({
|
||||
sourceY,
|
||||
targetY,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
label,
|
||||
}: WorkflowDiagramSuccessEdgeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX: CREATE_STEP_NODE_WIDTH,
|
||||
sourceY,
|
||||
targetX: CREATE_STEP_NODE_WIDTH,
|
||||
targetY,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
markerStart={markerStart}
|
||||
markerEnd={markerEnd}
|
||||
path={edgePath}
|
||||
style={{ stroke: theme.tag.text.turquoise }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<StyledLabel
|
||||
variant="small"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(0, -50%) translate(${labelX}px, ${labelY}px) translateX(${EDGE_GREEN_ROUNDED_ARROW_MARKER_WIDTH_PX / 2 + 3}px)`,
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
className="nodrag nopan"
|
||||
>
|
||||
{label}
|
||||
</StyledLabel>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { EDGE_GRAY_CIRCLE_MARKED_ID } from '@/workflow/workflow-diagram/constants/EdgeGrayCircleMarkedId';
|
||||
import { EDGE_GREEN_CIRCLE_MARKED_ID } from '@/workflow/workflow-diagram/constants/EdgeGreenCircleMarkedId';
|
||||
import { EDGE_GREEN_ROUNDED_ARROW_MARKER_ID } from '@/workflow/workflow-diagram/constants/EdgeGreenRoundedArrowMarkerId';
|
||||
import { EDGE_ROUNDED_ARROW_MARKER_ID } from '@/workflow/workflow-diagram/constants/EdgeRoundedArrowMarkerId';
|
||||
import { Theme, useTheme } from '@emotion/react';
|
||||
import { BaseEdge } from '@xyflow/react';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
|
||||
const toMarkerId = (id: string) => `url(#${id})`;
|
||||
|
||||
const getMarkerStart = (edgeExecutionStatus: StepStatus | undefined) => {
|
||||
if (edgeExecutionStatus === StepStatus.SUCCESS) {
|
||||
return EDGE_GREEN_CIRCLE_MARKED_ID;
|
||||
}
|
||||
|
||||
return EDGE_GRAY_CIRCLE_MARKED_ID;
|
||||
};
|
||||
|
||||
const getMarkerEnd = (edgeExecutionStatus: StepStatus | undefined) => {
|
||||
if (edgeExecutionStatus === StepStatus.SUCCESS) {
|
||||
return EDGE_GREEN_ROUNDED_ARROW_MARKER_ID;
|
||||
}
|
||||
|
||||
return EDGE_ROUNDED_ARROW_MARKER_ID;
|
||||
};
|
||||
|
||||
const getStrokeColor = ({
|
||||
theme,
|
||||
edgeExecutionStatus,
|
||||
}: {
|
||||
theme: Theme;
|
||||
edgeExecutionStatus: StepStatus | undefined;
|
||||
}) => {
|
||||
if (edgeExecutionStatus === StepStatus.SUCCESS) {
|
||||
return theme.tag.text.turquoise;
|
||||
}
|
||||
|
||||
return theme.border.color.strong;
|
||||
};
|
||||
|
||||
type WorkflowRunDiagramBaseEdgeProps = {
|
||||
edgePath: string;
|
||||
edgeExecutionStatus: StepStatus | undefined;
|
||||
};
|
||||
|
||||
export const WorkflowRunDiagramBaseEdge = ({
|
||||
edgePath,
|
||||
edgeExecutionStatus,
|
||||
}: WorkflowRunDiagramBaseEdgeProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<BaseEdge
|
||||
markerStart={toMarkerId(getMarkerStart(edgeExecutionStatus))}
|
||||
markerEnd={toMarkerId(getMarkerEnd(edgeExecutionStatus))}
|
||||
path={edgePath}
|
||||
style={{
|
||||
stroke: getStrokeColor({
|
||||
theme,
|
||||
edgeExecutionStatus,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+6
-4
@@ -1,8 +1,9 @@
|
||||
import { WorkflowRunStatus } from '@/workflow/types/Workflow';
|
||||
import { WorkflowDiagramCanvasBase } from '@/workflow/workflow-diagram/components/WorkflowDiagramCanvasBase';
|
||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||
import { WorkflowDiagramDefaultEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdgeRun';
|
||||
import { WorkflowDiagramFilterEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilterEdgeRun';
|
||||
import { WorkflowDiagramFilteringDisabledEdgeRun } from '@/workflow/workflow-diagram/components/WorkflowDiagramFilteringDisabledEdgeRun';
|
||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||
import { WorkflowRunDiagramCanvasEffect } from '@/workflow/workflow-diagram/components/WorkflowRunDiagramCanvasEffect';
|
||||
import { useHandleWorkflowRunDiagramCanvasInit } from '@/workflow/workflow-diagram/hooks/useHandleWorkflowRunDiagramCanvasInit';
|
||||
import { getWorkflowRunStatusTagProps } from '@/workflow/workflow-diagram/utils/getWorkflowRunStatusTagProps';
|
||||
@@ -27,8 +28,9 @@ export const WorkflowRunDiagramCanvas = ({
|
||||
default: WorkflowDiagramStepNodeReadonly,
|
||||
}}
|
||||
edgeTypes={{
|
||||
default: WorkflowDiagramDefaultEdge,
|
||||
success: WorkflowDiagramSuccessEdge,
|
||||
'filtering-disabled--run': WorkflowDiagramFilteringDisabledEdgeRun,
|
||||
'empty-filter--run': WorkflowDiagramDefaultEdgeRun,
|
||||
'filter--run': WorkflowDiagramFilterEdgeRun,
|
||||
}}
|
||||
tagContainerTestId="workflow-run-status"
|
||||
tagColor={tagProps.color}
|
||||
|
||||
+8
@@ -18,10 +18,12 @@ import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/
|
||||
import { generateWorkflowRunDiagram } from '@/workflow/workflow-diagram/utils/generateWorkflowRunDiagram';
|
||||
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
|
||||
import { selectWorkflowDiagramNode } from '@/workflow/workflow-diagram/utils/selectWorkflowDiagramNode';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const WorkflowRunVisualizerEffect = ({
|
||||
workflowRunId,
|
||||
@@ -67,6 +69,10 @@ export const WorkflowRunVisualizerEffect = ({
|
||||
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const isWorkflowFilteringEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setWorkflowRunId(workflowRunId);
|
||||
}, [setWorkflowRunId, workflowRunId]);
|
||||
@@ -117,6 +123,7 @@ export const WorkflowRunVisualizerEffect = ({
|
||||
trigger: workflowRunState.flow.trigger,
|
||||
steps: workflowRunState.flow.steps,
|
||||
stepInfos: workflowRunState.stepInfos,
|
||||
isWorkflowFilteringEnabled,
|
||||
});
|
||||
|
||||
if (isDefined(stepToOpenByDefault)) {
|
||||
@@ -184,6 +191,7 @@ export const WorkflowRunVisualizerEffect = ({
|
||||
[
|
||||
flowState,
|
||||
getIcon,
|
||||
isWorkflowFilteringEnabled,
|
||||
openWorkflowRunViewStepInCommandMenu,
|
||||
workflowDiagramState,
|
||||
workflowDiagramStatusState,
|
||||
|
||||
+12
-2
@@ -6,8 +6,10 @@ import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/wo
|
||||
import { workflowVisualizerWorkflowVersionIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowVersionIdComponentState';
|
||||
import { workflowDiagramComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramComponentState';
|
||||
import { getWorkflowVersionDiagram } from '@/workflow/workflow-diagram/utils/getWorkflowVersionDiagram';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const WorkflowVersionVisualizerEffect = ({
|
||||
workflowVersionId,
|
||||
@@ -29,6 +31,10 @@ export const WorkflowVersionVisualizerEffect = ({
|
||||
|
||||
const { populateStepsOutputSchema } = useStepsOutputSchema();
|
||||
|
||||
const isWorkflowFilteringEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_FILTERING_ENABLED,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(workflowVersion)) {
|
||||
setFlow(undefined);
|
||||
@@ -58,10 +64,14 @@ export const WorkflowVersionVisualizerEffect = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const nextWorkflowDiagram = getWorkflowVersionDiagram(workflowVersion);
|
||||
const nextWorkflowDiagram = getWorkflowVersionDiagram({
|
||||
workflowVersion,
|
||||
isWorkflowFilteringEnabled,
|
||||
isEditable: false,
|
||||
});
|
||||
|
||||
setWorkflowDiagram(nextWorkflowDiagram);
|
||||
}, [setWorkflowDiagram, workflowVersion]);
|
||||
}, [isWorkflowFilteringEnabled, setWorkflowDiagram, workflowVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(workflowVersion)) {
|
||||
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { WorkflowDiagramCreateStepNode } from '@/workflow/workflow-diagram/components/WorkflowDiagramCreateStepNode';
|
||||
import { WorkflowDiagramDefaultEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramDefaultEdge';
|
||||
import { WorkflowDiagramEmptyTrigger } from '@/workflow/workflow-diagram/components/WorkflowDiagramEmptyTrigger';
|
||||
import { WorkflowDiagramStepNodeReadonly } from '@/workflow/workflow-diagram/components/WorkflowDiagramStepNodeReadonly';
|
||||
import { WorkflowDiagramSuccessEdge } from '@/workflow/workflow-diagram/components/WorkflowDiagramSuccessEdge';
|
||||
import { WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeDefaultConfiguration';
|
||||
import { WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION } from '@/workflow/workflow-diagram/constants/WorkflowVisualizerEdgeSuccessConfiguration';
|
||||
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
|
||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { workflowDiagramComponentState } from '../../states/workflowDiagramComponentState';
|
||||
import { WorkflowDiagramCanvasBase } from '../WorkflowDiagramCanvasBase';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Modules/Workflow/WorkflowDiagram/WorkflowDiagramCustomMarkers',
|
||||
component: WorkflowDiagramCanvasBase,
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
decorators: [
|
||||
WorkspaceDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
ReactflowDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof WorkflowDiagramCanvasBase>;
|
||||
|
||||
export const DefaultEdge: Story = {
|
||||
args: {
|
||||
nodeTypes: {
|
||||
default: WorkflowDiagramStepNodeReadonly,
|
||||
'create-step': WorkflowDiagramCreateStepNode,
|
||||
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
||||
},
|
||||
edgeTypes: {
|
||||
default: WorkflowDiagramDefaultEdge,
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const workflowVisualizerComponentInstanceId =
|
||||
'workflow-visualizer-test-id';
|
||||
|
||||
return (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
workflowDiagramComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
{
|
||||
nodes: [
|
||||
{
|
||||
id: 'trigger-1',
|
||||
type: 'default',
|
||||
position: { x: 100, y: 100 },
|
||||
data: {
|
||||
nodeType: 'trigger',
|
||||
triggerType: 'DATABASE_EVENT',
|
||||
name: 'When record is created',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'action-1',
|
||||
type: 'default',
|
||||
position: { x: 300, y: 100 },
|
||||
data: {
|
||||
nodeType: 'action',
|
||||
actionType: 'CREATE_RECORD',
|
||||
name: 'Create record',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'create-step-1',
|
||||
type: 'create-step',
|
||||
position: { x: 500, y: 100 },
|
||||
data: {
|
||||
nodeType: 'create-step',
|
||||
parentNodeId: 'action-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
|
||||
id: 'edge-1',
|
||||
source: 'trigger-1',
|
||||
target: 'action-1',
|
||||
},
|
||||
{
|
||||
...WORKFLOW_VISUALIZER_EDGE_DEFAULT_CONFIGURATION,
|
||||
id: 'edge-2',
|
||||
source: 'action-1',
|
||||
target: 'create-step-1',
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<WorkflowVisualizerComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<Story />
|
||||
</StyledContainer>
|
||||
</WorkflowVisualizerComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const SuccessEdge: Story = {
|
||||
args: {
|
||||
nodeTypes: {
|
||||
default: WorkflowDiagramStepNodeReadonly,
|
||||
'create-step': WorkflowDiagramCreateStepNode,
|
||||
'empty-trigger': WorkflowDiagramEmptyTrigger,
|
||||
},
|
||||
edgeTypes: {
|
||||
default: WorkflowDiagramDefaultEdge,
|
||||
success: WorkflowDiagramSuccessEdge,
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const workflowVisualizerComponentInstanceId =
|
||||
'workflow-visualizer-test-id';
|
||||
|
||||
return (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
workflowDiagramComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
{
|
||||
nodes: [
|
||||
{
|
||||
id: 'trigger-1',
|
||||
type: 'default',
|
||||
position: { x: 100, y: 100 },
|
||||
data: {
|
||||
nodeType: 'trigger',
|
||||
triggerType: 'DATABASE_EVENT',
|
||||
name: 'When record is created',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'action-1',
|
||||
type: 'default',
|
||||
position: { x: 300, y: 100 },
|
||||
data: {
|
||||
nodeType: 'action',
|
||||
actionType: 'CREATE_RECORD',
|
||||
name: 'Create record',
|
||||
},
|
||||
},
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
...WORKFLOW_VISUALIZER_EDGE_SUCCESS_CONFIGURATION,
|
||||
id: 'edge-1',
|
||||
source: 'trigger-1',
|
||||
target: 'action-1',
|
||||
type: 'success',
|
||||
label: '1 item',
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
<WorkflowVisualizerComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<Story />
|
||||
</StyledContainer>
|
||||
</WorkflowVisualizerComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
|
||||
import { WorkflowDiagramEdgeV2EmptyContent } from '../WorkflowDiagramEdgeV2EmptyContent';
|
||||
|
||||
const meta: Meta<typeof WorkflowDiagramEdgeV2EmptyContent> = {
|
||||
title: 'Modules/Workflow/WorkflowDiagramEdgeV2EmptyContent',
|
||||
component: WorkflowDiagramEdgeV2EmptyContent,
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
ReactflowDecorator,
|
||||
(Story) => {
|
||||
const workflowVisualizerComponentInstanceId =
|
||||
'workflow-visualizer-test-id';
|
||||
|
||||
return (
|
||||
<WorkflowVisualizerComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</WorkflowVisualizerComponentInstanceContext.Provider>
|
||||
);
|
||||
},
|
||||
],
|
||||
args: {
|
||||
labelX: 0,
|
||||
labelY: 0,
|
||||
parentStepId: 'parent-step-id',
|
||||
nextStepId: 'next-step-id',
|
||||
onCreateFilter: fn(),
|
||||
onCreateNode: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof WorkflowDiagramEdgeV2EmptyContent>;
|
||||
|
||||
export const ButtonsAppearOnHover: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const filterButton = buttons[0];
|
||||
|
||||
userEvent.hover(filterButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(filterButton).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const CreateFilter: Story = {
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const filterButton = buttons[0];
|
||||
|
||||
userEvent.hover(filterButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(filterButton).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.click(filterButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(args.onCreateFilter).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const AddNodeAction: Story = {
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const addNodeButton = buttons[1];
|
||||
|
||||
userEvent.hover(addNodeButton);
|
||||
|
||||
userEvent.click(addNodeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(args.onCreateNode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
};
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, waitFor, within } from '@storybook/test';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import {
|
||||
ComponentDecorator,
|
||||
getCanvasElementForDropdownTesting,
|
||||
} from 'twenty-ui/testing';
|
||||
import { ReactflowDecorator } from '~/testing/decorators/ReactflowDecorator';
|
||||
import { WorkflowDiagramEdgeV2FilterContent } from '../WorkflowDiagramEdgeV2FilterContent';
|
||||
|
||||
const meta: Meta<typeof WorkflowDiagramEdgeV2FilterContent> = {
|
||||
title: 'Modules/Workflow/WorkflowDiagramEdgeV2FilterContent',
|
||||
component: WorkflowDiagramEdgeV2FilterContent,
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
ReactflowDecorator,
|
||||
(Story) => {
|
||||
const workflowVisualizerComponentInstanceId =
|
||||
'workflow-visualizer-test-id';
|
||||
|
||||
return (
|
||||
<WorkflowVisualizerComponentInstanceContext.Provider
|
||||
value={{
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</WorkflowVisualizerComponentInstanceContext.Provider>
|
||||
);
|
||||
},
|
||||
],
|
||||
args: {
|
||||
labelX: 0,
|
||||
labelY: 0,
|
||||
parentStepId: 'parent-step-id',
|
||||
nextStepId: 'next-step-id',
|
||||
onDeleteFilter: fn(),
|
||||
onCreateNode: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof WorkflowDiagramEdgeV2FilterContent>;
|
||||
|
||||
export const ButtonsAppearOnHover: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const filterButton = buttons[0];
|
||||
|
||||
userEvent.hover(filterButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(filterButton).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const AddNodeAction: Story = {
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const dotsButton = buttons[1];
|
||||
|
||||
userEvent.hover(dotsButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dotsButton).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.click(dotsButton);
|
||||
|
||||
const addNodeButton = await within(
|
||||
getCanvasElementForDropdownTesting(),
|
||||
).findByText('Add Node');
|
||||
|
||||
userEvent.click(addNodeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(canvas.queryByText('Add Node')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(args.onCreateNode).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const DropdownInteractions: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const buttons = await canvas.findAllByRole('button');
|
||||
const dotsButton = buttons[1];
|
||||
|
||||
userEvent.hover(dotsButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dotsButton).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.click(dotsButton);
|
||||
|
||||
const dropdownCanvas = within(getCanvasElementForDropdownTesting());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdownCanvas.getByText('Filter')).toBeVisible();
|
||||
});
|
||||
|
||||
userEvent.click(canvasElement);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdownCanvas.queryByText('Filter')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
userEvent.click(dotsButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dropdownCanvas.getByText('Filter')).toBeVisible();
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user