diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
index 88f849fc8c7ed..1d1bfdf5beaf2 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter.tsx
@@ -1,9 +1,9 @@
import { MultipleRecordsActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/multiple-records/components/MultipleRecordsActionMenuEntrySetterEffect';
import { NoSelectionActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/no-selection/components/NoSelectionActionMenuEntrySetterEffect';
-import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
+import { SingleRecordActionMenuEntrySetter } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter';
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
-import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
@@ -32,30 +32,35 @@ const ActionEffects = ({
objectId: objectMetadataItemId,
});
- const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
- contextStoreNumberOfSelectedRecordsComponentState,
+ const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
);
const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
return (
<>
- {contextStoreNumberOfSelectedRecords === 0 && (
-
- )}
- {contextStoreNumberOfSelectedRecords === 1 && (
-
- )}
- {contextStoreNumberOfSelectedRecords === 1 && isWorkflowEnabled && (
-
- )}
- {contextStoreNumberOfSelectedRecords > 1 && (
+ {contextStoreTargetedRecordsRule.mode === 'selection' &&
+ contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 && (
+
+ )}
+ {contextStoreTargetedRecordsRule.mode === 'selection' &&
+ contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && (
+ <>
+
+ {isWorkflowEnabled && (
+
+ )}
+ >
+ )}
+ {(contextStoreTargetedRecordsRule.mode === 'exclusion' ||
+ contextStoreTargetedRecordsRule.selectedRecordIds.length > 1) && (
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx
new file mode 100644
index 0000000000000..447a08447877d
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx
@@ -0,0 +1,132 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { useDeleteMultipleRecordsAction } from '../useDeleteMultipleRecordsAction';
+
+jest.mock('@/object-record/hooks/useDeleteManyRecords', () => ({
+ useDeleteManyRecords: () => ({
+ deleteManyRecords: jest.fn(),
+ }),
+}));
+jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({
+ useDeleteFavorite: () => ({
+ deleteFavorite: jest.fn(),
+ }),
+}));
+jest.mock('@/favorites/hooks/useFavorites', () => ({
+ useFavorites: () => ({
+ sortedFavorites: [],
+ }),
+}));
+jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({
+ useRecordTable: () => ({
+ resetTableRowSelection: jest.fn(),
+ }),
+}));
+
+const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'company',
+)!;
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+ onInitializeRecoilSnapshot: ({ set }) => {
+ set(
+ contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
+ instanceId: '1',
+ }),
+ 3,
+ );
+ },
+});
+
+describe('useDeleteMultipleRecordsAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+ {children}
+
+
+
+ );
+
+ it('should register delete action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('delete-multiple-records'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('delete-multiple-records')?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister delete action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeleteMultipleRecordsAction: useDeleteMultipleRecordsAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeleteMultipleRecordsAction.registerDeleteMultipleRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+
+ act(() => {
+ result.current.useDeleteMultipleRecordsAction.unregisterDeleteMultipleRecordsAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx
new file mode 100644
index 0000000000000..7d9dbff6ef776
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useExportMultipleRecordsAction.test.tsx
@@ -0,0 +1,105 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { useExportMultipleRecordsAction } from '../useExportMultipleRecordsAction';
+
+const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'company',
+)!;
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+describe('useExportMultipleRecordsAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register export multiple records action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useExportMultipleRecordsAction: useExportMultipleRecordsAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('export-multiple-records'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('export-multiple-records')?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister export multiple records action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useExportMultipleRecordsAction: useExportMultipleRecordsAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useExportMultipleRecordsAction.registerExportMultipleRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+
+ act(() => {
+ result.current.useExportMultipleRecordsAction.unregisterExportMultipleRecordsAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
index 29be66860a838..5e517ace13461 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx
@@ -22,10 +22,8 @@ import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui';
export const useDeleteMultipleRecordsAction = ({
- position,
objectMetadataItem,
}: {
- position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
@@ -106,7 +104,11 @@ export const useDeleteMultipleRecordsAction = ({
const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);
- const registerDeleteMultipleRecordsAction = () => {
+ const registerDeleteMultipleRecordsAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
if (canDelete) {
addActionMenuEntry({
type: ActionMenuEntryType.Standard,
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx
index eacdf1e5e643e..b8cebf5fc2471 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction.tsx
@@ -12,10 +12,8 @@ import {
} from '@/object-record/record-index/export/hooks/useExportRecords';
export const useExportMultipleRecordsAction = ({
- position,
objectMetadataItem,
}: {
- position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
@@ -27,7 +25,11 @@ export const useExportMultipleRecordsAction = ({
filename: `${objectMetadataItem.nameSingular}.csv`,
});
- const registerExportMultipleRecordsAction = () => {
+ const registerExportMultipleRecordsAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
addActionMenuEntry({
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx
index 78e459e23d388..2b135aaa0e332 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useMultipleRecordsActions.tsx
@@ -1,5 +1,5 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
-import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction';
+import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useMultipleRecordsActions = ({
@@ -11,26 +11,24 @@ export const useMultipleRecordsActions = ({
registerDeleteMultipleRecordsAction,
unregisterDeleteMultipleRecordsAction,
} = useDeleteMultipleRecordsAction({
- position: 0,
objectMetadataItem,
});
const {
- registerExportViewNoSelectionRecordsAction,
- unregisterExportViewNoSelectionRecordsAction,
- } = useExportViewNoSelectionRecordAction({
- position: 1,
+ registerExportMultipleRecordsAction,
+ unregisterExportMultipleRecordsAction,
+ } = useExportMultipleRecordsAction({
objectMetadataItem,
});
const registerMultipleRecordsActions = () => {
- registerDeleteMultipleRecordsAction();
- registerExportViewNoSelectionRecordsAction();
+ registerDeleteMultipleRecordsAction({ position: 1 });
+ registerExportMultipleRecordsAction({ position: 2 });
};
const unregisterMultipleRecordsActions = () => {
unregisterDeleteMultipleRecordsAction();
- unregisterExportViewNoSelectionRecordsAction();
+ unregisterExportMultipleRecordsAction();
};
return {
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx
new file mode 100644
index 0000000000000..7b00c186536a4
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/__tests__/useExportViewNoSelectionRecordAction.test.tsx
@@ -0,0 +1,108 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+
+import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction';
+const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'company',
+)!;
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+describe('useExportViewNoSelectionRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register export view action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useExportViewNoSelectionRecordAction:
+ useExportViewNoSelectionRecordAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('export-view-no-selection'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('export-view-no-selection')
+ ?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister export view action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useExportViewNoSelectionRecordAction:
+ useExportViewNoSelectionRecordAction({
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useExportViewNoSelectionRecordAction.registerExportViewNoSelectionRecordsAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+
+ act(() => {
+ result.current.useExportViewNoSelectionRecordAction.unregisterExportViewNoSelectionRecordsAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx
similarity index 92%
rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction.tsx
rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx
index d3a257144430d..3bb7cebbbe7f6 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction.tsx
@@ -12,10 +12,8 @@ import {
} from '@/object-record/record-index/export/hooks/useExportRecords';
export const useExportViewNoSelectionRecordAction = ({
- position,
objectMetadataItem,
}: {
- position: number;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
@@ -27,7 +25,11 @@ export const useExportViewNoSelectionRecordAction = ({
filename: `${objectMetadataItem.nameSingular}.csv`,
});
- const registerExportViewNoSelectionRecordsAction = () => {
+ const registerExportViewNoSelectionRecordsAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
addActionMenuEntry({
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.Global,
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx
index a647e449b7401..af08e721b4046 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useNoSelectionRecordActions.tsx
@@ -1,4 +1,4 @@
-import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportMultipleRecordsAction';
+import { useExportViewNoSelectionRecordAction } from '@/action-menu/actions/record-actions/no-selection/hooks/useExportViewNoSelectionRecordAction';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useNoSelectionRecordActions = ({
@@ -10,12 +10,11 @@ export const useNoSelectionRecordActions = ({
registerExportViewNoSelectionRecordsAction,
unregisterExportViewNoSelectionRecordsAction,
} = useExportViewNoSelectionRecordAction({
- position: 0,
objectMetadataItem,
});
const registerNoSelectionRecordActions = () => {
- registerExportViewNoSelectionRecordsAction();
+ registerExportViewNoSelectionRecordsAction({ position: 1 });
};
const unregisterNoSelectionRecordActions = () => {
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx
new file mode 100644
index 0000000000000..acd2c99a80100
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetter.tsx
@@ -0,0 +1,26 @@
+import { SingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/components/SingleRecordActionMenuEntrySetterEffect';
+import { WorkflowSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect';
+import { WorkflowVersionsSingleRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+
+export const SingleRecordActionMenuEntrySetter = ({
+ objectMetadataItem,
+}: {
+ objectMetadataItem: ObjectMetadataItem;
+}) => {
+ return (
+ <>
+
+ {objectMetadataItem.nameSingular === CoreObjectNameSingular.Workflow && (
+
+ )}
+ {objectMetadataItem.nameSingular ===
+ CoreObjectNameSingular.WorkflowVersion && (
+
+ )}
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts
new file mode 100644
index 0000000000000..9bc77b975169f
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects.ts
@@ -0,0 +1 @@
+export const NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS = 2;
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..b05aa3900e0b6
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useDeleteSingleRecordAction.test.tsx
@@ -0,0 +1,121 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { RecoilRoot } from 'recoil';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { useDeleteSingleRecordAction } from '../useDeleteSingleRecordAction';
+
+jest.mock('@/object-record/hooks/useDeleteOneRecord', () => ({
+ useDeleteOneRecord: () => ({
+ deleteOneRecord: jest.fn(),
+ }),
+}));
+jest.mock('@/favorites/hooks/useDeleteFavorite', () => ({
+ useDeleteFavorite: () => ({
+ deleteFavorite: jest.fn(),
+ }),
+}));
+jest.mock('@/favorites/hooks/useFavorites', () => ({
+ useFavorites: () => ({
+ sortedFavorites: [],
+ }),
+}));
+jest.mock('@/object-record/record-table/hooks/useRecordTable', () => ({
+ useRecordTable: () => ({
+ resetTableRowSelection: jest.fn(),
+ }),
+}));
+
+const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'company',
+)!;
+
+describe('useDeleteSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+ {children}
+
+
+
+ );
+
+ it('should register delete action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeleteSingleRecordAction: useDeleteSingleRecordAction({
+ recordId: 'record1',
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('delete-single-record'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('delete-single-record')?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister delete action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeleteSingleRecordAction: useDeleteSingleRecordAction({
+ recordId: 'record1',
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeleteSingleRecordAction.registerDeleteSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+
+ act(() => {
+ result.current.useDeleteSingleRecordAction.unregisterDeleteSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..86e279ecf49d4
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/__tests__/useManageFavoritesSingleRecordAction.test.tsx
@@ -0,0 +1,108 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
+import { useManageFavoritesSingleRecordAction } from '../useManageFavoritesSingleRecordAction';
+
+const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
+ (item) => item.nameSingular === 'company',
+)!;
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+describe('useManageFavoritesSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register manage favorites action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useManageFavoritesSingleRecordAction:
+ useManageFavoritesSingleRecordAction({
+ recordId: 'record1',
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('manage-favorites-single-record'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('manage-favorites-single-record')
+ ?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister manage favorites action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useManageFavoritesSingleRecordAction:
+ useManageFavoritesSingleRecordAction({
+ recordId: 'record1',
+ objectMetadataItem: companyMockObjectMetadataItem,
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useManageFavoritesSingleRecordAction.registerManageFavoritesSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ act(() => {
+ result.current.useManageFavoritesSingleRecordAction.unregisterManageFavoritesSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
index 0148468eb10fe..90888ccc4a5a8 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx
@@ -4,7 +4,6 @@ import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -12,15 +11,14 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useState } from 'react';
import { IconTrash, isDefined } from 'twenty-ui';
export const useDeleteSingleRecordAction = ({
- position,
+ recordId,
objectMetadataItem,
}: {
- position: number;
+ recordId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
@@ -39,39 +37,26 @@ export const useDeleteSingleRecordAction = ({
const { sortedFavorites: favorites } = useFavorites();
const { deleteFavorite } = useDeleteFavorite();
- const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
const { closeRightDrawer } = useRightDrawer();
- const recordIdToDelete =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
- : undefined;
-
const handleDeleteClick = useCallback(async () => {
- if (!isDefined(recordIdToDelete)) {
- return;
- }
-
resetTableRowSelection();
const foundFavorite = favorites?.find(
- (favorite) => favorite.recordId === recordIdToDelete,
+ (favorite) => favorite.recordId === recordId,
);
if (isDefined(foundFavorite)) {
deleteFavorite(foundFavorite.id);
}
- await deleteOneRecord(recordIdToDelete);
+ await deleteOneRecord(recordId);
}, [
deleteFavorite,
deleteOneRecord,
favorites,
- recordIdToDelete,
resetTableRowSelection,
+ recordId,
]);
const isRemoteObject = objectMetadataItem.isRemote;
@@ -79,8 +64,12 @@ export const useDeleteSingleRecordAction = ({
const { isInRightDrawer, onActionExecutedCallback } =
useContext(ActionMenuContext);
- const registerDeleteSingleRecordAction = () => {
- if (isRemoteObject || !isDefined(recordIdToDelete)) {
+ const registerDeleteSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (isRemoteObject) {
return;
}
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts
similarity index 70%
rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.tsx
rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts
index 3687318ed74a5..25a56d6fecef6 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction.ts
@@ -3,51 +3,42 @@ import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
-import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useCreateFavorite } from '@/favorites/hooks/useCreateFavorite';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilValue } from 'recoil';
import { IconHeart, IconHeartOff, isDefined } from 'twenty-ui';
export const useManageFavoritesSingleRecordAction = ({
- position,
+ recordId,
objectMetadataItem,
}: {
- position: number;
+ recordId: string;
objectMetadataItem: ObjectMetadataItem;
}) => {
const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
- const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
- contextStoreTargetedRecordsRuleComponentState,
- );
-
const { sortedFavorites: favorites } = useFavorites();
const { createFavorite } = useCreateFavorite();
const { deleteFavorite } = useDeleteFavorite();
- const selectedRecordId =
- contextStoreTargetedRecordsRule.mode === 'selection'
- ? contextStoreTargetedRecordsRule.selectedRecordIds[0]
- : undefined;
-
- const selectedRecord = useRecoilValue(
- recordStoreFamilyState(selectedRecordId ?? ''),
- );
+ const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
const foundFavorite = favorites?.find(
- (favorite) => favorite.recordId === selectedRecordId,
+ (favorite) => favorite.recordId === recordId,
);
- const isFavorite = !!selectedRecordId && !!foundFavorite;
+ const isFavorite = !!foundFavorite;
- const registerManageFavoritesSingleRecordAction = () => {
+ const registerManageFavoritesSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) {
return;
}
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts
similarity index 56%
rename from packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.tsx
rename to packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts
index 4b6a3763c67fc..fb723c46d349d 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useSingleRecordActions.ts
@@ -1,17 +1,33 @@
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useManageFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useManageFavoritesSingleRecordAction';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { isDefined } from 'twenty-ui';
export const useSingleRecordActions = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
+ const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ );
+
+ const selectedRecordId =
+ contextStoreTargetedRecordsRule.mode === 'selection'
+ ? contextStoreTargetedRecordsRule.selectedRecordIds[0]
+ : undefined;
+
+ if (!isDefined(selectedRecordId)) {
+ throw new Error('Selected record ID is required');
+ }
+
const {
registerManageFavoritesSingleRecordAction,
unregisterManageFavoritesSingleRecordAction,
} = useManageFavoritesSingleRecordAction({
- position: 0,
+ recordId: selectedRecordId,
objectMetadataItem,
});
@@ -19,13 +35,13 @@ export const useSingleRecordActions = ({
registerDeleteSingleRecordAction,
unregisterDeleteSingleRecordAction,
} = useDeleteSingleRecordAction({
- position: 1,
+ recordId: selectedRecordId,
objectMetadataItem,
});
const registerSingleRecordActions = () => {
- registerManageFavoritesSingleRecordAction();
- registerDeleteSingleRecordAction();
+ registerManageFavoritesSingleRecordAction({ position: 1 });
+ registerDeleteSingleRecordAction({ position: 2 });
};
const unregisterSingleRecordActions = () => {
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx
new file mode 100644
index 0000000000000..4725f00574d31
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/WorkflowSingleRecordActionMenuEntrySetterEffect.tsx
@@ -0,0 +1,21 @@
+import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects';
+import { useEffect } from 'react';
+import { useWorkflowSingleRecordActions } from '../hooks/useWorkflowSingleRecordActions';
+
+export const WorkflowSingleRecordActionMenuEntrySetterEffect = () => {
+ const { registerSingleRecordActions, unregisterSingleRecordActions } =
+ useWorkflowSingleRecordActions();
+
+ useEffect(() => {
+ registerSingleRecordActions({
+ startPosition:
+ NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1,
+ });
+
+ return () => {
+ unregisterSingleRecordActions();
+ };
+ }, [registerSingleRecordActions, unregisterSingleRecordActions]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..a468db65a544a
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowDraftWorkflowSingleRecordAction.test.tsx
@@ -0,0 +1,123 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { useActivateWorkflowDraftWorkflowSingleRecordAction } from '../useActivateWorkflowDraftWorkflowSingleRecordAction';
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
+ useWorkflowWithCurrentVersion: () => ({
+ id: 'workflowId',
+ currentVersion: {
+ id: 'currentVersionId',
+ trigger: 'trigger',
+ status: 'DRAFT',
+ steps: [
+ {
+ id: 'stepId1',
+ },
+ {
+ id: 'stepId2',
+ },
+ ],
+ },
+ }),
+}));
+
+describe('useActivateWorkflowDraftWorkflowSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register activate workflow draft workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useActivateWorkflowDraftWorkflowSingleRecordAction:
+ useActivateWorkflowDraftWorkflowSingleRecordAction({
+ workflowId: 'workflowId',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get(
+ 'activate-workflow-draft-single-record',
+ ),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get(
+ 'activate-workflow-draft-single-record',
+ )?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister activate workflow draft workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useActivateWorkflowDraftWorkflowSingleRecordAction:
+ useActivateWorkflowDraftWorkflowSingleRecordAction({
+ workflowId: 'workflow1',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.registerActivateWorkflowDraftWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ act(() => {
+ result.current.useActivateWorkflowDraftWorkflowSingleRecordAction.unregisterActivateWorkflowDraftWorkflowSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..e81a4e419dc08
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.test.tsx
@@ -0,0 +1,124 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '../useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction';
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
+ useWorkflowWithCurrentVersion: () => ({
+ id: 'workflowId',
+ currentVersion: {
+ id: 'currentVersionId',
+ trigger: 'trigger',
+ status: 'DEACTIVATED',
+ steps: [
+ {
+ id: 'stepId1',
+ },
+ {
+ id: 'stepId2',
+ },
+ ],
+ },
+ lastPublishedVersionId: 'lastPublishedVersionId',
+ }),
+}));
+
+describe('useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register activate workflow last published version workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction:
+ useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
+ workflowId: 'workflowId',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get(
+ 'activate-workflow-last-published-version-single-record',
+ ),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get(
+ 'activate-workflow-last-published-version-single-record',
+ )?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister activate workflow last published version workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction:
+ useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
+ workflowId: 'workflow1',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ act(() => {
+ result.current.useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..3b56488b46617
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDeactivateWorkflowWorkflowSingleRecordAction.test.tsx
@@ -0,0 +1,113 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { useDeactivateWorkflowWorkflowSingleRecordAction } from '../useDeactivateWorkflowWorkflowSingleRecordAction';
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
+ useWorkflowWithCurrentVersion: () => ({
+ id: 'workflowId',
+ currentVersion: {
+ id: 'currentVersionId',
+ trigger: 'trigger',
+ status: 'ACTIVE',
+ },
+ lastPublishedVersionId: 'lastPublishedVersionId',
+ }),
+}));
+
+describe('useDeactivateWorkflowWorkflowSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register activate workflow last published version workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeactivateWorkflowWorkflowSingleRecordAction:
+ useDeactivateWorkflowWorkflowSingleRecordAction({
+ workflowId: 'workflowId',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get('deactivate-workflow-single-record'),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get('deactivate-workflow-single-record')
+ ?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister deactivate workflow workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDeactivateWorkflowWorkflowSingleRecordAction:
+ useDeactivateWorkflowWorkflowSingleRecordAction({
+ workflowId: 'workflow1',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDeactivateWorkflowWorkflowSingleRecordAction.registerDeactivateWorkflowWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ act(() => {
+ result.current.useDeactivateWorkflowWorkflowSingleRecordAction.unregisterDeactivateWorkflowWorkflowSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx
new file mode 100644
index 0000000000000..428d21704f651
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/__tests__/useDiscardDraftWorkflowSingleRecordAction.test.tsx
@@ -0,0 +1,128 @@
+import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
+import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
+import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { expect } from '@storybook/test';
+import { renderHook } from '@testing-library/react';
+import { act } from 'react';
+import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
+import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
+import { useDiscardDraftWorkflowSingleRecordAction } from '../useDiscardDraftWorkflowSingleRecordAction';
+
+const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({
+ apolloMocks: [],
+});
+
+jest.mock('@/workflow/hooks/useWorkflowWithCurrentVersion', () => ({
+ useWorkflowWithCurrentVersion: () => ({
+ id: 'workflowId',
+ currentVersion: {
+ id: 'currentVersionId',
+ trigger: 'trigger',
+ status: 'DRAFT',
+ },
+ lastPublishedVersionId: 'lastPublishedVersionId',
+ versions: [
+ {
+ id: 'currentVersionId',
+ trigger: 'trigger',
+ status: 'DRAFT',
+ },
+ {
+ id: 'lastPublishedVersionId',
+ trigger: 'trigger',
+ status: 'ACTIVE',
+ },
+ ],
+ }),
+}));
+
+describe('useDiscardDraftWorkflowSingleRecordAction', () => {
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
+
+
+
+
+ {children}
+
+
+
+
+ );
+
+ it('should register discard workflow draft workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDiscardDraftWorkflowSingleRecordAction:
+ useDiscardDraftWorkflowSingleRecordAction({
+ workflowId: 'workflowId',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(1);
+ expect(
+ result.current.actionMenuEntries.get(
+ 'discard-workflow-draft-single-record',
+ ),
+ ).toBeDefined();
+ expect(
+ result.current.actionMenuEntries.get(
+ 'discard-workflow-draft-single-record',
+ )?.position,
+ ).toBe(1);
+ });
+
+ it('should unregister deactivate workflow workflow action', () => {
+ const { result } = renderHook(
+ () => {
+ const actionMenuEntries = useRecoilComponentValueV2(
+ actionMenuEntriesComponentState,
+ );
+
+ return {
+ actionMenuEntries,
+ useDiscardDraftWorkflowSingleRecordAction:
+ useDiscardDraftWorkflowSingleRecordAction({
+ workflowId: 'workflow1',
+ }),
+ };
+ },
+ { wrapper },
+ );
+
+ act(() => {
+ result.current.useDiscardDraftWorkflowSingleRecordAction.registerDiscardDraftWorkflowSingleRecordAction(
+ { position: 1 },
+ );
+ });
+
+ act(() => {
+ result.current.useDiscardDraftWorkflowSingleRecordAction.unregisterDiscardDraftWorkflowSingleRecordAction();
+ });
+
+ expect(result.current.actionMenuEntries.size).toBe(0);
+ });
+});
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..abaf0ba08ff93
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction.ts
@@ -0,0 +1,64 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { IconPower, isDefined } from 'twenty-ui';
+
+export const useActivateWorkflowDraftWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const { activateWorkflowVersion } = useActivateWorkflowVersion();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const registerActivateWorkflowDraftWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (
+ !isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) ||
+ !isDefined(workflowWithCurrentVersion.currentVersion?.steps)
+ ) {
+ return;
+ }
+
+ const isDraft =
+ workflowWithCurrentVersion.currentVersion.status === 'DRAFT';
+
+ if (!isDraft) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'activate-workflow-draft-single-record',
+ label: 'Activate Draft',
+ position,
+ Icon: IconPower,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ onClick: () => {
+ activateWorkflowVersion({
+ workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
+ workflowId: workflowWithCurrentVersion.id,
+ });
+ },
+ });
+ };
+
+ const unregisterActivateWorkflowDraftWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('activate-workflow-draft-single-record');
+ };
+
+ return {
+ registerActivateWorkflowDraftWorkflowSingleRecordAction,
+ unregisterActivateWorkflowDraftWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..ee6d6463cd7e9
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction.ts
@@ -0,0 +1,61 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useActivateWorkflowVersion } from '@/workflow/hooks/useActivateWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { IconPower, isDefined } from 'twenty-ui';
+
+export const useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
+ ({ workflowId }: { workflowId: string }) => {
+ const { addActionMenuEntry, removeActionMenuEntry } =
+ useActionMenuEntries();
+
+ const { activateWorkflowVersion } = useActivateWorkflowVersion();
+
+ const workflowWithCurrentVersion =
+ useWorkflowWithCurrentVersion(workflowId);
+
+ const registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
+ ({ position }: { position: number }) => {
+ if (
+ !isDefined(workflowWithCurrentVersion) ||
+ !isDefined(workflowWithCurrentVersion.currentVersion.trigger) ||
+ !isDefined(workflowWithCurrentVersion.lastPublishedVersionId) ||
+ workflowWithCurrentVersion.currentVersion.status === 'ACTIVE' ||
+ !isDefined(workflowWithCurrentVersion.currentVersion?.steps) ||
+ workflowWithCurrentVersion.currentVersion?.steps.length === 0
+ ) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'activate-workflow-last-published-version-single-record',
+ label: 'Activate last published version',
+ position,
+ Icon: IconPower,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ onClick: () => {
+ activateWorkflowVersion({
+ workflowVersionId:
+ workflowWithCurrentVersion.lastPublishedVersionId,
+ workflowId: workflowWithCurrentVersion.id,
+ });
+ },
+ });
+ };
+
+ const unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction =
+ () => {
+ removeActionMenuEntry(
+ 'activate-workflow-last-published-version-single-record',
+ );
+ };
+
+ return {
+ registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
+ unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
+ };
+ };
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..ab132227f3fa4
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction.ts
@@ -0,0 +1,55 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useDeactivateWorkflowVersion } from '@/workflow/hooks/useDeactivateWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { IconPlayerPause, isDefined } from 'twenty-ui';
+
+export const useDeactivateWorkflowWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const { deactivateWorkflowVersion } = useDeactivateWorkflowVersion();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const isWorkflowActive =
+ isDefined(workflowWithCurrentVersion) &&
+ workflowWithCurrentVersion.currentVersion.status === 'ACTIVE';
+
+ const registerDeactivateWorkflowWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (!isDefined(workflowWithCurrentVersion) || !isWorkflowActive) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'deactivate-workflow-single-record',
+ label: 'Deactivate Workflow',
+ position,
+ Icon: IconPlayerPause,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ onClick: () => {
+ deactivateWorkflowVersion(workflowWithCurrentVersion.currentVersion.id);
+ },
+ });
+ };
+
+ const unregisterDeactivateWorkflowWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('deactivate-workflow-single-record');
+ };
+
+ return {
+ registerDeactivateWorkflowWorkflowSingleRecordAction,
+ unregisterDeactivateWorkflowWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..22a7cb519fb52
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction.ts
@@ -0,0 +1,63 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useDeleteOneWorkflowVersion } from '@/workflow/hooks/useDeleteOneWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { IconTrash, isDefined } from 'twenty-ui';
+
+export const useDiscardDraftWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const { deleteOneWorkflowVersion } = useDeleteOneWorkflowVersion();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const registerDiscardDraftWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (
+ !isDefined(workflowWithCurrentVersion) ||
+ workflowWithCurrentVersion.versions.length < 2
+ ) {
+ return;
+ }
+
+ const isDraft =
+ workflowWithCurrentVersion.currentVersion.status === 'DRAFT';
+
+ if (!isDraft) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'discard-workflow-draft-single-record',
+ label: 'Discard Draft',
+ position,
+ Icon: IconTrash,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ onClick: () => {
+ deleteOneWorkflowVersion({
+ workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
+ });
+ },
+ });
+ };
+
+ const unregisterDiscardDraftWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('discard-workflow-draft-single-record');
+ };
+
+ return {
+ registerDiscardDraftWorkflowSingleRecordAction,
+ unregisterDiscardDraftWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..e40794f4c5af3
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction.ts
@@ -0,0 +1,60 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion';
+import { useNavigate } from 'react-router-dom';
+import { useRecoilValue } from 'recoil';
+import { IconHistory, isDefined } from 'twenty-ui';
+
+export const useSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflow = useRecoilValue(recordStoreFamilyState(workflowId));
+
+ const isDraft = workflow?.statuses?.includes('DRAFT');
+
+ const workflowActiveVersion = useActiveWorkflowVersion(workflowId);
+
+ const navigate = useNavigate();
+
+ const registerSeeWorkflowActiveVersionWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (!isDefined(workflowActiveVersion) || !isDraft) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'see-workflow-active-version-single-record',
+ label: 'See active version',
+ position,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ Icon: IconHistory,
+ onClick: () => {
+ navigate(
+ `/object/${CoreObjectNameSingular.WorkflowVersion}/${workflowActiveVersion.id}`,
+ );
+ },
+ });
+ };
+
+ const unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('see-workflow-active-version-single-record');
+ };
+
+ return {
+ registerSeeWorkflowActiveVersionWorkflowSingleRecordAction,
+ unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..2a4cd8de1dfda
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction.ts
@@ -0,0 +1,66 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
+import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import qs from 'qs';
+import { useNavigate } from 'react-router-dom';
+import { IconHistoryToggle, isDefined } from 'twenty-ui';
+
+export const useSeeWorkflowRunsWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const navigate = useNavigate();
+
+ const registerSeeWorkflowRunsWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (!isDefined(workflowWithCurrentVersion)) {
+ return;
+ }
+
+ const filterQueryParams: FilterQueryParams = {
+ filter: {
+ workflow: {
+ [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
+ },
+ },
+ };
+ const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
+ filterQueryParams,
+ )}`;
+
+ addActionMenuEntry({
+ key: 'see-workflow-runs-single-record',
+ label: 'See runs',
+ position,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ Icon: IconHistoryToggle,
+ onClick: () => {
+ navigate(filterLinkHref);
+ },
+ });
+ };
+
+ const unregisterSeeWorkflowRunsWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('see-workflow-runs-single-record');
+ };
+
+ return {
+ registerSeeWorkflowRunsWorkflowSingleRecordAction,
+ unregisterSeeWorkflowRunsWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..2204c1af1eb33
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction.ts
@@ -0,0 +1,66 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
+import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import qs from 'qs';
+import { useNavigate } from 'react-router-dom';
+import { IconHistory, isDefined } from 'twenty-ui';
+
+export const useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const navigate = useNavigate();
+
+ const registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (!isDefined(workflowWithCurrentVersion)) {
+ return;
+ }
+
+ const filterQueryParams: FilterQueryParams = {
+ filter: {
+ workflow: {
+ [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
+ },
+ },
+ };
+ const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowVersion}?${qs.stringify(
+ filterQueryParams,
+ )}`;
+
+ addActionMenuEntry({
+ key: 'see-workflow-versions-history-single-record',
+ label: 'See versions history',
+ position,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ Icon: IconHistory,
+ onClick: () => {
+ navigate(filterLinkHref);
+ },
+ });
+ };
+
+ const unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('see-workflow-versions-history-single-record');
+ };
+
+ return {
+ registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
+ unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts
new file mode 100644
index 0000000000000..d99f35784c352
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction.ts
@@ -0,0 +1,60 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { IconPlayerPlay, isDefined } from 'twenty-ui';
+
+export const useTestWorkflowWorkflowSingleRecordAction = ({
+ workflowId,
+}: {
+ workflowId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
+
+ const { runWorkflowVersion } = useRunWorkflowVersion();
+
+ const registerTestWorkflowWorkflowSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (
+ !isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) ||
+ workflowWithCurrentVersion.currentVersion.trigger.type !== 'MANUAL' ||
+ isDefined(
+ workflowWithCurrentVersion.currentVersion.trigger.settings.objectType,
+ )
+ ) {
+ return;
+ }
+
+ addActionMenuEntry({
+ key: 'test-workflow-single-record',
+ label: 'Test workflow',
+ position,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ Icon: IconPlayerPlay,
+ onClick: () => {
+ runWorkflowVersion({
+ workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
+ workflowName: workflowWithCurrentVersion.name,
+ });
+ },
+ });
+ };
+
+ const unregisterTestWorkflowWorkflowSingleRecordAction = () => {
+ removeActionMenuEntry('test-workflow-single-record');
+ };
+
+ return {
+ registerTestWorkflowWorkflowSingleRecordAction,
+ unregisterTestWorkflowWorkflowSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts
new file mode 100644
index 0000000000000..a35ccc486881e
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useWorkflowSingleRecordActions.ts
@@ -0,0 +1,127 @@
+import { useActivateWorkflowDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowDraftWorkflowSingleRecordAction';
+import { useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction';
+import { useDeactivateWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDeactivateWorkflowWorkflowSingleRecordAction';
+import { useDiscardDraftWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useDiscardDraftWorkflowSingleRecordAction';
+import { useSeeWorkflowActiveVersionWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowActiveVersionWorkflowSingleRecordAction';
+import { useSeeWorkflowRunsWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowRunsWorkflowSingleRecordAction';
+import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction';
+import { useTestWorkflowWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useTestWorkflowWorkflowSingleRecordAction';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { isDefined } from 'twenty-ui';
+
+export const useWorkflowSingleRecordActions = () => {
+ const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ );
+
+ const selectedRecordId =
+ contextStoreTargetedRecordsRule.mode === 'selection'
+ ? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
+ : undefined;
+
+ if (!isDefined(selectedRecordId)) {
+ throw new Error('Selected record ID is required');
+ }
+
+ const {
+ registerTestWorkflowWorkflowSingleRecordAction,
+ unregisterTestWorkflowWorkflowSingleRecordAction,
+ } = useTestWorkflowWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
+ unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction,
+ } = useActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerDeactivateWorkflowWorkflowSingleRecordAction,
+ unregisterDeactivateWorkflowWorkflowSingleRecordAction,
+ } = useDeactivateWorkflowWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerSeeWorkflowRunsWorkflowSingleRecordAction,
+ unregisterSeeWorkflowRunsWorkflowSingleRecordAction,
+ } = useSeeWorkflowRunsWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
+ unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction,
+ } = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerSeeWorkflowActiveVersionWorkflowSingleRecordAction,
+ unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction,
+ } = useSeeWorkflowActiveVersionWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerActivateWorkflowDraftWorkflowSingleRecordAction,
+ unregisterActivateWorkflowDraftWorkflowSingleRecordAction,
+ } = useActivateWorkflowDraftWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const {
+ registerDiscardDraftWorkflowSingleRecordAction,
+ unregisterDiscardDraftWorkflowSingleRecordAction,
+ } = useDiscardDraftWorkflowSingleRecordAction({
+ workflowId: selectedRecordId,
+ });
+
+ const registerSingleRecordActions = ({
+ startPosition,
+ }: {
+ startPosition: number;
+ }) => {
+ registerTestWorkflowWorkflowSingleRecordAction({ position: startPosition });
+ registerDiscardDraftWorkflowSingleRecordAction({
+ position: startPosition + 1,
+ });
+ registerActivateWorkflowDraftWorkflowSingleRecordAction({
+ position: startPosition + 2,
+ });
+ registerActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction({
+ position: startPosition + 3,
+ });
+ registerDeactivateWorkflowWorkflowSingleRecordAction({
+ position: startPosition + 4,
+ });
+ registerSeeWorkflowRunsWorkflowSingleRecordAction({
+ position: startPosition + 5,
+ });
+ registerSeeWorkflowActiveVersionWorkflowSingleRecordAction({
+ position: startPosition + 6,
+ });
+ registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
+ position: startPosition + 7,
+ });
+ };
+
+ const unregisterSingleRecordActions = () => {
+ unregisterTestWorkflowWorkflowSingleRecordAction();
+ unregisterActivateWorkflowLastPublishedVersionWorkflowSingleRecordAction();
+ unregisterDiscardDraftWorkflowSingleRecordAction();
+ unregisterActivateWorkflowDraftWorkflowSingleRecordAction();
+ unregisterDeactivateWorkflowWorkflowSingleRecordAction();
+ unregisterSeeWorkflowRunsWorkflowSingleRecordAction();
+ unregisterSeeWorkflowActiveVersionWorkflowSingleRecordAction();
+ unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction();
+ };
+
+ return {
+ registerSingleRecordActions,
+ unregisterSingleRecordActions,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx
new file mode 100644
index 0000000000000..edc710b83ff15
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/WorkflowVersionsSingleRecordActionMenuEntrySetterEffect.tsx
@@ -0,0 +1,21 @@
+import { NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS } from '@/action-menu/actions/record-actions/single-record/constants/NumberOfStandardSingleRecordActionsOnAllObjects';
+import { useWorkflowVersionsSingleRecordActions } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions';
+import { useEffect } from 'react';
+
+export const WorkflowVersionsSingleRecordActionMenuEntrySetterEffect = () => {
+ const { registerSingleRecordActions, unregisterSingleRecordActions } =
+ useWorkflowVersionsSingleRecordActions();
+
+ useEffect(() => {
+ registerSingleRecordActions({
+ startPosition:
+ NUMBER_OF_STANDARD_SINGLE_RECORD_ACTIONS_ON_ALL_OBJECTS + 1,
+ });
+
+ return () => {
+ unregisterSingleRecordActions();
+ };
+ }, [registerSingleRecordActions, unregisterSingleRecordActions]);
+
+ return null;
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts
new file mode 100644
index 0000000000000..f0452d4f3282e
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction.ts
@@ -0,0 +1,78 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { FilterQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import qs from 'qs';
+import { useNavigate } from 'react-router-dom';
+import { useRecoilValue } from 'recoil';
+import { IconHistoryToggle, isDefined } from 'twenty-ui';
+
+export const useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({
+ workflowVersionId,
+}: {
+ workflowVersionId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflowVersion = useRecoilValue(
+ recordStoreFamilyState(workflowVersionId),
+ );
+
+ const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
+ workflowVersion?.workflow.id,
+ );
+
+ const navigate = useNavigate();
+
+ const registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (!isDefined(workflowWithCurrentVersion)) {
+ return;
+ }
+
+ const filterQueryParams: FilterQueryParams = {
+ filter: {
+ workflow: {
+ [ViewFilterOperand.Is]: [workflowWithCurrentVersion.id],
+ },
+ workflowVersion: {
+ [ViewFilterOperand.Is]: [workflowVersionId],
+ },
+ },
+ };
+ const filterLinkHref = `/objects/${CoreObjectNamePlural.WorkflowRun}?${qs.stringify(
+ filterQueryParams,
+ )}`;
+
+ addActionMenuEntry({
+ key: 'see-workflow-executions-single-record',
+ label: 'See executions',
+ position,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ Icon: IconHistoryToggle,
+ onClick: () => {
+ navigate(filterLinkHref);
+ },
+ });
+ };
+
+ const unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction =
+ () => {
+ removeActionMenuEntry('see-workflow-executions-single-record');
+ };
+
+ return {
+ registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
+ unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts
new file mode 100644
index 0000000000000..ea607ff96a32a
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction.ts
@@ -0,0 +1,27 @@
+import { useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { useRecoilValue } from 'recoil';
+
+export const useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction = ({
+ workflowVersionId,
+}: {
+ workflowVersionId: string;
+}) => {
+ const workflowVersion = useRecoilValue(
+ recordStoreFamilyState(workflowVersionId),
+ );
+
+ const {
+ registerSeeWorkflowVersionsHistoryWorkflowSingleRecordAction:
+ registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ unregisterSeeWorkflowVersionsHistoryWorkflowSingleRecordAction:
+ unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ } = useSeeWorkflowVersionsHistoryWorkflowSingleRecordAction({
+ workflowId: workflowVersion?.workflow.id,
+ });
+
+ return {
+ registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx
new file mode 100644
index 0000000000000..3fc9906da2e0e
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction.tsx
@@ -0,0 +1,92 @@
+import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
+import {
+ ActionMenuEntryScope,
+ ActionMenuEntryType,
+} from '@/action-menu/types/ActionMenuEntry';
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal';
+import { useCreateNewWorkflowVersion } from '@/workflow/hooks/useCreateNewWorkflowVersion';
+import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
+import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
+import { useRecoilValue, useSetRecoilState } from 'recoil';
+import { IconPencil, isDefined } from 'twenty-ui';
+
+export const useUseAsDraftWorkflowVersionSingleRecordAction = ({
+ workflowVersionId,
+}: {
+ workflowVersionId: string;
+}) => {
+ const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();
+
+ const workflowVersion = useRecoilValue(
+ recordStoreFamilyState(workflowVersionId),
+ );
+
+ const workflow = useWorkflowWithCurrentVersion(
+ workflowVersion?.workflow?.id ?? '',
+ );
+
+ const { createNewWorkflowVersion } = useCreateNewWorkflowVersion();
+
+ const setOpenOverrideWorkflowDraftConfirmationModal = useSetRecoilState(
+ openOverrideWorkflowDraftConfirmationModalState,
+ );
+
+ const registerUseAsDraftWorkflowVersionSingleRecordAction = ({
+ position,
+ }: {
+ position: number;
+ }) => {
+ if (
+ !isDefined(workflowVersion) ||
+ !isDefined(workflow) ||
+ !isDefined(workflow.statuses) ||
+ workflowVersion.status === 'DRAFT'
+ ) {
+ return;
+ }
+
+ const hasAlreadyDraftVersion = workflow.statuses.includes('DRAFT');
+
+ addActionMenuEntry({
+ key: 'use-workflow-version-as-draft-single-record',
+ label: 'Use as draft',
+ position,
+ Icon: IconPencil,
+ type: ActionMenuEntryType.Standard,
+ scope: ActionMenuEntryScope.RecordSelection,
+ onClick: async () => {
+ if (hasAlreadyDraftVersion) {
+ setOpenOverrideWorkflowDraftConfirmationModal(true);
+ } else {
+ await createNewWorkflowVersion({
+ workflowId: workflowVersion.workflow.id,
+ name: `v${workflow.versions.length + 1}`,
+ status: 'DRAFT',
+ trigger: workflowVersion.trigger,
+ steps: workflowVersion.steps,
+ });
+ }
+ },
+ ConfirmationModal: (
+
+ ),
+ });
+ };
+
+ const unregisterUseAsDraftWorkflowVersionSingleRecordAction = () => {
+ removeActionMenuEntry('use-workflow-version-as-draft-single-record');
+ };
+
+ return {
+ registerUseAsDraftWorkflowVersionSingleRecordAction,
+ unregisterUseAsDraftWorkflowVersionSingleRecordAction,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts
new file mode 100644
index 0000000000000..258c06777ffe5
--- /dev/null
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useWorkflowVersionsSingleRecordActions.ts
@@ -0,0 +1,70 @@
+import { useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction';
+import { useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction';
+import { useUseAsDraftWorkflowVersionSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/workflow-version-actions/hooks/useUseAsDraftWorkflowVersionSingleRecordAction';
+import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { isDefined } from 'twenty-ui';
+
+export const useWorkflowVersionsSingleRecordActions = () => {
+ const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
+ contextStoreTargetedRecordsRuleComponentState,
+ );
+
+ const selectedRecordId =
+ contextStoreTargetedRecordsRule.mode === 'selection'
+ ? contextStoreTargetedRecordsRule.selectedRecordIds?.[0]
+ : undefined;
+
+ if (!isDefined(selectedRecordId)) {
+ throw new Error('Selected record ID is required');
+ }
+
+ const {
+ registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction,
+ } = useSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({
+ workflowVersionId: selectedRecordId,
+ });
+
+ const {
+ registerUseAsDraftWorkflowVersionSingleRecordAction,
+ unregisterUseAsDraftWorkflowVersionSingleRecordAction,
+ } = useUseAsDraftWorkflowVersionSingleRecordAction({
+ workflowVersionId: selectedRecordId,
+ });
+
+ const {
+ registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
+ unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction,
+ } = useSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({
+ workflowVersionId: selectedRecordId,
+ });
+
+ const registerSingleRecordActions = ({
+ startPosition,
+ }: {
+ startPosition: number;
+ }) => {
+ registerUseAsDraftWorkflowVersionSingleRecordAction({
+ position: startPosition,
+ });
+ registerSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction({
+ position: startPosition + 1,
+ });
+
+ registerSeeWorkflowExecutionsWorkflowVersionSingleRecordAction({
+ position: startPosition + 2,
+ });
+ };
+
+ const unregisterSingleRecordActions = () => {
+ unregisterUseAsDraftWorkflowVersionSingleRecordAction();
+ unregisterSeeWorkflowVersionsHistoryWorkflowVersionSingleRecordAction();
+ unregisterSeeWorkflowExecutionsWorkflowVersionSingleRecordAction();
+ };
+
+ return {
+ registerSingleRecordActions,
+ unregisterSingleRecordActions,
+ };
+};
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx
index dbb7f7cc8aea6..8bd8a3d0fa068 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions.tsx
@@ -6,13 +6,10 @@ import {
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
-import { useTheme } from '@emotion/react';
import { useRecoilValue } from 'recoil';
import { IconSettingsAutomation, isDefined } from 'twenty-ui';
import { capitalize } from '~/utils/string/capitalize';
@@ -33,8 +30,12 @@ export const useWorkflowRunRecordActions = ({
? contextStoreTargetedRecordsRule.selectedRecordIds[0]
: undefined;
+ if (!isDefined(selectedRecordId)) {
+ throw new Error('Selected record ID is required');
+ }
+
const selectedRecord = useRecoilValue(
- recordStoreFamilyState(selectedRecordId ?? ''),
+ recordStoreFamilyState(selectedRecordId),
);
const { records: activeWorkflowVersions } = useAllActiveWorkflowVersions({
@@ -44,10 +45,6 @@ export const useWorkflowRunRecordActions = ({
const { runWorkflowVersion } = useRunWorkflowVersion();
- const { enqueueSnackBar } = useSnackBar();
-
- const theme = useTheme();
-
const registerWorkflowRunRecordActions = () => {
if (!isDefined(objectMetadataItem) || objectMetadataItem.isRemote) {
return;
@@ -57,6 +54,9 @@ export const useWorkflowRunRecordActions = ({
index,
activeWorkflowVersion,
] of activeWorkflowVersions.entries()) {
+ if (!isDefined(activeWorkflowVersion.workflow)) {
+ continue;
+ }
const name = capitalize(activeWorkflowVersion.workflow.name);
addActionMenuEntry({
type: ActionMenuEntryType.WorkflowRun,
@@ -72,19 +72,9 @@ export const useWorkflowRunRecordActions = ({
await runWorkflowVersion({
workflowVersionId: activeWorkflowVersion.id,
+ workflowName: name,
payload: selectedRecord,
});
-
- enqueueSnackBar('', {
- variant: SnackBarVariant.Success,
- title: `${name} starting...`,
- icon: (
-
- ),
- });
},
});
}
diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx
index 24a99704607b2..33a2c270cfd2a 100644
--- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx
+++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/workflow-run-actions/hooks/useWorkflowRunActions.tsx
@@ -3,14 +3,11 @@ import {
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
-import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useAllActiveWorkflowVersions } from '@/workflow/hooks/useAllActiveWorkflowVersions';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
-import { useTheme } from '@emotion/react';
-import { IconSettingsAutomation } from 'twenty-ui';
+import { IconSettingsAutomation, isDefined } from 'twenty-ui';
import { capitalize } from '~/utils/string/capitalize';
export const useWorkflowRunActions = () => {
@@ -24,10 +21,6 @@ export const useWorkflowRunActions = () => {
const { runWorkflowVersion } = useRunWorkflowVersion();
- const { enqueueSnackBar } = useSnackBar();
-
- const theme = useTheme();
-
const addWorkflowRunActions = () => {
if (!isWorkflowEnabled) {
return;
@@ -37,7 +30,12 @@ export const useWorkflowRunActions = () => {
index,
activeWorkflowVersion,
] of activeWorkflowVersions.entries()) {
+ if (!isDefined(activeWorkflowVersion.workflow)) {
+ continue;
+ }
+
const name = capitalize(activeWorkflowVersion.workflow.name);
+
addActionMenuEntry({
type: ActionMenuEntryType.WorkflowRun,
key: `workflow-run-${activeWorkflowVersion.id}`,
@@ -48,17 +46,7 @@ export const useWorkflowRunActions = () => {
onClick: async () => {
await runWorkflowVersion({
workflowVersionId: activeWorkflowVersion.id,
- });
-
- enqueueSnackBar('', {
- variant: SnackBarVariant.Success,
- title: `${name} starting...`,
- icon: (
-
- ),
+ workflowName: name,
});
},
});
diff --git a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
index d66b4629a732c..2cc66103abf5d 100644
--- a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx
@@ -7,14 +7,17 @@ import {
} from '@/ui/layout/modal/components/ConfirmationModal';
import { openOverrideWorkflowDraftConfirmationModalState } from '@/workflow/states/openOverrideWorkflowDraftConfirmationModalState';
import { WorkflowVersion } from '@/workflow/types/Workflow';
+import { useNavigate } from 'react-router-dom';
import { useRecoilState } from 'recoil';
export const OverrideWorkflowDraftConfirmationModal = ({
draftWorkflowVersionId,
workflowVersionUpdateInput,
+ workflowId,
}: {
draftWorkflowVersionId: string;
workflowVersionUpdateInput: Pick;
+ workflowId: string;
}) => {
const [
openOverrideWorkflowDraftConfirmationModal,
@@ -26,11 +29,15 @@ export const OverrideWorkflowDraftConfirmationModal = ({
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
});
+ const navigate = useNavigate();
+
const handleOverrideDraft = async () => {
await updateOneWorkflowVersion({
idToUpdate: draftWorkflowVersionId,
updateOneRecordInput: workflowVersionUpdateInput,
});
+
+ navigate(buildShowPageURL(CoreObjectNameSingular.Workflow, workflowId));
};
return (
diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx
index 7afd648c77c57..d6b9f263a3697 100644
--- a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowHeader.tsx
@@ -71,6 +71,7 @@ export const RecordShowPageWorkflowHeader = ({
await runWorkflowVersion({
workflowVersionId: workflowWithCurrentVersion.currentVersion.id,
+ workflowName: workflowWithCurrentVersion.name,
});
enqueueSnackBar('', {
diff --git a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
index 08c388b49eda6..3fb44ace77d7e 100644
--- a/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
+++ b/packages/twenty-front/src/modules/workflow/components/RecordShowPageWorkflowVersionHeader.tsx
@@ -131,6 +131,7 @@ export const RecordShowPageWorkflowVersionHeader = ({
{isDefined(workflowVersion) && isDefined(draftWorkflowVersion) ? (
{
+ const { records: workflowVersions } = useFindManyRecords<
+ WorkflowVersion & {
+ workflow: Omit & {
+ versions: Array<{ __typename: string }>;
+ };
+ }
+ >({
+ objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
+ filter: {
+ workflowId: {
+ eq: workflowId,
+ },
+ status: {
+ eq: 'ACTIVE',
+ },
+ },
+ recordGqlFields: {
+ id: true,
+ name: true,
+ createdAt: true,
+ updatedAt: true,
+ workflowId: true,
+ trigger: true,
+ steps: true,
+ status: true,
+ workflow: {
+ id: true,
+ name: true,
+ statuses: true,
+ versions: {
+ totalCount: true,
+ },
+ },
+ },
+ });
+
+ return workflowVersions?.[0];
+};
diff --git a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx
similarity index 56%
rename from packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.ts
rename to packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx
index e330f0c91f993..a93cc7ac0e646 100644
--- a/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.ts
+++ b/packages/twenty-front/src/modules/workflow/hooks/useRunWorkflowVersion.tsx
@@ -1,10 +1,15 @@
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
+import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
+import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { RUN_WORKFLOW_VERSION } from '@/workflow/graphql/mutations/runWorkflowVersion';
import { useMutation } from '@apollo/client';
+import { useTheme } from '@emotion/react';
+import { IconSettingsAutomation } from 'twenty-ui';
import {
RunWorkflowVersionMutation,
RunWorkflowVersionMutationVariables,
} from '~/generated/graphql';
+import { capitalize } from '~/utils/string/capitalize';
export const useRunWorkflowVersion = () => {
const apolloMetadataClient = useApolloMetadataClient();
@@ -15,16 +20,33 @@ export const useRunWorkflowVersion = () => {
client: apolloMetadataClient,
});
+ const { enqueueSnackBar } = useSnackBar();
+
+ const theme = useTheme();
+
const runWorkflowVersion = async ({
workflowVersionId,
+ workflowName,
payload,
}: {
workflowVersionId: string;
+ workflowName: string;
payload?: Record;
}) => {
await mutate({
variables: { input: { workflowVersionId, payload } },
});
+
+ enqueueSnackBar('', {
+ variant: SnackBarVariant.Success,
+ title: `${capitalize(workflowName)} starting...`,
+ icon: (
+
+ ),
+ });
};
return { runWorkflowVersion };
diff --git a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts
index 18b17713d2620..b67e8f874073c 100644
--- a/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts
+++ b/packages/twenty-front/src/modules/workflow/hooks/useWorkflowWithCurrentVersion.ts
@@ -17,6 +17,7 @@ export const useWorkflowWithCurrentVersion = (
id: true,
name: true,
statuses: true,
+ lastPublishedVersionId: true,
versions: true,
},
skip: !isDefined(workflowId),
diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
index 7b2cfe6fa3438..25fa6ff2977f7 100644
--- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
+++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts
@@ -150,6 +150,8 @@ export {
IconHeartOff,
IconHelpCircle,
IconHierarchy2,
+ IconHistory,
+ IconHistoryToggle,
IconHome,
IconInbox,
IconInfoCircle,
@@ -192,6 +194,7 @@ export {
IconPhoto,
IconPhotoUp,
IconPilcrow,
+ IconPlayerPause,
IconPlayerPlay,
IconPlayerStop,
IconPlaylistAdd,
diff --git a/packages/twenty-ui/src/display/icon/providers/internal/AllIcons.ts b/packages/twenty-ui/src/display/icon/providers/internal/AllIcons.ts
index f85a10d416ae8..d8262e70c060a 100644
--- a/packages/twenty-ui/src/display/icon/providers/internal/AllIcons.ts
+++ b/packages/twenty-ui/src/display/icon/providers/internal/AllIcons.ts
@@ -10,20 +10,21 @@ import {
Icon3dRotate,
IconAB,
IconAB2,
+ IconABOff,
IconAbacus,
IconAbacusOff,
IconAbc,
- IconABOff,
- IconAccessible,
- IconAccessibleOff,
IconAccessPoint,
IconAccessPointOff,
+ IconAccessible,
+ IconAccessibleOff,
IconActivity,
IconActivityHeartbeat,
IconAd,
IconAd2,
IconAdCircle,
IconAdCircleOff,
+ IconAdOff,
IconAddressBookOff,
IconAdjustments,
IconAdjustmentsAlt,
@@ -48,7 +49,6 @@ import {
IconAdjustmentsStar,
IconAdjustmentsUp,
IconAdjustmentsX,
- IconAdOff,
IconAerialLift,
IconAffiliate,
IconAirBalloon,
@@ -118,10 +118,10 @@ import {
IconApiApp,
IconApiAppOff,
IconApiOff,
+ IconAppWindow,
IconApple,
IconApps,
IconAppsOff,
- IconAppWindow,
IconArchive,
IconArchiveOff,
IconArmchair,
@@ -233,6 +233,23 @@ import {
IconArrowRotaryStraight,
IconArrowRoundaboutLeft,
IconArrowRoundaboutRight,
+ IconArrowSharpTurnLeft,
+ IconArrowSharpTurnRight,
+ IconArrowUp,
+ IconArrowUpBar,
+ IconArrowUpCircle,
+ IconArrowUpLeft,
+ IconArrowUpLeftCircle,
+ IconArrowUpRhombus,
+ IconArrowUpRight,
+ IconArrowUpRightCircle,
+ IconArrowUpSquare,
+ IconArrowUpTail,
+ IconArrowWaveLeftDown,
+ IconArrowWaveLeftUp,
+ IconArrowWaveRightDown,
+ IconArrowWaveRightUp,
+ IconArrowZigZag,
IconArrowsCross,
IconArrowsDiagonal,
IconArrowsDiagonal2,
@@ -247,8 +264,6 @@ import {
IconArrowsDownUp,
IconArrowsExchange,
IconArrowsExchange2,
- IconArrowSharpTurnLeft,
- IconArrowSharpTurnRight,
IconArrowsHorizontal,
IconArrowsJoin,
IconArrowsJoin2,
@@ -276,21 +291,6 @@ import {
IconArrowsUpLeft,
IconArrowsUpRight,
IconArrowsVertical,
- IconArrowUp,
- IconArrowUpBar,
- IconArrowUpCircle,
- IconArrowUpLeft,
- IconArrowUpLeftCircle,
- IconArrowUpRhombus,
- IconArrowUpRight,
- IconArrowUpRightCircle,
- IconArrowUpSquare,
- IconArrowUpTail,
- IconArrowWaveLeftDown,
- IconArrowWaveLeftUp,
- IconArrowWaveRightDown,
- IconArrowWaveRightUp,
- IconArrowZigZag,
IconArtboard,
IconArtboardOff,
IconArticle,
@@ -331,13 +331,13 @@ import {
IconBadgeCc,
IconBadgeHd,
IconBadgeOff,
- IconBadges,
IconBadgeSd,
- IconBadgesOff,
IconBadgeTm,
IconBadgeVo,
IconBadgeVr,
IconBadgeWc,
+ IconBadges,
+ IconBadgesOff,
IconBaguette,
IconBallAmericanFootball,
IconBallAmericanFootballOff,
@@ -346,12 +346,12 @@ import {
IconBallBowling,
IconBallFootball,
IconBallFootballOff,
+ IconBallTennis,
+ IconBallVolleyball,
IconBalloon,
IconBalloonOff,
IconBallpen,
IconBallpenOff,
- IconBallTennis,
- IconBallVolleyball,
IconBan,
IconBandage,
IconBandageOff,
@@ -468,6 +468,8 @@ import {
IconBook,
IconBook2,
IconBookDownload,
+ IconBookOff,
+ IconBookUpload,
IconBookmark,
IconBookmarkEdit,
IconBookmarkMinus,
@@ -476,10 +478,8 @@ import {
IconBookmarkQuestion,
IconBookmarks,
IconBookmarksOff,
- IconBookOff,
IconBooks,
IconBooksOff,
- IconBookUpload,
IconBorderAll,
IconBorderBottom,
IconBorderCorners,
@@ -582,6 +582,7 @@ import {
IconBrandBulma,
IconBrandBumble,
IconBrandBunpo,
+ IconBrandCSharp,
IconBrandCake,
IconBrandCakephp,
IconBrandCampaignmonitor,
@@ -603,7 +604,6 @@ import {
IconBrandCpp,
IconBrandCraft,
IconBrandCrunchbase,
- IconBrandCSharp,
IconBrandCss3,
IconBrandCtemplar,
IconBrandCucumber,
@@ -739,8 +739,8 @@ import {
IconBrandOkRu,
IconBrandOnedrive,
IconBrandOnlyfans,
- IconBrandOpenai,
IconBrandOpenSource,
+ IconBrandOpenai,
IconBrandOpenvpn,
IconBrandOpera,
IconBrandPagekit,
@@ -934,9 +934,9 @@ import {
IconBulbOff,
IconBulldozer,
IconBus,
- IconBusinessplan,
IconBusOff,
IconBusStop,
+ IconBusinessplan,
IconButterfly,
IconCactus,
IconCactusOff,
@@ -1006,9 +1006,11 @@ import {
IconCapture,
IconCaptureOff,
IconCar,
- IconCaravan,
IconCarCrane,
IconCarCrash,
+ IconCarOff,
+ IconCarTurbine,
+ IconCaravan,
IconCardboards,
IconCardboardsOff,
IconCards,
@@ -1016,12 +1018,10 @@ import {
IconCaretLeft,
IconCaretRight,
IconCaretUp,
- IconCarOff,
IconCarouselHorizontal,
IconCarouselVertical,
IconCarrot,
IconCarrotOff,
- IconCarTurbine,
IconCash,
IconCashBanknote,
IconCashBanknoteOff,
@@ -1032,6 +1032,7 @@ import {
IconCategory,
IconCategory2,
IconCe,
+ IconCeOff,
IconCell,
IconCellSignal1,
IconCellSignal2,
@@ -1039,7 +1040,6 @@ import {
IconCellSignal4,
IconCellSignal5,
IconCellSignalOff,
- IconCeOff,
IconCertificate,
IconCertificate2,
IconCertificate2Off,
@@ -1105,6 +1105,9 @@ import {
IconChevronLeftPipe,
IconChevronRight,
IconChevronRightPipe,
+ IconChevronUp,
+ IconChevronUpLeft,
+ IconChevronUpRight,
IconChevronsDown,
IconChevronsDownLeft,
IconChevronsDownRight,
@@ -1113,9 +1116,6 @@ import {
IconChevronsUp,
IconChevronsUpLeft,
IconChevronsUpRight,
- IconChevronUp,
- IconChevronUpLeft,
- IconChevronUpRight,
IconChisel,
IconChristmasTree,
IconChristmasTreeOff,
@@ -1136,11 +1136,11 @@ import {
IconCircleChevronDown,
IconCircleChevronLeft,
IconCircleChevronRight,
+ IconCircleChevronUp,
IconCircleChevronsDown,
IconCircleChevronsLeft,
IconCircleChevronsRight,
IconCircleChevronsUp,
- IconCircleChevronUp,
IconCircleDashed,
IconCircleDot,
IconCircleDotted,
@@ -1189,11 +1189,11 @@ import {
IconCirclePlus,
IconCircleRectangle,
IconCircleRectangleOff,
- IconCircles,
IconCircleSquare,
- IconCirclesRelation,
IconCircleTriangle,
IconCircleX,
+ IconCircles,
+ IconCirclesRelation,
IconCircuitAmmeter,
IconCircuitBattery,
IconCircuitBulb,
@@ -1320,9 +1320,9 @@ import {
IconCoinOff,
IconCoinPound,
IconCoinRupee,
- IconCoins,
IconCoinYen,
IconCoinYuan,
+ IconCoins,
IconColorFilter,
IconColorPicker,
IconColorPickerOff,
@@ -1361,9 +1361,9 @@ import {
IconCookieMan,
IconCookieOff,
IconCopy,
+ IconCopyOff,
IconCopyleft,
IconCopyleftOff,
- IconCopyOff,
IconCopyright,
IconCopyrightOff,
IconCornerDownLeft,
@@ -1399,8 +1399,8 @@ import {
IconCricket,
IconCrop,
IconCross,
- IconCrosshair,
IconCrossOff,
+ IconCrosshair,
IconCrown,
IconCrownOff,
IconCrutches,
@@ -1648,37 +1648,13 @@ import {
IconDeviceNintendoOff,
IconDeviceProjector,
IconDeviceRemote,
- IconDevices,
- IconDevices2,
- IconDevicesBolt,
- IconDevicesCancel,
- IconDevicesCheck,
- IconDevicesCode,
- IconDevicesCog,
IconDeviceSdCard,
- IconDevicesDollar,
- IconDevicesDown,
- IconDevicesExclamation,
- IconDevicesHeart,
IconDeviceSim,
IconDeviceSim1,
IconDeviceSim2,
IconDeviceSim3,
- IconDevicesMinus,
- IconDevicesOff,
- IconDevicesPause,
- IconDevicesPc,
- IconDevicesPcOff,
IconDeviceSpeaker,
IconDeviceSpeakerOff,
- IconDevicesPin,
- IconDevicesPlus,
- IconDevicesQuestion,
- IconDevicesSearch,
- IconDevicesShare,
- IconDevicesStar,
- IconDevicesUp,
- IconDevicesX,
IconDeviceTablet,
IconDeviceTabletBolt,
IconDeviceTabletCancel,
@@ -1727,6 +1703,30 @@ import {
IconDeviceWatchStats2,
IconDeviceWatchUp,
IconDeviceWatchX,
+ IconDevices,
+ IconDevices2,
+ IconDevicesBolt,
+ IconDevicesCancel,
+ IconDevicesCheck,
+ IconDevicesCode,
+ IconDevicesCog,
+ IconDevicesDollar,
+ IconDevicesDown,
+ IconDevicesExclamation,
+ IconDevicesHeart,
+ IconDevicesMinus,
+ IconDevicesOff,
+ IconDevicesPause,
+ IconDevicesPc,
+ IconDevicesPcOff,
+ IconDevicesPin,
+ IconDevicesPlus,
+ IconDevicesQuestion,
+ IconDevicesSearch,
+ IconDevicesShare,
+ IconDevicesStar,
+ IconDevicesUp,
+ IconDevicesX,
IconDiabolo,
IconDiaboloOff,
IconDiaboloPlus,
@@ -1745,9 +1745,9 @@ import {
IconDimensions,
IconDirection,
IconDirectionHorizontal,
- IconDirections,
IconDirectionSign,
IconDirectionSignOff,
+ IconDirections,
IconDirectionsOff,
IconDisabled,
IconDisabled2,
@@ -1801,13 +1801,14 @@ import {
IconDropletPin,
IconDropletPlus,
IconDropletQuestion,
- IconDroplets,
IconDropletSearch,
IconDropletShare,
IconDropletStar,
IconDropletUp,
IconDropletX,
+ IconDroplets,
IconDualScreen,
+ IconEPassport,
IconEar,
IconEarOff,
IconEaseIn,
@@ -1833,7 +1834,6 @@ import {
IconEmphasis,
IconEngine,
IconEngineOff,
- IconEPassport,
IconEqual,
IconEqualDouble,
IconEqualNot,
@@ -1872,9 +1872,6 @@ import {
IconEyeDown,
IconEyeEdit,
IconEyeExclamation,
- IconEyeglass,
- IconEyeglass2,
- IconEyeglassOff,
IconEyeHeart,
IconEyeMinus,
IconEyeOff,
@@ -1888,6 +1885,9 @@ import {
IconEyeTable,
IconEyeUp,
IconEyeX,
+ IconEyeglass,
+ IconEyeglass2,
+ IconEyeglassOff,
IconFaceId,
IconFaceIdError,
IconFaceMask,
@@ -1942,13 +1942,11 @@ import {
IconFilePower,
IconFileReport,
IconFileRss,
- IconFiles,
IconFileScissors,
IconFileSearch,
IconFileSettings,
IconFileShredder,
IconFileSignal,
- IconFilesOff,
IconFileSpreadsheet,
IconFileStack,
IconFileStar,
@@ -1985,6 +1983,8 @@ import {
IconFileVector,
IconFileX,
IconFileZip,
+ IconFiles,
+ IconFilesOff,
IconFilter,
IconFilterBolt,
IconFilterCancel,
@@ -2003,12 +2003,12 @@ import {
IconFilterPin,
IconFilterPlus,
IconFilterQuestion,
- IconFilters,
IconFilterSearch,
IconFilterShare,
IconFilterStar,
IconFilterUp,
IconFilterX,
+ IconFilters,
IconFingerprint,
IconFingerprintOff,
IconFireExtinguisher,
@@ -2070,6 +2070,7 @@ import {
IconFocusCentered,
IconFold,
IconFoldDown,
+ IconFoldUp,
IconFolder,
IconFolderBolt,
IconFolderCancel,
@@ -2087,15 +2088,14 @@ import {
IconFolderPin,
IconFolderPlus,
IconFolderQuestion,
- IconFolders,
IconFolderSearch,
IconFolderShare,
- IconFoldersOff,
IconFolderStar,
IconFolderSymlink,
IconFolderUp,
IconFolderX,
- IconFoldUp,
+ IconFolders,
+ IconFoldersOff,
IconForbid,
IconForbid2,
IconForklift,
@@ -2224,7 +2224,6 @@ import {
IconHeadsetOff,
IconHealthRecognition,
IconHeart,
- IconHeartbeat,
IconHeartBolt,
IconHeartBroken,
IconHeartCancel,
@@ -2243,13 +2242,14 @@ import {
IconHeartPlus,
IconHeartQuestion,
IconHeartRateMonitor,
- IconHearts,
IconHeartSearch,
IconHeartShare,
- IconHeartsOff,
IconHeartStar,
IconHeartUp,
IconHeartX,
+ IconHeartbeat,
+ IconHearts,
+ IconHeartsOff,
IconHelicopter,
IconHelicopterLanding,
IconHelmet,
@@ -2268,12 +2268,6 @@ import {
IconHemispherePlus,
IconHexagon,
IconHexagon3d,
- IconHexagonalPrism,
- IconHexagonalPrismOff,
- IconHexagonalPrismPlus,
- IconHexagonalPyramid,
- IconHexagonalPyramidOff,
- IconHexagonalPyramidPlus,
IconHexagonLetterA,
IconHexagonLetterB,
IconHexagonLetterC,
@@ -2311,6 +2305,12 @@ import {
IconHexagonNumber8,
IconHexagonNumber9,
IconHexagonOff,
+ IconHexagonalPrism,
+ IconHexagonalPrismOff,
+ IconHexagonalPrismPlus,
+ IconHexagonalPyramid,
+ IconHexagonalPyramidOff,
+ IconHexagonalPyramidPlus,
IconHexagons,
IconHexagonsOff,
IconHierarchy,
@@ -2424,6 +2424,7 @@ import {
IconKayak,
IconKering,
IconKey,
+ IconKeyOff,
IconKeyboard,
IconKeyboardHide,
IconKeyboardOff,
@@ -2433,7 +2434,6 @@ import {
IconKeyframeAlignHorizontal,
IconKeyframeAlignVertical,
IconKeyframes,
- IconKeyOff,
IconLadder,
IconLadderOff,
IconLadle,
@@ -2627,8 +2627,6 @@ import {
IconMail,
IconMailAi,
IconMailBolt,
- IconMailbox,
- IconMailboxOff,
IconMailCancel,
IconMailCheck,
IconMailCode,
@@ -2651,6 +2649,8 @@ import {
IconMailStar,
IconMailUp,
IconMailX,
+ IconMailbox,
+ IconMailboxOff,
IconMan,
IconManualGearbox,
IconMap,
@@ -2684,12 +2684,12 @@ import {
IconMapPinPin,
IconMapPinPlus,
IconMapPinQuestion,
- IconMapPins,
IconMapPinSearch,
IconMapPinShare,
IconMapPinStar,
IconMapPinUp,
IconMapPinX,
+ IconMapPins,
IconMapPlus,
IconMapQuestion,
IconMapSearch,
@@ -2720,8 +2720,8 @@ import {
IconMathFunctionY,
IconMathGreater,
IconMathIntegral,
- IconMathIntegrals,
IconMathIntegralX,
+ IconMathIntegrals,
IconMathLower,
IconMathMax,
IconMathMin,
@@ -2820,13 +2820,13 @@ import {
IconMessagePlus,
IconMessageQuestion,
IconMessageReport,
- IconMessages,
IconMessageSearch,
IconMessageShare,
- IconMessagesOff,
IconMessageStar,
IconMessageUp,
IconMessageX,
+ IconMessages,
+ IconMessagesOff,
IconMeteor,
IconMeteorOff,
IconMichelinBibGourmand,
@@ -2972,8 +2972,8 @@ import {
IconNeedleThread,
IconNetwork,
IconNetworkOff,
- IconNews,
IconNewSection,
+ IconNews,
IconNewsOff,
IconNfc,
IconNfcOff,
@@ -2982,9 +2982,9 @@ import {
IconNoDerivatives,
IconNorthStar,
IconNote,
+ IconNoteOff,
IconNotebook,
IconNotebookOff,
- IconNoteOff,
IconNotes,
IconNotesOff,
IconNotification,
@@ -3143,8 +3143,8 @@ import {
IconPlaneDeparture,
IconPlaneInflight,
IconPlaneOff,
- IconPlanet,
IconPlaneTilt,
+ IconPlanet,
IconPlanetOff,
IconPlant,
IconPlant2,
@@ -3153,6 +3153,9 @@ import {
IconPlayBasketball,
IconPlayCard,
IconPlayCardOff,
+ IconPlayFootball,
+ IconPlayHandball,
+ IconPlayVolleyball,
IconPlayerEject,
IconPlayerPause,
IconPlayerPlay,
@@ -3162,8 +3165,6 @@ import {
IconPlayerStop,
IconPlayerTrackNext,
IconPlayerTrackPrev,
- IconPlayFootball,
- IconPlayHandball,
IconPlaylist,
IconPlaylistAdd,
IconPlaylistOff,
@@ -3172,7 +3173,6 @@ import {
IconPlaystationSquare,
IconPlaystationTriangle,
IconPlaystationX,
- IconPlayVolleyball,
IconPlug,
IconPlugConnected,
IconPlugConnectedX,
@@ -3185,6 +3185,7 @@ import {
IconPodium,
IconPodiumOff,
IconPoint,
+ IconPointOff,
IconPointer,
IconPointerBolt,
IconPointerCancel,
@@ -3206,7 +3207,6 @@ import {
IconPointerStar,
IconPointerUp,
IconPointerX,
- IconPointOff,
IconPokeball,
IconPokeballOff,
IconPokerChip,
@@ -3256,9 +3256,9 @@ import {
IconRadar2,
IconRadarOff,
IconRadio,
+ IconRadioOff,
IconRadioactive,
IconRadioactiveOff,
- IconRadioOff,
IconRadiusBottomLeft,
IconRadiusBottomRight,
IconRadiusTopLeft,
@@ -3342,9 +3342,9 @@ import {
IconRobotOff,
IconRocket,
IconRocketOff,
+ IconRollerSkating,
IconRollercoaster,
IconRollercoasterOff,
- IconRollerSkating,
IconRosette,
IconRosetteNumber0,
IconRosetteNumber1,
@@ -3381,6 +3381,10 @@ import {
IconRulerMeasure,
IconRulerOff,
IconRun,
+ IconSTurnDown,
+ IconSTurnLeft,
+ IconSTurnRight,
+ IconSTurnUp,
IconSailboat,
IconSailboat2,
IconSailboatOff,
@@ -3471,6 +3475,7 @@ import {
IconShare2,
IconShare3,
IconShareOff,
+ IconShiJumping,
IconShield,
IconShieldBolt,
IconShieldCancel,
@@ -3496,7 +3501,6 @@ import {
IconShieldStar,
IconShieldUp,
IconShieldX,
- IconShiJumping,
IconShip,
IconShipOff,
IconShirt,
@@ -3538,6 +3542,8 @@ import {
IconShoppingCartX,
IconShovel,
IconShredder,
+ IconSignLeft,
+ IconSignRight,
IconSignal2g,
IconSignal3g,
IconSignal4g,
@@ -3551,13 +3557,11 @@ import {
IconSignalLte,
IconSignature,
IconSignatureOff,
- IconSignLeft,
- IconSignRight,
IconSitemap,
IconSitemapOff,
IconSkateboard,
- IconSkateboarding,
IconSkateboardOff,
+ IconSkateboarding,
IconSkull,
IconSlash,
IconSlashes,
@@ -3581,11 +3585,11 @@ import {
IconSolarPanel2,
IconSort09,
IconSort90,
+ IconSortAZ,
IconSortAscending,
IconSortAscending2,
IconSortAscendingLetters,
IconSortAscendingNumbers,
- IconSortAZ,
IconSortDescending,
IconSortDescending2,
IconSortDescendingLetters,
@@ -3624,11 +3628,11 @@ import {
IconSquareChevronDown,
IconSquareChevronLeft,
IconSquareChevronRight,
+ IconSquareChevronUp,
IconSquareChevronsDown,
IconSquareChevronsLeft,
IconSquareChevronsRight,
IconSquareChevronsUp,
- IconSquareChevronUp,
IconSquareDot,
IconSquareF0,
IconSquareF1,
@@ -3698,11 +3702,11 @@ import {
IconSquareRoundedChevronDown,
IconSquareRoundedChevronLeft,
IconSquareRoundedChevronRight,
+ IconSquareRoundedChevronUp,
IconSquareRoundedChevronsDown,
IconSquareRoundedChevronsLeft,
IconSquareRoundedChevronsRight,
IconSquareRoundedChevronsUp,
- IconSquareRoundedChevronUp,
IconSquareRoundedLetterA,
IconSquareRoundedLetterB,
IconSquareRoundedLetterC,
@@ -3742,10 +3746,10 @@ import {
IconSquareRoundedNumber9,
IconSquareRoundedPlus,
IconSquareRoundedX,
- IconSquaresDiagonal,
IconSquareToggle,
IconSquareToggleHorizontal,
IconSquareX,
+ IconSquaresDiagonal,
IconStack,
IconStack2,
IconStack3,
@@ -3774,25 +3778,21 @@ import {
IconStretching,
IconStretching2,
IconStrikethrough,
- IconSTurnDown,
- IconSTurnLeft,
- IconSTurnRight,
- IconSTurnUp,
IconSubmarine,
IconSubscript,
IconSubtask,
IconSum,
IconSumOff,
IconSun,
- IconSunglasses,
IconSunHigh,
IconSunLow,
IconSunMoon,
IconSunOff,
+ IconSunWind,
+ IconSunglasses,
IconSunrise,
IconSunset,
IconSunset2,
- IconSunWind,
IconSuperscript,
IconSvg,
IconSwimming,
@@ -3863,18 +3863,18 @@ import {
IconTextResize,
IconTextSize,
IconTextSpellcheck,
- IconTexture,
IconTextWrap,
IconTextWrapDisabled,
+ IconTexture,
IconTheater,
IconThermometer,
IconThumbDown,
IconThumbDownOff,
IconThumbUp,
IconThumbUpOff,
+ IconTicTac,
IconTicket,
IconTicketOff,
- IconTicTac,
IconTie,
IconTilde,
IconTiltShift,
@@ -3960,8 +3960,8 @@ import {
IconTriangle,
IconTriangleInverted,
IconTriangleOff,
- IconTriangles,
IconTriangleSquareCircle,
+ IconTriangles,
IconTrident,
IconTrolley,
IconTrophy,
@@ -4002,16 +4002,16 @@ import {
IconUserPin,
IconUserPlus,
IconUserQuestion,
- IconUsers,
IconUserSearch,
- IconUsersGroup,
IconUserShare,
IconUserShield,
- IconUsersMinus,
- IconUsersPlus,
IconUserStar,
IconUserUp,
IconUserX,
+ IconUsers,
+ IconUsersGroup,
+ IconUsersMinus,
+ IconUsersPlus,
IconUvIndex,
IconUxCircle,
IconVaccine,
@@ -4060,9 +4060,9 @@ import {
IconVolumeOff,
IconWalk,
IconWall,
+ IconWallOff,
IconWallet,
IconWalletOff,
- IconWallOff,
IconWallpaper,
IconWallpaperOff,
IconWand,
@@ -4073,8 +4073,6 @@ import {
IconWashDry2,
IconWashDry3,
IconWashDryA,
- IconWashDryclean,
- IconWashDrycleanOff,
IconWashDryDip,
IconWashDryF,
IconWashDryFlat,
@@ -4083,6 +4081,8 @@ import {
IconWashDryP,
IconWashDryShade,
IconWashDryW,
+ IconWashDryclean,
+ IconWashDrycleanOff,
IconWashEco,
IconWashGentle,
IconWashHand,
@@ -4113,9 +4113,9 @@ import {
IconWifi2,
IconWifiOff,
IconWind,
+ IconWindOff,
IconWindmill,
IconWindmillOff,
- IconWindOff,
IconWindow,
IconWindowMaximize,
IconWindowMinimize,