Skip to content

Commit

Permalink
9260 refactor multiple record actions and no selection actions (#9314)
Browse files Browse the repository at this point in the history
Closes #9260

- Refactored multiple record actions and no selection record actions to
use config file
- Simplified actions registration logic
- Updated tests
  • Loading branch information
bosiraphael authored Jan 2, 2025
1 parent 306b45a commit e3f7a05
Show file tree
Hide file tree
Showing 52 changed files with 871 additions and 1,123 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
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 { RegisterRecordActionEffect } from '@/action-menu/actions/record-actions/components/RegisterRecordActionEffect';
import { WorkflowRunRecordActionMenuEntrySetterEffect } from '@/action-menu/actions/record-actions/workflow-run-record-actions/components/WorkflowRunRecordActionMenuEntrySetter';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
Expand All @@ -19,33 +16,13 @@ export const RecordActionMenuEntriesSetter = () => {
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataIdComponentState,
);

const objectMetadataItems = useRecoilValue(objectMetadataItemsState);

const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === contextStoreCurrentObjectMetadataId,
);

if (
!isDefined(contextStoreCurrentObjectMetadataId) ||
!isDefined(objectMetadataItem)
) {
return null;
}

return (
<ActionEffects objectMetadataItemId={contextStoreCurrentObjectMetadataId} />
);
};

const ActionEffects = ({
objectMetadataItemId,
}: {
objectMetadataItemId: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: objectMetadataItemId,
});

const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
Expand All @@ -58,43 +35,52 @@ const ActionEffects = ({
FeatureFlagKey.IsWorkflowEnabled,
);

const isPageHeaderV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPageHeaderV2Enabled,
);

if (
!isDefined(contextStoreCurrentObjectMetadataId) ||
!isDefined(objectMetadataItem)
) {
return null;
}

const viewType = getActionViewType(
contextStoreCurrentViewType,
contextStoreTargetedRecordsRule,
);

const actionConfig = getActionConfig(
objectMetadataItem,
isPageHeaderV2Enabled,
);

const actionsToRegister = isDefined(viewType)
? Object.values(actionConfig ?? {}).filter((action) =>
action.availableOn?.includes(viewType),
)
: [];

