feat: merge records (#13436)

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
neo773
2025-07-31 15:36:36 +05:30
committed by GitHub
parent 00d12e854a
commit 06e0814ce8
45 changed files with 2047 additions and 29 deletions
@@ -1,4 +1,5 @@
import { ActionLink } from '@/action-menu/actions/components/ActionLink';
import { ActionOpenSidePanelPage } from '@/action-menu/actions/components/ActionOpenSidePanelPage';
import { DeleteMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/components/DeleteMultipleRecordsAction';
import { DestroyMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/components/DestroyMultipleRecordsAction';
import { ExportMultipleRecordsAction } from '@/action-menu/actions/record-actions/multiple-records/components/ExportMultipleRecordsAction';
@@ -24,6 +25,7 @@ import { ActionConfig } from '@/action-menu/actions/types/ActionConfig';
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionType } from '@/action-menu/actions/types/ActionType';
import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { BACKEND_BATCH_REQUEST_MAX_COUNT } from '@/object-record/constants/BackendBatchRequestMaxCount';
@@ -31,8 +33,11 @@ import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { msg } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { MUTATION_MAX_MERGE_RECORDS } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import {
IconArrowMerge,
IconBuildingSkyscraper,
IconCheckbox,
IconChevronDown,
@@ -173,13 +178,42 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
component: <ExportSingleRecordAction />,
requiredPermissionFlag: PermissionFlagType.EXPORT_CSV,
},
[MultipleRecordsActionKeys.MERGE]: {
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
key: MultipleRecordsActionKeys.MERGE,
label: msg`Merge records`,
shortLabel: msg`Merge`,
position: 5,
Icon: IconArrowMerge,
accent: 'default',
isPinned: false,
shouldBeRegistered: ({
objectMetadataItem,
numberOfSelectedRecords,
objectPermissions,
}) =>
isDefined(objectMetadataItem?.duplicateCriteria) &&
isDefined(numberOfSelectedRecords) &&
Boolean(objectPermissions.canUpdateObjectRecords) &&
Boolean(objectPermissions.canDestroyObjectRecords) &&
numberOfSelectedRecords <= MUTATION_MAX_MERGE_RECORDS,
availableOn: [ActionViewType.INDEX_PAGE_BULK_SELECTION],
component: (
<ActionOpenSidePanelPage
page={CommandMenuPages.MergeRecords}
pageTitle={msg`Merge records`}
pageIcon={IconArrowMerge}
/>
),
},
[MultipleRecordsActionKeys.EXPORT]: {
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
key: MultipleRecordsActionKeys.EXPORT,
label: msg`Export records`,
shortLabel: msg`Export`,
position: 5,
position: 6,
Icon: IconFileExport,
accent: 'default',
isPinned: false,
@@ -194,7 +228,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.IMPORT_RECORDS,
label: msg`Import records`,
shortLabel: msg`Import`,
position: 6,
position: 7,
Icon: IconFileImport,
accent: 'default',
isPinned: false,
@@ -210,7 +244,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.EXPORT_VIEW,
label: msg`Export view`,
shortLabel: msg`Export`,
position: 7,
position: 8,
Icon: IconFileExport,
accent: 'default',
isPinned: false,
@@ -225,7 +259,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: SingleRecordActionKeys.DELETE,
label: msg`Delete`,
shortLabel: msg`Delete`,
position: 8,
position: 9,
Icon: IconTrash,
accent: 'default',
isPinned: true,
@@ -252,7 +286,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: MultipleRecordsActionKeys.DELETE,
label: msg`Delete records`,
shortLabel: msg`Delete`,
position: 9,
position: 10,
Icon: IconTrash,
accent: 'default',
isPinned: true,
@@ -277,7 +311,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.SEE_DELETED_RECORDS,
label: msg`See deleted records`,
shortLabel: msg`Deleted records`,
position: 10,
position: 11,
Icon: IconRotate2,
accent: 'default',
isPinned: false,
@@ -292,7 +326,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.CREATE_NEW_VIEW,
label: msg`Create View`,
shortLabel: msg`Create View`,
position: 11,
position: 12,
Icon: IconLayout,
accent: 'default',
isPinned: false,
@@ -307,7 +341,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.HIDE_DELETED_RECORDS,
label: msg`Hide deleted records`,
shortLabel: msg`Hide deleted`,
position: 12,
position: 13,
Icon: IconEyeOff,
accent: 'default',
isPinned: false,
@@ -322,7 +356,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: SingleRecordActionKeys.DESTROY,
label: msg`Permanently destroy record`,
shortLabel: msg`Destroy`,
position: 13,
position: 14,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,
@@ -342,7 +376,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
scope: ActionScope.RecordSelection,
key: SingleRecordActionKeys.NAVIGATE_TO_PREVIOUS_RECORD,
label: msg`Navigate to previous record`,
position: 14,
position: 15,
isPinned: true,
Icon: IconChevronUp,
shouldBeRegistered: ({ isInRightDrawer }) => !isInRightDrawer,
@@ -354,7 +388,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
scope: ActionScope.RecordSelection,
key: SingleRecordActionKeys.NAVIGATE_TO_NEXT_RECORD,
label: msg`Navigate to next record`,
position: 15,
position: 16,
isPinned: true,
Icon: IconChevronDown,
shouldBeRegistered: ({ isInRightDrawer }) => !isInRightDrawer,
@@ -367,7 +401,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: MultipleRecordsActionKeys.DESTROY,
label: msg`Permanently destroy records`,
shortLabel: msg`Destroy`,
position: 16,
position: 17,
Icon: IconTrashX,
accent: 'danger',
isPinned: true,
@@ -393,7 +427,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: SingleRecordActionKeys.RESTORE,
label: msg`Restore record`,
shortLabel: msg`Restore`,
position: 17,
position: 18,
Icon: IconRefresh,
accent: 'default',
isPinned: true,
@@ -422,7 +456,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: MultipleRecordsActionKeys.RESTORE,
label: msg`Restore records`,
shortLabel: msg`Restore`,
position: 18,
position: 19,
Icon: IconRefresh,
accent: 'default',
isPinned: true,
@@ -448,7 +482,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_WORKFLOWS,
label: msg`Go to workflows`,
shortLabel: msg`See workflows`,
position: 19,
position: 20,
Icon: IconSettingsAutomation,
accent: 'default',
isPinned: false,
@@ -480,7 +514,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_PEOPLE,
label: msg`Go to People`,
shortLabel: msg`People`,
position: 20,
position: 21,
Icon: IconUser,
isPinned: false,
availableOn: [
@@ -511,7 +545,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_COMPANIES,
label: msg`Go to Companies`,
shortLabel: msg`Companies`,
position: 21,
position: 22,
Icon: IconBuildingSkyscraper,
isPinned: false,
availableOn: [
@@ -542,7 +576,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_OPPORTUNITIES,
label: msg`Go to Opportunities`,
shortLabel: msg`Opportunities`,
position: 22,
position: 23,
Icon: IconTargetArrow,
isPinned: false,
availableOn: [
@@ -575,7 +609,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_SETTINGS,
label: msg`Go to Settings`,
shortLabel: msg`Settings`,
position: 23,
position: 24,
Icon: IconSettings,
isPinned: false,
availableOn: [
@@ -601,7 +635,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_TASKS,
label: msg`Go to Tasks`,
shortLabel: msg`Tasks`,
position: 24,
position: 25,
Icon: IconCheckbox,
isPinned: false,
availableOn: [
@@ -632,7 +666,7 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record<
key: NoSelectionRecordActionKeys.GO_TO_NOTES,
label: msg`Go to Notes`,
shortLabel: msg`Notes`,
position: 25,
position: 26,
Icon: IconCheckbox,
isPinned: false,
availableOn: [
@@ -1,6 +1,7 @@
export enum MultipleRecordsActionKeys {
DELETE = 'delete-multiple-records',
EXPORT = 'export-multiple-records',
MERGE = 'merge-multiple-records',
DESTROY = 'destroy-multiple-records',
RESTORE = 'restore-multiple-records',
}