Add counter to filter (#14108)
See figma https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=70822-35001&m=dev <img width="75" height="61" alt="Capture d’écran 2025-08-27 à 16 57 46" src="https://github.com/user-attachments/assets/e41290c8-3ec5-4d64-a5d2-d52bd14ccd57" /> <img width="75" height="61" alt="Capture d’écran 2025-08-27 à 16 57 58" src="https://github.com/user-attachments/assets/710d5735-c256-47fc-ad2f-93e4f72b8002" /> <img width="121" height="89" alt="Capture d’écran 2025-08-27 à 16 16 33" src="https://github.com/user-attachments/assets/0c8216b2-f251-4f0e-ad9f-f3a25229df40" /> https://github.com/user-attachments/assets/43156c0c-91b8-4386-a971-e37577e3c199
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||||
|
import { flowComponentState } from '@/workflow/states/flowComponentState';
|
||||||
|
import type { WorkflowAction } from '@/workflow/types/Workflow';
|
||||||
|
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const createStepSelector = (stepId: string) =>
|
||||||
|
createComponentSelector<WorkflowAction | null>({
|
||||||
|
key: `stepSelector-${stepId}`,
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const flowState = get(flowComponentState.atomFamily({ instanceId }));
|
||||||
|
|
||||||
|
if (!isDefined(flowState) || !isDefined(flowState.steps)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = flowState.steps.find(
|
||||||
|
(step: WorkflowAction) => step.id === stepId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return step ?? null;
|
||||||
|
},
|
||||||
|
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
|
||||||
|
});
|
||||||
+12
-2
@@ -1,11 +1,12 @@
|
|||||||
import { type Theme } from '@emotion/react';
|
|
||||||
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
|
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
|
||||||
|
import { type Theme } from '@emotion/react';
|
||||||
|
|
||||||
type WorkflowDiagramColors = {
|
type WorkflowDiagramColors = {
|
||||||
background: string;
|
background: string;
|
||||||
borderColor: string;
|
borderColor: string;
|
||||||
color: string;
|
color: string;
|
||||||
titleColor: string;
|
titleColor: string;
|
||||||
|
tagBackground: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowDiagramNodeColors = {
|
export type WorkflowDiagramNodeColors = {
|
||||||
@@ -29,28 +30,33 @@ export const getWorkflowDiagramColors = ({
|
|||||||
borderColor: theme.color.yellow,
|
borderColor: theme.color.yellow,
|
||||||
color: theme.tag.text.yellow,
|
color: theme.tag.text.yellow,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.yellow,
|
||||||
},
|
},
|
||||||
unselected: {
|
unselected: {
|
||||||
background: theme.background.secondary,
|
background: theme.background.secondary,
|
||||||
borderColor: theme.border.color.strong,
|
borderColor: theme.border.color.strong,
|
||||||
color: theme.tag.text.yellow,
|
color: theme.tag.text.yellow,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.yellow,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'FAILED': {
|
case 'FAILED':
|
||||||
|
case 'STOPPED': {
|
||||||
return {
|
return {
|
||||||
selected: {
|
selected: {
|
||||||
background: theme.adaptiveColors.red1,
|
background: theme.adaptiveColors.red1,
|
||||||
borderColor: theme.color.red,
|
borderColor: theme.color.red,
|
||||||
color: theme.tag.text.red,
|
color: theme.tag.text.red,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.red,
|
||||||
},
|
},
|
||||||
unselected: {
|
unselected: {
|
||||||
background: theme.background.secondary,
|
background: theme.background.secondary,
|
||||||
borderColor: theme.border.color.strong,
|
borderColor: theme.border.color.strong,
|
||||||
color: theme.tag.text.red,
|
color: theme.tag.text.red,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.red,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -61,12 +67,14 @@ export const getWorkflowDiagramColors = ({
|
|||||||
borderColor: theme.color.turquoise,
|
borderColor: theme.color.turquoise,
|
||||||
color: theme.tag.text.green,
|
color: theme.tag.text.green,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.turquoise,
|
||||||
},
|
},
|
||||||
unselected: {
|
unselected: {
|
||||||
background: theme.background.secondary,
|
background: theme.background.secondary,
|
||||||
borderColor: theme.border.color.strong,
|
borderColor: theme.border.color.strong,
|
||||||
color: theme.tag.text.green,
|
color: theme.tag.text.green,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.tag.background.turquoise,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -77,12 +85,14 @@ export const getWorkflowDiagramColors = ({
|
|||||||
borderColor: theme.color.blue,
|
borderColor: theme.color.blue,
|
||||||
color: theme.tag.text.blue,
|
color: theme.tag.text.blue,
|
||||||
titleColor: theme.font.color.primary,
|
titleColor: theme.font.color.primary,
|
||||||
|
tagBackground: theme.border.color.strong,
|
||||||
},
|
},
|
||||||
unselected: {
|
unselected: {
|
||||||
background: theme.background.secondary,
|
background: theme.background.secondary,
|
||||||
borderColor: theme.border.color.strong,
|
borderColor: theme.border.color.strong,
|
||||||
color: theme.font.color.tertiary,
|
color: theme.font.color.tertiary,
|
||||||
titleColor: theme.font.color.light,
|
titleColor: theme.font.color.light,
|
||||||
|
tagBackground: theme.border.color.strong,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -9,12 +9,6 @@ import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
|||||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||||
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
|
|
||||||
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
|
|
||||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
|
||||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
|
||||||
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
|
||||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
|
||||||
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
|
import { useOpenWorkflowEditFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowEditFilterInCommandMenu';
|
||||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||||
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
|
import { workflowDiagramPanOnDragComponentState } from '@/workflow/workflow-diagram/states/workflowDiagramPanOnDragComponentState';
|
||||||
@@ -24,10 +18,18 @@ import {
|
|||||||
type WorkflowDiagramEdgeData,
|
type WorkflowDiagramEdgeData,
|
||||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
||||||
|
import { WorkflowDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramBaseEdge';
|
||||||
|
import { WorkflowDiagramEdgeButtonGroup } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeButtonGroup';
|
||||||
|
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
||||||
|
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||||
|
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||||
|
import { useEdgeState } from '@/workflow/workflow-diagram/workflow-edges/hooks/useEdgeState';
|
||||||
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
|
import { useDeleteEdge } from '@/workflow/workflow-steps/hooks/useDeleteEdge';
|
||||||
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
|
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
|
||||||
|
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
|
||||||
|
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { css } from '@emotion/react';
|
import { css, useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
@@ -38,6 +40,7 @@ import {
|
|||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IconDotsVertical,
|
IconDotsVertical,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
@@ -61,6 +64,12 @@ const assertFilterEdgeDataOrThrow: (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StyledConfiguredFilterContainer = styled.div`
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
|
const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
|
||||||
@@ -74,11 +83,6 @@ const StyledIconButtonGroup = styled(IconButtonGroup)<{ selected?: boolean }>`
|
|||||||
}}
|
}}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledConfiguredFilterContainer = styled.div`
|
|
||||||
height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const WorkflowDiagramFilterEdgeEditable = ({
|
export const WorkflowDiagramFilterEdgeEditable = ({
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
@@ -93,6 +97,7 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
|||||||
assertFilterEdgeDataOrThrow(data);
|
assertFilterEdgeDataOrThrow(data);
|
||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
const theme = useTheme();
|
||||||
const isWorkflowBranchEnabled = useIsFeatureEnabled(
|
const isWorkflowBranchEnabled = useIsFeatureEnabled(
|
||||||
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
|
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
|
||||||
);
|
);
|
||||||
@@ -178,6 +183,9 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
|||||||
await deleteEdge({ source, target });
|
await deleteEdge({ source, target });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { filterCounter } = useFilterCounter({ stepId: data.stepId });
|
||||||
|
const { unselected } = getWorkflowDiagramColors({ theme });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WorkflowDiagramBaseEdge
|
<WorkflowDiagramBaseEdge
|
||||||
@@ -196,6 +204,11 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
|||||||
>
|
>
|
||||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||||
<StyledConfiguredFilterContainer>
|
<StyledConfiguredFilterContainer>
|
||||||
|
<WorkflowStepFilterCounter
|
||||||
|
backgroundColor={unselected.tagBackground}
|
||||||
|
textColor={theme.font.color.inverted}
|
||||||
|
counter={filterCounter}
|
||||||
|
/>
|
||||||
{isEdgeHovered({ source, target }) ||
|
{isEdgeHovered({ source, target }) ||
|
||||||
isDropdownOpen ||
|
isDropdownOpen ||
|
||||||
nodeCreationStarted ? (
|
nodeCreationStarted ? (
|
||||||
|
|||||||
+15
-3
@@ -1,9 +1,6 @@
|
|||||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||||
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
|
||||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
|
||||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
|
||||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
|
||||||
import { useOpenWorkflowViewFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowViewFilterInCommandMenu';
|
import { useOpenWorkflowViewFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowViewFilterInCommandMenu';
|
||||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +8,11 @@ import {
|
|||||||
type WorkflowDiagramEdgeData,
|
type WorkflowDiagramEdgeData,
|
||||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
||||||
|
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
||||||
|
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||||
|
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||||
|
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
|
||||||
|
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
|
||||||
import { css, useTheme } from '@emotion/react';
|
import { css, useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
@@ -102,6 +104,11 @@ export const WorkflowDiagramFilterEdgeReadonly = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { filterCounter } = useFilterCounter({
|
||||||
|
stepId: data.stepId,
|
||||||
|
});
|
||||||
|
const { unselected } = getWorkflowDiagramColors({ theme });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseEdge
|
<BaseEdge
|
||||||
@@ -119,6 +126,11 @@ export const WorkflowDiagramFilterEdgeReadonly = ({
|
|||||||
>
|
>
|
||||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||||
<StyledConfiguredFilterContainer>
|
<StyledConfiguredFilterContainer>
|
||||||
|
<WorkflowStepFilterCounter
|
||||||
|
backgroundColor={unselected.tagBackground}
|
||||||
|
textColor={theme.font.color.inverted}
|
||||||
|
counter={filterCounter}
|
||||||
|
/>
|
||||||
<StyledIconButtonGroup
|
<StyledIconButtonGroup
|
||||||
className="nodrag nopan"
|
className="nodrag nopan"
|
||||||
iconButtons={[
|
iconButtons={[
|
||||||
|
|||||||
+21
-6
@@ -1,8 +1,5 @@
|
|||||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
|
||||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
|
||||||
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowRunDiagramBaseEdge';
|
|
||||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
|
||||||
import { useOpenWorkflowRunFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowRunFilterInCommandMenu';
|
import { useOpenWorkflowRunFilterInCommandMenu } from '@/workflow/workflow-diagram/hooks/useOpenWorkflowRunFilterInCommandMenu';
|
||||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +7,13 @@ import {
|
|||||||
type WorkflowDiagramEdgeData,
|
type WorkflowDiagramEdgeData,
|
||||||
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
|
||||||
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
import { getWorkflowDiagramColors } from '@/workflow/workflow-diagram/utils/getWorkflowDiagramColors';
|
||||||
import { css } from '@emotion/react';
|
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2Container';
|
||||||
|
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||||
|
import { WorkflowRunDiagramBaseEdge } from '@/workflow/workflow-diagram/workflow-edges/components/WorkflowRunDiagramBaseEdge';
|
||||||
|
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/workflow-edges/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||||
|
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
|
||||||
|
import { useFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterCounter';
|
||||||
|
import { css, useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +24,6 @@ import {
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconFilter } from 'twenty-ui/display';
|
import { IconFilter } from 'twenty-ui/display';
|
||||||
import { IconButtonGroup } from 'twenty-ui/input';
|
import { IconButtonGroup } from 'twenty-ui/input';
|
||||||
import type { WorkflowRunStepStatus } from '@/workflow/types/Workflow';
|
|
||||||
|
|
||||||
type WorkflowDiagramFilterEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
|
type WorkflowDiagramFilterEdgeRunProps = EdgeProps<WorkflowDiagramEdge>;
|
||||||
|
|
||||||
@@ -67,6 +69,8 @@ export const WorkflowDiagramFilterEdgeRun = ({
|
|||||||
}: WorkflowDiagramFilterEdgeRunProps) => {
|
}: WorkflowDiagramFilterEdgeRunProps) => {
|
||||||
assertFilterEdgeDataOrThrow(data);
|
assertFilterEdgeDataOrThrow(data);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
@@ -96,6 +100,12 @@ export const WorkflowDiagramFilterEdgeRun = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { filterCounter } = useFilterCounter({ stepId: data.stepId });
|
||||||
|
const { selected } = getWorkflowDiagramColors({
|
||||||
|
theme,
|
||||||
|
runStatus: data.runStatus,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WorkflowRunDiagramBaseEdge
|
<WorkflowRunDiagramBaseEdge
|
||||||
@@ -112,6 +122,11 @@ export const WorkflowDiagramFilterEdgeRun = ({
|
|||||||
>
|
>
|
||||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||||
<StyledConfiguredFilterContainer>
|
<StyledConfiguredFilterContainer>
|
||||||
|
<WorkflowStepFilterCounter
|
||||||
|
backgroundColor={selected.tagBackground}
|
||||||
|
textColor={selected.color}
|
||||||
|
counter={filterCounter}
|
||||||
|
/>
|
||||||
<StyledIconButtonGroup
|
<StyledIconButtonGroup
|
||||||
className="nodrag nopan"
|
className="nodrag nopan"
|
||||||
iconButtons={[
|
iconButtons={[
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledFilterCounter = styled.div<{
|
||||||
|
backgroundColor: string;
|
||||||
|
textColor: string;
|
||||||
|
}>`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ backgroundColor }) => backgroundColor};
|
||||||
|
border-radius: 50%;
|
||||||
|
color: ${({ textColor }) => textColor};
|
||||||
|
display: flex;
|
||||||
|
font-size: ${({ theme }) => theme.font.size.xxs};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
height: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
width: 12px;
|
||||||
|
left: -3px;
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type WorkflowStepFilterCounterProps = {
|
||||||
|
backgroundColor: string;
|
||||||
|
textColor: string;
|
||||||
|
counter: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkflowStepFilterCounter = ({
|
||||||
|
backgroundColor,
|
||||||
|
textColor,
|
||||||
|
counter,
|
||||||
|
}: WorkflowStepFilterCounterProps) => (
|
||||||
|
<StyledFilterCounter backgroundColor={backgroundColor} textColor={textColor}>
|
||||||
|
{counter}
|
||||||
|
</StyledFilterCounter>
|
||||||
|
);
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import { WorkflowStepFilterCounter } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterCounter';
|
||||||
|
import { type Meta, type StoryObj } from '@storybook/react';
|
||||||
|
import { expect, within } from '@storybook/test';
|
||||||
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { THEME_LIGHT } from 'twenty-ui/theme';
|
||||||
|
|
||||||
|
const meta: Meta<typeof WorkflowStepFilterCounter> = {
|
||||||
|
title: 'Modules/Workflow/Actions/Filter/WorkflowStepFilterCounter',
|
||||||
|
component: WorkflowStepFilterCounter,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof WorkflowStepFilterCounter>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
counter: 1,
|
||||||
|
backgroundColor: THEME_LIGHT.border.color.strong,
|
||||||
|
textColor: THEME_LIGHT.font.color.inverted,
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
expect(await canvas.findByText('1')).toBeVisible();
|
||||||
|
},
|
||||||
|
};
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
|
import { createStepSelector } from '@/workflow/states/selectors/stepSelector';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useFilterCounter = ({ stepId }: { stepId: string }) => {
|
||||||
|
const stepSelector = useMemo(() => createStepSelector(stepId), [stepId]);
|
||||||
|
const step = useRecoilComponentValue(stepSelector);
|
||||||
|
|
||||||
|
if (!isDefined(step)) {
|
||||||
|
return {
|
||||||
|
filterCounter: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.type === 'FILTER' && isDefined(step.settings?.input?.stepFilters)) {
|
||||||
|
return {
|
||||||
|
filterCounter: step.settings.input.stepFilters.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filterCounter: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
+8
@@ -42,6 +42,14 @@ export const useSearchVariable = ({
|
|||||||
stepIds: [stepId],
|
stepIds: [stepId],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!isDefined(stepOutputSchema)) {
|
||||||
|
return {
|
||||||
|
variableLabel: undefined,
|
||||||
|
variablePathLabel: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const stepType =
|
const stepType =
|
||||||
stepId === TRIGGER_STEP_ID
|
stepId === TRIGGER_STEP_ID
|
||||||
? flow.trigger?.type
|
? flow.trigger?.type
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"assets",
|
|
||||||
"accessibility",
|
"accessibility",
|
||||||
|
"assets",
|
||||||
"components",
|
"components",
|
||||||
"display",
|
"display",
|
||||||
"feedback",
|
"feedback",
|
||||||
|
|||||||
Reference in New Issue
Block a user