return (
<>
{contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0 && (
<NoSelectionActionMenuEntrySetterEffect
{actionsToRegister.map((action) => (
<RegisterRecordActionEffect
key={action.key}
action={action}
objectMetadataItem={objectMetadataItem}
/>
))}

{isWorkflowEnabled &&
!(
contextStoreTargetedRecordsRule?.mode === 'selection' &&
contextStoreTargetedRecordsRule?.selectedRecordIds.length === 0
) && (
<WorkflowRunRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
{contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1 && (
<>
{contextStoreCurrentViewType === ContextStoreViewType.ShowPage && (
<SingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
viewType={ActionViewType.SHOW_PAGE}
/>
)}
{(contextStoreCurrentViewType === ContextStoreViewType.Table ||
contextStoreCurrentViewType === ContextStoreViewType.Kanban) && (
<SingleRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
viewType={ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION}
/>
)}
{isWorkflowEnabled && (
<WorkflowRunRecordActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
</>
)}
{(contextStoreTargetedRecordsRule.mode === 'exclusion' ||
contextStoreTargetedRecordsRule.selectedRecordIds.length > 1) && (
<MultipleRecordsActionMenuEntrySetterEffect
objectMetadataItem={objectMetadataItem}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { wrapActionInCallbacks } from '@/action-menu/actions/utils/wrapActionInCallbacks';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useActionMenuEntries } from '@/action-menu/hooks/useActionMenuEntries';
import { ActionMenuEntry } from '@/action-menu/types/ActionMenuEntry';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useContext, useEffect } from 'react';

type RegisterRecordActionEffectProps = {
action: ActionMenuEntry & {
actionHook: ActionHook;
};
objectMetadataItem: ObjectMetadataItem;
};

export const RegisterRecordActionEffect = ({
action,
objectMetadataItem,
}: RegisterRecordActionEffectProps) => {
const { shouldBeRegistered, onClick, ConfirmationModal } = action.actionHook({
objectMetadataItem,
});

const { onActionStartedCallback, onActionExecutedCallback } =
useContext(ActionMenuContext);

const { addActionMenuEntry, removeActionMenuEntry } = useActionMenuEntries();

const wrappedAction = wrapActionInCallbacks({
action: {
...action,
onClick,
ConfirmationModal,
},
onActionStartedCallback,
onActionExecutedCallback,
});

useEffect(() => {
if (shouldBeRegistered) {
addActionMenuEntry(wrappedAction);
}

return () => {
removeActionMenuEntry(wrappedAction.key);
};
}, [
addActionMenuEntry,
removeActionMenuEntry,
shouldBeRegistered,
wrappedAction,
]);

return null;
};
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey';
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
ActionMenuEntryScope,
ActionMenuEntryType,
} from '@/action-menu/types/ActionMenuEntry';
import { IconHeart, IconHeartOff, IconTrash } from 'twenty-ui';
import {
IconDatabaseExport,
IconHeart,
IconHeartOff,
IconTrash,
} from 'twenty-ui';

export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
export const DEFAULT_ACTIONS_CONFIG_V1: Record<
string,
ActionMenuEntry & {
actionHook: SingleRecordActionHook;
actionHook: ActionHook;
}
> = {
addToFavoritesSingleRecord: {
Expand Down Expand Up @@ -58,4 +67,43 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V1: Record<
],
actionHook: useDeleteSingleRecordAction,
},
deleteMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.DELETE,
label: 'Delete records',
shortLabel: 'Delete',
position: 3,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
actionHook: useDeleteMultipleRecordsAction,
},
exportMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.EXPORT,
label: 'Export records',
shortLabel: 'Export',
position: 4,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
actionHook: useExportMultipleRecordsAction,
},
exportView: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: NoSelectionRecordActionKeys.EXPORT_VIEW,
label: 'Export view',
shortLabel: 'Export',
position: 5,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
actionHook: useExportMultipleRecordsAction,
},
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { useDeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction';
import { useExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/hooks/useExportMultipleRecordsAction';
import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/multiple-records/types/MultipleRecordsActionKeys';
import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKey';
import { useAddToFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useAddToFavoritesSingleRecordAction';
import { useDeleteSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction';
import { useDestroySingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction';
Expand All @@ -6,8 +10,8 @@ import { useNavigateToNextRecordSingleRecordAction } from '@/action-menu/actions
import { useNavigateToPreviousRecordSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useNavigateToPreviousRecordSingleRecordAction';
import { useRemoveFromFavoritesSingleRecordAction } from '@/action-menu/actions/record-actions/single-record/hooks/useRemoveFromFavoritesSingleRecordAction';
import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey';
import { ActionHook } from '@/action-menu/actions/types/ActionHook';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { SingleRecordActionHook } from '@/action-menu/actions/types/SingleRecordActionHook';
import {
ActionMenuEntry,
ActionMenuEntryScope,
Expand All @@ -16,17 +20,18 @@ import {
import {
IconChevronDown,
IconChevronUp,
IconDatabaseExport,
IconFileExport,
IconHeart,
IconHeartOff,
IconTrash,
IconTrashX,
} from 'twenty-ui';

export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
export const DEFAULT_ACTIONS_CONFIG_V2: Record<
string,
ActionMenuEntry & {
actionHook: SingleRecordActionHook;
actionHook: ActionHook;
}
> = {
exportNoteToPdf: {
Expand Down Expand Up @@ -87,13 +92,52 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
],
actionHook: useDeleteSingleRecordAction,
},
deleteMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.DELETE,
label: 'Delete records',
shortLabel: 'Delete',
position: 4,
Icon: IconTrash,
accent: 'danger',
isPinned: true,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
actionHook: useDeleteMultipleRecordsAction,
},
exportMultipleRecords: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: MultipleRecordsActionKeys.EXPORT,
label: 'Export records',
shortLabel: 'Export',
position: 5,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
actionHook: useExportMultipleRecordsAction,
},
exportView: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: NoSelectionRecordActionKeys.EXPORT_VIEW,
label: 'Export view',
shortLabel: 'Export',
position: 6,
Icon: IconDatabaseExport,
accent: 'default',
isPinned: false,
availableOn: [ActionViewType.INDEX_PAGE_NO_SELECTION],
actionHook: useExportMultipleRecordsAction,
},
destroySingleRecord: {
type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection,
key: SingleRecordActionKeys.DESTROY,
label: 'Permanently destroy record',
shortLabel: 'Destroy',
position: 4,
position: 7,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,
Expand All @@ -109,7 +153,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
key: SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD,
label: 'Navigate to previous record',
shortLabel: '',
position: 5,
position: 8,
isPinned: true,
Icon: IconChevronUp,
availableOn: [ActionViewType.SHOW_PAGE],
Expand All @@ -121,7 +165,7 @@ export const DEFAULT_SINGLE_RECORD_ACTIONS_CONFIG_V2: Record<
key: SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD,
label: 'Navigate to next record',
shortLabel: '',
position: 6,
position: 9,
isPinned: true,
Icon: IconChevronDown,
availableOn: [ActionViewType.SHOW_PAGE],
Expand Down
Loading

0 comments on commit e3f7a05

Please sign in to comment.