From 78d39294ef8c3b9fa2e05e0735d532b0d9b76b31 Mon Sep 17 00:00:00 2001
From: Etienne <45695613+etiennejouan@users.noreply.github.com>
Date: Wed, 18 Jun 2025 12:13:24 +0200
Subject: [PATCH] Import - Increase record import limit (#12627)
closes https://github.com/twentyhq/twenty/issues/11980
---
.../activities/hooks/useCreateActivityInDB.ts | 4 +-
.../__tests__/useCreateManyRecords.test.tsx | 9 +-
.../hooks/useBatchCreateManyRecords.ts | 106 ++++++++++++++++++
.../hooks/useCreateManyRecords.ts | 27 ++++-
...penObjectRecordsSpreadsheetImportDialog.ts | 25 ++++-
.../components/StepNavigationButton.tsx | 33 +++---
...SpreadsheetImportCreateRecordsBatchSize.ts | 1 +
.../SpreadsheetMaxRecordImportCapacity.ts | 2 +-
.../__tests__/useSpreadsheetImport.test.tsx | 2 +
.../hooks/useHideStepBar.ts | 23 ++++
.../hooks/useOpenSpreadsheetImportDialog.ts | 1 +
.../components/SpreadsheetImportProvider.tsx | 2 +
...dsheetImportCreatedRecordsProgressState.ts | 6 +
.../states/spreadsheetImportDialogState.ts | 4 +-
.../steps/components/ImportDataStep.tsx | 64 +++++++++++
.../MatchColumnsStep/MatchColumnsStep.tsx | 6 +-
.../SelectHeaderStep/SelectHeaderStep.tsx | 4 +-
.../SelectSheetStep/SelectSheetStep.tsx | 6 +-
.../components/SpreadsheetImportStepper.tsx | 9 +-
.../SpreadsheetImportStepperContainer.tsx | 24 ++--
.../UploadStep/components/DropZone.tsx | 7 +-
.../ValidationStep/ValidationStep.tsx | 7 +-
.../steps/types/SpreadsheetImportStep.ts | 4 +
.../steps/types/SpreadsheetImportStepType.ts | 1 +
.../types/SpreadsheetImportDialogOptions.ts | 2 +
.../internal/usePersistViewGroupRecords.ts | 6 +-
26 files changed, 331 insertions(+), 54 deletions(-)
create mode 100644 packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts
create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize.ts
create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/hooks/useHideStepBar.ts
create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState.ts
create mode 100644 packages/twenty-front/src/modules/spreadsheet-import/steps/components/ImportDataStep.tsx
diff --git a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts
index 1f0b3958c..4278ef7cf 100644
--- a/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts
+++ b/packages/twenty-front/src/modules/activities/hooks/useCreateActivityInDB.ts
@@ -69,7 +69,9 @@ export const useCreateActivityInDB = ({
activityToCreate.noteTargets ?? activityToCreate.taskTargets ?? [];
if (isNonEmptyArray(activityTargetsToCreate)) {
- await createManyActivityTargets(activityTargetsToCreate);
+ await createManyActivityTargets({
+ recordsToCreate: activityTargetsToCreate,
+ });
}
const activityTargetsConnection = getRecordConnectionFromRecords({
diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx
index 1523b99c1..6b76c9494 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx
+++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx
@@ -76,7 +76,9 @@ describe('useCreateManyRecords', () => {
);
await act(async () => {
- const res = await result.current.createManyRecords(input);
+ const res = await result.current.createManyRecords({
+ recordsToCreate: input,
+ });
expect(res).toEqual(response);
});
@@ -96,7 +98,10 @@ describe('useCreateManyRecords', () => {
);
await act(async () => {
- const res = await result.current.createManyRecords(input, true);
+ const res = await result.current.createManyRecords({
+ recordsToCreate: input,
+ upsert: true,
+ });
expect(res).toEqual(response);
});
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts
new file mode 100644
index 000000000..f20d9c5fb
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts
@@ -0,0 +1,106 @@
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
+import {
+ useCreateManyRecords,
+ useCreateManyRecordsProps,
+} from '@/object-record/hooks/useCreateManyRecords';
+import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
+import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { ApolloError } from '@apollo/client';
+import { t } from '@lingui/core/macro';
+import { formatNumber } from '~/utils/format/number';
+
+export const useBatchCreateManyRecords = <
+ CreatedObjectRecord extends ObjectRecord = ObjectRecord,
+>({
+ objectNameSingular,
+ recordGqlFields,
+ skipPostOptimisticEffect = false,
+ shouldMatchRootQueryFilter,
+ mutationBatchSize = DEFAULT_MUTATION_BATCH_SIZE,
+ setBatchedRecordsCount,
+ abortController,
+}: useCreateManyRecordsProps & {
+ mutationBatchSize?: number;
+ setBatchedRecordsCount?: (count: number) => void;
+ abortController?: AbortController;
+}) => {
+ const { createManyRecords } = useCreateManyRecords({
+ objectNameSingular,
+ recordGqlFields,
+ skipPostOptimisticEffect,
+ shouldMatchRootQueryFilter,
+ shouldRefetchAggregateQueries: false,
+ });
+
+ const { objectMetadataItem } = useObjectMetadataItem({
+ objectNameSingular,
+ });
+
+ const { refetchAggregateQueries } = useRefetchAggregateQueries({
+ objectMetadataNamePlural: objectMetadataItem.namePlural,
+ });
+
+ const { enqueueSnackBar } = useSnackBar();
+
+ const batchCreateManyRecords = async ({
+ recordsToCreate,
+ upsert,
+ }: {
+ recordsToCreate: Partial[];
+ upsert?: boolean;
+ }) => {
+ const numberOfBatches = Math.ceil(
+ recordsToCreate.length / mutationBatchSize,
+ );
+
+ setBatchedRecordsCount?.(0);
+
+ const allCreatedRecords = [];
+ let createdRecordsCount = 0;
+ try {
+ for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) {
+ const batchedRecordsToCreate = recordsToCreate.slice(
+ batchIndex * mutationBatchSize,
+ (batchIndex + 1) * mutationBatchSize,
+ );
+
+ createdRecordsCount =
+ batchIndex + 1 === numberOfBatches
+ ? recordsToCreate.length
+ : (batchIndex + 1) * mutationBatchSize;
+
+ const createdRecords = await createManyRecords({
+ recordsToCreate: batchedRecordsToCreate,
+ upsert,
+ abortController,
+ });
+
+ setBatchedRecordsCount?.(createdRecordsCount);
+ allCreatedRecords.push(...createdRecords);
+ }
+ } catch (error) {
+ if (error instanceof ApolloError && error.message.includes('aborted')) {
+ const formattedCreatedRecordsCount = formatNumber(createdRecordsCount);
+ enqueueSnackBar(
+ t`Record creation stopped. ${formattedCreatedRecordsCount} records created.`,
+ {
+ variant: SnackBarVariant.Warning,
+ duration: 5000,
+ },
+ );
+ } else {
+ throw error;
+ }
+ }
+
+ await refetchAggregateQueries();
+ return allCreatedRecords;
+ };
+
+ return {
+ batchCreateManyRecords,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts
index 4e1b75de6..d3c175ca9 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts
+++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts
@@ -32,11 +32,12 @@ type PartialObjectRecordWithOptionalId = Partial & {
id?: string;
};
-type useCreateManyRecordsProps = {
+export type useCreateManyRecordsProps = {
objectNameSingular: string;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
skipPostOptimisticEffect?: boolean;
shouldMatchRootQueryFilter?: boolean;
+ shouldRefetchAggregateQueries?: boolean;
};
export const useCreateManyRecords = <
@@ -46,6 +47,7 @@ export const useCreateManyRecords = <
recordGqlFields,
skipPostOptimisticEffect = false,
shouldMatchRootQueryFilter,
+ shouldRefetchAggregateQueries = true,
}: useCreateManyRecordsProps) => {
const apolloClient = useApolloClient();
@@ -76,10 +78,17 @@ export const useCreateManyRecords = <
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
- const createManyRecords = async (
- recordsToCreate: Partial[],
- upsert?: boolean,
- ) => {
+ type createManyRecordsProps = {
+ recordsToCreate: Partial[];
+ upsert?: boolean;
+ abortController?: AbortController;
+ };
+
+ const createManyRecords = async ({
+ recordsToCreate,
+ upsert,
+ abortController,
+ }: createManyRecordsProps) => {
const sanitizedCreateManyRecordsInput: PartialObjectRecordWithOptionalId[] =
[];
const recordOptimisticRecordsInput: PartialObjectRecordWithId[] = [];
@@ -169,6 +178,11 @@ export const useCreateManyRecords = <
data: sanitizedCreateManyRecordsInput,
upsert: upsert,
},
+ context: {
+ fetchOptions: {
+ signal: abortController?.signal,
+ },
+ },
update: (cache, { data }) => {
const records = data?.[mutationResponseField];
@@ -205,7 +219,8 @@ export const useCreateManyRecords = <
throw error;
});
- await refetchAggregateQueries();
+ if (shouldRefetchAggregateQueries) await refetchAggregateQueries();
+
return createdObjects.data?.[mutationResponseField] ?? [];
};
diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog.ts
index f48cfd232..a62cde8ac 100644
--- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog.ts
+++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/hooks/useOpenObjectRecordsSpreadsheetImportDialog.ts
@@ -1,12 +1,15 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
+import { useBatchCreateManyRecords } from '@/object-record/hooks/useBatchCreateManyRecords';
import { useBuildAvailableFieldsForImport } from '@/object-record/spreadsheet-import/hooks/useBuildAvailableFieldsForImport';
import { buildRecordFromImportedStructuredRow } from '@/object-record/spreadsheet-import/utils/buildRecordFromImportedStructuredRow';
import { spreadsheetImportFilterAvailableFieldMetadataItems } from '@/object-record/spreadsheet-import/utils/spreadsheetImportFilterAvailableFieldMetadataItems.ts';
+import { SpreadsheetImportCreateRecordsBatchSize } from '@/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize';
import { useOpenSpreadsheetImportDialog } from '@/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog';
+import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState';
import { SpreadsheetImportDialogOptions } from '@/spreadsheet-import/types';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { useSetRecoilState } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useOpenObjectRecordsSpreadsheetImportDialog = (
@@ -19,8 +22,17 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
objectNameSingular,
});
- const { createManyRecords } = useCreateManyRecords({
+ const setCreatedRecordsProgress = useSetRecoilState(
+ spreadsheetImportCreatedRecordsProgressState,
+ );
+
+ const abortController = new AbortController();
+
+ const { batchCreateManyRecords } = useBatchCreateManyRecords({
objectNameSingular,
+ mutationBatchSize: SpreadsheetImportCreateRecordsBatchSize,
+ setBatchedRecordsCount: setCreatedRecordsProgress,
+ abortController,
});
const { buildAvailableFieldsForImport } = useBuildAvailableFieldsForImport();
@@ -61,8 +73,10 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
});
try {
- const upsert = true;
- await createManyRecords(createInputs, upsert);
+ await batchCreateManyRecords({
+ recordsToCreate: createInputs,
+ upsert: true,
+ });
} catch (error: any) {
enqueueSnackBar(error?.message || 'Something went wrong', {
variant: SnackBarVariant.Error,
@@ -71,6 +85,9 @@ export const useOpenObjectRecordsSpreadsheetImportDialog = (
},
fields: availableFieldsForMatching,
availableFieldMetadataItems: availableFieldMetadataItemsToImport,
+ onAbortSubmit: () => {
+ abortController.abort();
+ },
});
};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/components/StepNavigationButton.tsx b/packages/twenty-front/src/modules/spreadsheet-import/components/StepNavigationButton.tsx
index fe7c12075..c24ffd30a 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/components/StepNavigationButton.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/components/StepNavigationButton.tsx
@@ -1,6 +1,7 @@
import styled from '@emotion/styled';
import { Modal } from '@/ui/layout/modal/components/Modal';
+import { t } from '@lingui/core/macro';
import { CircularProgressBar } from 'twenty-ui/feedback';
import { MainButton } from 'twenty-ui/input';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@@ -15,37 +16,41 @@ const StyledFooter = styled(Modal.Footer)`
`;
type StepNavigationButtonProps = {
- onClick: () => void;
- title: string;
+ onContinue?: () => void;
+ continueTitle?: string;
+ isContinueDisabled?: boolean;
isLoading?: boolean;
onBack?: () => void;
- isNextDisabled?: boolean;
+ backTitle?: string;
};
export const StepNavigationButton = ({
- onClick,
- title,
+ onContinue,
+ continueTitle = t`Continue`,
isLoading,
onBack,
- isNextDisabled = false,
+ backTitle = t`Back`,
+ isContinueDisabled = false,
}: StepNavigationButtonProps) => {
return (
{!isUndefinedOrNull(onBack) && (
)}
-
+ {!isUndefinedOrNull(onContinue) && (
+
+ )}
);
};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize.ts b/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize.ts
new file mode 100644
index 000000000..c8e8c2250
--- /dev/null
+++ b/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetImportCreateRecordsBatchSize.ts
@@ -0,0 +1 @@
+export const SpreadsheetImportCreateRecordsBatchSize = 500;
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetMaxRecordImportCapacity.ts b/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetMaxRecordImportCapacity.ts
index 6baa07a73..78a3a6057 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetMaxRecordImportCapacity.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/constants/SpreadsheetMaxRecordImportCapacity.ts
@@ -1 +1 @@
-export const SpreadsheetMaxRecordImportCapacity = 2000;
+export const SpreadsheetMaxRecordImportCapacity = 10000;
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImport.test.tsx b/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImport.test.tsx
index 7f835812b..e48fe1b38 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImport.test.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/hooks/__tests__/useSpreadsheetImport.test.tsx
@@ -60,6 +60,7 @@ describe('useSpreadsheetImport', () => {
);
expect(result.current.spreadsheetImportState).toStrictEqual({
isOpen: false,
+ isStepBarVisible: true,
options: null,
});
act(() => {
@@ -69,6 +70,7 @@ describe('useSpreadsheetImport', () => {
});
expect(result.current.spreadsheetImportState).toStrictEqual({
isOpen: true,
+ isStepBarVisible: true,
options: mockedSpreadsheetOptions,
});
});
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/hooks/useHideStepBar.ts b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useHideStepBar.ts
new file mode 100644
index 000000000..65084458e
--- /dev/null
+++ b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useHideStepBar.ts
@@ -0,0 +1,23 @@
+import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState';
+import { useRecoilCallback } from 'recoil';
+
+export const useHideStepBar = () => {
+ const hideStepBar = useRecoilCallback(
+ ({ set, snapshot }) =>
+ () => {
+ const isStepBarVisible = snapshot
+ .getLoadable(spreadsheetImportDialogState)
+ .getValue().isStepBarVisible;
+
+ if (isStepBarVisible) {
+ set(spreadsheetImportDialogState, (state) => ({
+ ...state,
+ isStepBarVisible: false,
+ }));
+ }
+ },
+ [],
+ );
+
+ return hideStepBar;
+};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog.ts b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog.ts
index 8ae064da7..f68cc75cd 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/hooks/useOpenSpreadsheetImportDialog.ts
@@ -15,6 +15,7 @@ export const useOpenSpreadsheetImportDialog = () => {
openModal(SPREADSHEET_IMPORT_MODAL_ID);
setSpreadSheetImport({
isOpen: true,
+ isStepBarVisible: true,
options,
});
};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImportProvider.tsx b/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImportProvider.tsx
index 9cd15d700..2b204a557 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImportProvider.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/provider/components/SpreadsheetImportProvider.tsx
@@ -43,8 +43,10 @@ export const SpreadsheetImportProvider = (
const { closeModal } = useModal();
const handleClose = () => {
+ spreadsheetImportDialog.options?.onAbortSubmit?.();
setSpreadsheetImportDialog({
isOpen: false,
+ isStepBarVisible: true,
options: null,
});
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState.ts b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState.ts
new file mode 100644
index 000000000..d6e1fd2e4
--- /dev/null
+++ b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState.ts
@@ -0,0 +1,6 @@
+import { createState } from 'twenty-ui/utilities';
+
+export const spreadsheetImportCreatedRecordsProgressState = createState({
+ key: 'spreadsheetImportCreatedRecordsProgressState',
+ defaultValue: 0,
+});
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts
index f2efc20c3..cd0d5c904 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/states/spreadsheetImportDialogState.ts
@@ -1,8 +1,9 @@
-import { SpreadsheetImportDialogOptions } from '../types';
import { createState } from 'twenty-ui/utilities';
+import { SpreadsheetImportDialogOptions } from '../types';
export type SpreadsheetImportDialogState = {
isOpen: boolean;
+ isStepBarVisible: boolean;
options: Omit, 'isOpen' | 'onClose'> | null;
};
@@ -12,6 +13,7 @@ export const spreadsheetImportDialogState = createState<
key: 'spreadsheetImportDialogState',
defaultValue: {
isOpen: false,
+ isStepBarVisible: true,
options: null,
},
});
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ImportDataStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ImportDataStep.tsx
new file mode 100644
index 000000000..7ba45e779
--- /dev/null
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ImportDataStep.tsx
@@ -0,0 +1,64 @@
+import { useRecoilValue } from 'recoil';
+
+import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
+import { useHideStepBar } from '@/spreadsheet-import/hooks/useHideStepBar';
+import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
+import { spreadsheetImportCreatedRecordsProgressState } from '@/spreadsheet-import/states/spreadsheetImportCreatedRecordsProgressState';
+import { Modal } from '@/ui/layout/modal/components/Modal';
+import styled from '@emotion/styled';
+import { t } from '@lingui/core/macro';
+import { Loader } from 'twenty-ui/feedback';
+import { formatNumber } from '~/utils/format/number';
+
+const StyledContent = styled(Modal.Content)`
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ padding: 0px;
+`;
+
+const StyledHeader = styled.span`
+ color: ${({ theme }) => theme.font.color.primary};
+ font-size: ${({ theme }) => theme.font.size.md};
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ margin-bottom: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledDescription = styled.span`
+ color: ${({ theme }) => theme.font.color.tertiary};
+ font-size: ${({ theme }) => theme.font.size.md};
+ font-weight: ${({ theme }) => theme.font.weight.regular};
+ margin-bottom: ${({ theme }) => theme.spacing(5)};
+`;
+
+type ImportDataStepProps = {
+ recordsToImportCount: number;
+};
+
+export const ImportDataStep = ({
+ recordsToImportCount,
+}: ImportDataStepProps) => {
+ const hideStepBar = useHideStepBar();
+ hideStepBar();
+
+ const { onClose } = useSpreadsheetImportInternal();
+ const spreadsheetImportCreatedRecordsProgress = useRecoilValue(
+ spreadsheetImportCreatedRecordsProgressState,
+ );
+
+ const formattedCreatedRecordsProgress = formatNumber(
+ spreadsheetImportCreatedRecordsProgress,
+ );
+ const formattedRecordsToImportCount = formatNumber(recordsToImportCount);
+
+ return (
+ <>
+
+ {t`Importing Data ...`}
+ {t`${formattedCreatedRecordsProgress} out of ${formattedRecordsToImportCount} records imported.`}
+
+
+
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
index 12bbcdf39..51d86cc32 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/MatchColumnsStep/MatchColumnsStep.tsx
@@ -279,14 +279,14 @@ export const MatchColumnsStep = ({
{
onBack?.();
setColumns([]);
}}
- isNextDisabled={!hasMatchedColumns}
+ isContinueDisabled={!hasMatchedColumns}
/>
>
);
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep.tsx
index 238ee79ed..b3dafae81 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectHeaderStep/SelectHeaderStep.tsx
@@ -114,9 +114,9 @@ export const SelectHeaderStep = ({
>
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep.tsx
index 7631366f1..2e2798aca 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SelectSheetStep/SelectSheetStep.tsx
@@ -11,8 +11,8 @@ import { mapWorkbook } from '@/spreadsheet-import/utils/mapWorkbook';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
-import { WorkBook } from 'xlsx-ugnis';
import { Radio, RadioGroup } from 'twenty-ui/input';
+import { WorkBook } from 'xlsx-ugnis';
const StyledContent = styled(Modal.Content)`
align-items: center;
@@ -116,10 +116,10 @@ export const SelectSheetStep = ({
handleOnContinue(value)}
+ onContinue={() => handleOnContinue(value)}
onBack={onBack}
isLoading={isLoading}
- title={t`Next Step`}
+ continueTitle={t`Next Step`}
/>
>
);
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepper.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepper.tsx
index 4ecaabd3f..f30b0590c 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepper.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepper.tsx
@@ -7,14 +7,15 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Modal } from '@/ui/layout/modal/components/Modal';
+import { ImportDataStep } from '@/spreadsheet-import/steps/components/ImportDataStep';
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
+import { CircularProgressBar } from 'twenty-ui/feedback';
import { MatchColumnsStep } from './MatchColumnsStep/MatchColumnsStep';
import { SelectHeaderStep } from './SelectHeaderStep/SelectHeaderStep';
import { SelectSheetStep } from './SelectSheetStep/SelectSheetStep';
import { UploadStep } from './UploadStep/UploadStep';
import { ValidationStep } from './ValidationStep/ValidationStep';
-import { CircularProgressBar } from 'twenty-ui/feedback';
const StyledProgressBarContainer = styled(Modal.Content)`
align-items: center;
@@ -128,6 +129,12 @@ export const SpreadsheetImportStepper = ({
}}
/>
);
+ case SpreadsheetImportStepType.importData:
+ return (
+
+ );
case SpreadsheetImportStepType.loading:
default:
return (
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx
index 2964b892d..76c54284e 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/SpreadsheetImportStepperContainer.tsx
@@ -6,8 +6,10 @@ import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpre
import { StepBar } from '@/ui/navigation/step-bar/components/StepBar';
import { useStepBar } from '@/ui/navigation/step-bar/hooks/useStepBar';
+import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
+import { useRecoilValue } from 'recoil';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { SpreadsheetImportStepper } from './SpreadsheetImportStepper';
@@ -26,6 +28,8 @@ const StyledHeader = styled(Modal.Header)`
export const SpreadsheetImportStepperContainer = () => {
const { t } = useLingui();
+ const spreadsheetImportDialog = useRecoilValue(spreadsheetImportDialogState);
+
const stepTitles = {
uploadStep: t`Upload File`,
matchColumnsStep: t`Match Columns`,
@@ -45,15 +49,17 @@ export const SpreadsheetImportStepperContainer = () => {
return (
<>
-
- {steps.map((key) => (
-
- ))}
-
+ {spreadsheetImportDialog.isStepBarVisible && (
+
+ {steps.map((key) => (
+
+ ))}
+
+ )}
>
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
index f5cc409de..d1ad453d2 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/components/DropZone.tsx
@@ -11,6 +11,7 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Trans, useLingui } from '@lingui/react/macro';
import { MainButton } from 'twenty-ui/input';
+import { formatNumber } from '~/utils/format/number';
const StyledContainer = styled.div`
align-items: center;
@@ -154,6 +155,10 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
const { t } = useLingui();
+ const formatSpreadsheetMaxRecordImportCapacity = formatNumber(
+ SpreadsheetMaxRecordImportCapacity,
+ );
+
return (
{
- {t`Max import capacity: ${SpreadsheetMaxRecordImportCapacity} records. Otherwise, consider splitting your file or using the API.`}{' '}
+ {t`Max import capacity: ${formatSpreadsheetMaxRecordImportCapacity} records. Otherwise, consider splitting your file or using the API.`}{' '}
{t`Download sample file.`}
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
index 78427ccd1..f2971c7ea 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/ValidationStep/ValidationStep.tsx
@@ -237,7 +237,8 @@ export const ValidationStep = ({
);
setCurrentStepState({
- type: SpreadsheetImportStepType.loading,
+ type: SpreadsheetImportStepType.importData,
+ recordsToImportCount: calculatedData.validStructuredRows.length,
});
await onSubmit(calculatedData, file);
@@ -321,9 +322,9 @@ export const ValidationStep = ({
>
);
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStep.ts b/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStep.ts
index 6e13bb902..22f2292c4 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStep.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStep.ts
@@ -27,4 +27,8 @@ export type SpreadsheetImportStep =
}
| {
type: SpreadsheetImportStepType.loading;
+ }
+ | {
+ type: SpreadsheetImportStepType.importData;
+ recordsToImportCount: number;
};
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStepType.ts b/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStepType.ts
index 9c2bf555d..da4764ede 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStepType.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/types/SpreadsheetImportStepType.ts
@@ -4,5 +4,6 @@ export enum SpreadsheetImportStepType {
selectHeader = 'selectHeader',
matchColumns = 'matchColumns',
validateData = 'validateData',
+ importData = 'importData',
loading = 'loading',
}
diff --git a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetImportDialogOptions.ts b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetImportDialogOptions.ts
index e46a22272..e308df5c6 100644
--- a/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetImportDialogOptions.ts
+++ b/packages/twenty-front/src/modules/spreadsheet-import/types/SpreadsheetImportDialogOptions.ts
@@ -35,6 +35,8 @@ export type SpreadsheetImportDialogOptions = {
validationResult: SpreadsheetImportImportValidationResult,
file: File,
) => Promise;
+ // Function called when user aborts the importing flow
+ onAbortSubmit?: () => void;
// Allows submitting with errors. Default: true
allowInvalidSubmit?: boolean;
// Theme configuration passed to underlying Chakra-UI
diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts
index eb155ff69..98668fab1 100644
--- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts
+++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewGroupRecords.ts
@@ -31,12 +31,12 @@ export const usePersistViewGroupRecords = () => {
({ viewGroupsToCreate, viewId }: CreateViewGroupRecordsArgs) => {
if (viewGroupsToCreate.length === 0) return;
- return createManyRecords(
- viewGroupsToCreate.map((viewGroup) => ({
+ return createManyRecords({
+ recordsToCreate: viewGroupsToCreate.map((viewGroup) => ({
...viewGroup,
viewId,
})),
- );
+ });
},
[createManyRecords],
);