@commercetools-frontend-extensions/operations 3.1.2 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,8 +18,8 @@ import _inherits from '@babel/runtime-corejs3/helpers/esm/inherits';
18
18
  import _wrapNativeSuper from '@babel/runtime-corejs3/helpers/esm/wrapNativeSuper';
19
19
  import { MC_API_PROXY_TARGETS } from '@commercetools-frontend/constants';
20
20
  import _everyInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/every';
21
- import _Array$isArray from '@babel/runtime-corejs3/core-js-stable/array/is-array';
22
21
  import { plural } from 'pluralize';
22
+ import _Array$isArray from '@babel/runtime-corejs3/core-js-stable/array/is-array';
23
23
  import _reduceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/reduce';
24
24
  import _flatMapInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/flat-map';
25
25
  import _mapInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/map';
@@ -27,13 +27,10 @@ import _Promise from '@babel/runtime-corejs3/core-js-stable/promise';
27
27
  import _findInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/find';
28
28
  import _includesInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/includes';
29
29
  import _someInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/some';
30
- import _Set from '@babel/runtime-corejs3/core-js-stable/set';
31
- import _findIndexInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/find-index';
32
- import _trimInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/trim';
33
- import _bindInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/bind';
34
30
  import Papa from 'papaparse';
35
31
  import _sliceInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/slice';
36
32
  import _JSON$stringify from '@babel/runtime-corejs3/core-js-stable/json/stringify';
33
+ import _bindInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/bind';
37
34
  import _startsWithInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/starts-with';
38
35
  import _setTimeout from '@babel/runtime-corejs3/core-js-stable/set-timeout';
39
36
  import _concatInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/concat';
@@ -331,14 +328,9 @@ function assertProcessFileImportJobResponse(maybeResponse) {
331
328
  throw new Error('Invalid Process File Import Job response');
332
329
  }
333
330
  function assertListFileImportJobsResponse(maybeResponse) {
334
- if (!_Array$isArray(maybeResponse)) {
335
- throw new Error('Invalid List File Import Jobs response: expected an array');
336
- }
337
- if (maybeResponse.length > 0) {
338
- const requiredFields = ['id', 'fileName', 'importContainerKey', 'state'];
339
- if (!hasRequiredFields(maybeResponse[0], requiredFields)) {
340
- throw new Error('Invalid List File Import Jobs response: missing required fields');
341
- }
331
+ const requiredFields = ['results', 'total', 'limit', 'offset', 'count'];
332
+ if (!hasRequiredFields(maybeResponse, requiredFields)) {
333
+ throw new Error('Invalid List File Import Jobs response: missing required fields');
342
334
  }
343
335
  }
344
336
 
@@ -451,7 +443,7 @@ function shouldContinuePollingForImportValidation(job) {
451
443
  }
452
444
 
453
445
  function ownKeys$8(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
454
- function _objectSpread$8(e) { for (var r = 1; r < arguments.length; r++) { var _context6, _context7; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context6 = ownKeys$8(Object(t), !0)).call(_context6, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context7 = ownKeys$8(Object(t))).call(_context7, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
446
+ function _objectSpread$8(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context2 = ownKeys$8(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context3 = ownKeys$8(Object(t))).call(_context3, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
455
447
 
456
448
  /**
457
449
  * Convert megabytes to bytes
@@ -570,44 +562,6 @@ const countJsonFileItems = async file => {
570
562
  }
571
563
  };
572
564
 
573
- /**
574
- * Count unique resources in a CSV file by counting unique values in the "key" column.
575
- * A single resource can span multiple rows (when it has array fields like variants, assets...),
576
- * so we count unique keys rather than rows.
577
- * @param file The CSV file to process
578
- * @returns A promise that resolves to the number of unique resources
579
- */
580
- const countUniqueResourcesInCsv = file => {
581
- return new _Promise(resolve => {
582
- const uniqueKeys = new _Set();
583
- let keyColumnIndex = -1;
584
- let isFirstRow = true;
585
- Papa.parse(file, {
586
- step: _ref2 => {
587
- var _context4, _context5;
588
- let data = _ref2.data;
589
- if (!_Array$isArray(data)) return;
590
- if (isFirstRow) {
591
- keyColumnIndex = _findIndexInstanceProperty(data).call(data, col => {
592
- var _context2, _context3;
593
- return ((_context2 = col?.toLowerCase()) == null ? void 0 : _bindInstanceProperty(_context3 = Function.call).call(_context3, _trimInstanceProperty(_context2), _context2))?.() === 'key';
594
- });
595
- isFirstRow = false;
596
- return;
597
- }
598
- if (keyColumnIndex === -1) return;
599
- const keyValue = ((_context4 = data[keyColumnIndex]) == null ? void 0 : _bindInstanceProperty(_context5 = Function.call).call(_context5, _trimInstanceProperty(_context4), _context4))?.();
600
- if (keyValue) {
601
- uniqueKeys.add(keyValue);
602
- }
603
- },
604
- complete: () => {
605
- resolve(uniqueKeys.size);
606
- }
607
- });
608
- });
609
- };
610
-
611
565
  /**
612
566
  * Map file upload errors to upload file error rows with unique IDs
613
567
  * @param uploadFileErrors Array of file upload errors
@@ -1120,10 +1074,48 @@ async function listFileImportJobs(_ref6) {
1120
1074
  assertListFileImportJobsResponse(response);
1121
1075
  return response;
1122
1076
  }
1077
+ /**
1078
+ * Gets the file import job info for an import container
1079
+ *
1080
+ * For the new file import job flow, import operations are created incrementally
1081
+ * during the 'initialising' state. The import summary total
1082
+ * reflects only the operations created so far, which can be misleading
1083
+ *
1084
+ * This helper fetches the file import job (if it exists) to get:
1085
+ * - The true total from the job summary (known from initial CSV validation)
1086
+ * - Whether the job is still initializing (creating import operations)
1087
+ *
1088
+ * @returns Job info if found, null otherwise
1089
+ */
1090
+ async function getFileImportJobInfoForContainer(_ref7) {
1091
+ let projectKey = _ref7.projectKey,
1092
+ importContainerKey = _ref7.importContainerKey;
1093
+ try {
1094
+ const response = await listFileImportJobs({
1095
+ projectKey,
1096
+ importContainerKey,
1097
+ limit: 1
1098
+ });
1099
+ if (response.results.length > 0 && response.results[0].summary?.total != null) {
1100
+ var _context;
1101
+ const job = response.results[0];
1102
+ const isInitializing = _includesInstanceProperty(_context = ['processing', 'initialising']).call(_context, job.state);
1103
+ return {
1104
+ total: job.summary.total,
1105
+ isInitializing
1106
+ };
1107
+ }
1108
+ return null;
1109
+ } catch {
1110
+ // Job might not exist (old flow)
1111
+ return null;
1112
+ }
1113
+ }
1123
1114
 
1124
1115
  function ownKeys$6(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = _filterInstanceProperty(o).call(o, function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
1125
1116
  function _objectSpread$6(e) { for (var r = 1; r < arguments.length; r++) { var _context2, _context3; var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? _forEachInstanceProperty(_context2 = ownKeys$6(Object(t), !0)).call(_context2, function (r) { _defineProperty(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : _forEachInstanceProperty(_context3 = ownKeys$6(Object(t))).call(_context3, function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
1126
1117
  function getImportState(importSummary) {
1118
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1127
1119
  const processing = importSummary.states.processing > 0;
1128
1120
  if (processing) return ImportStates.Processing;
1129
1121
  const waitForUnresolvedReferences = importSummary.states.waitForMasterVariant > 0 || importSummary.states.unresolved > 0;
@@ -1132,6 +1124,11 @@ function getImportState(importSummary) {
1132
1124
  if (partiallyCompleted) return ImportStates.PartiallyCompleted;
1133
1125
  const noRunning = importSummary.total === 0;
1134
1126
  if (noRunning) return ImportStates.NoRunningImports;
1127
+
1128
+ // For the new flow: job is actively creating import operations (to show as Processing even if no operations exist yet)
1129
+ if (options.isJobInitializing) {
1130
+ return ImportStates.Processing;
1131
+ }
1135
1132
  const successfullyCompleted = importSummary.states.imported === importSummary.total || importSummary.states.deleted === importSummary.total;
1136
1133
  if (successfullyCompleted) return ImportStates.SuccessfullyCompleted;
1137
1134
  const failed = importSummary.states.rejected + importSummary.states.validationFailed === importSummary.total;
@@ -1280,12 +1277,33 @@ async function cancelImportContainerByKey(_ref8) {
1280
1277
  return response;
1281
1278
  }
1282
1279
  async function importContainerToContainerDetails(projectKey, importContainer) {
1283
- const importSummary = await fetchImportSummary({
1280
+ let importSummary = await fetchImportSummary({
1284
1281
  projectKey,
1285
1282
  importContainerKey: importContainer.key
1286
1283
  });
1287
- const importState = getImportState(importSummary);
1288
1284
  const isFileUploadImport = checkIfFileUploadImport(importContainer.tags);
1285
+
1286
+ // For the new file import job flow the import operations are created incrementally
1287
+ // The import summary total reflects only operations created so far
1288
+ // Only override total when job is actively initializing (creating operations)
1289
+ let isJobInitializing = false;
1290
+ if (isFileUploadImport) {
1291
+ const jobInfo = await getFileImportJobInfoForContainer({
1292
+ projectKey,
1293
+ importContainerKey: importContainer.key
1294
+ });
1295
+ if (jobInfo !== null) {
1296
+ isJobInitializing = jobInfo.isInitializing;
1297
+ if (isJobInitializing || importSummary.total > 0) {
1298
+ importSummary = _objectSpread$6(_objectSpread$6({}, importSummary), {}, {
1299
+ total: jobInfo.total
1300
+ });
1301
+ }
1302
+ }
1303
+ }
1304
+ const importState = getImportState(importSummary, {
1305
+ isJobInitializing
1306
+ });
1289
1307
  return {
1290
1308
  importContainer: importContainer,
1291
1309
  importState,
@@ -2563,13 +2581,16 @@ const UploadingModal = _ref => {
2563
2581
  label: cancelLabel,
2564
2582
  onClick: onCancel
2565
2583
  })]
2566
- }), /*#__PURE__*/jsx(ProgressBar, {
2567
- barWidth: "scale",
2568
- height: "10",
2569
- progress: progress
2570
- }), statusMessage && /*#__PURE__*/jsx(Text.Detail, {
2571
- tone: "tertiary",
2572
- children: statusMessage
2584
+ }), /*#__PURE__*/jsxs(Spacings.Stack, {
2585
+ scale: "xs",
2586
+ children: [/*#__PURE__*/jsx(ProgressBar, {
2587
+ barWidth: "scale",
2588
+ height: "10",
2589
+ progress: progress
2590
+ }), statusMessage && /*#__PURE__*/jsx(Text.Detail, {
2591
+ tone: "tertiary",
2592
+ children: statusMessage
2593
+ })]
2573
2594
  })]
2574
2595
  })
2575
2596
  });
@@ -2992,7 +3013,6 @@ const useFileUpload = _ref2 => {
2992
3013
  setProgress = _React$useState4[1];
2993
3014
  const _React$useState5 = React.useState({
2994
3015
  processed: 0,
2995
- total: 0,
2996
3016
  isValidating: false
2997
3017
  }),
2998
3018
  _React$useState6 = _slicedToArray(_React$useState5, 2),
@@ -3009,7 +3029,6 @@ const useFileUpload = _ref2 => {
3009
3029
  setProgress(0);
3010
3030
  setValidationProgress({
3011
3031
  processed: 0,
3012
- total: 0,
3013
3032
  isValidating: false
3014
3033
  });
3015
3034
  }, []);
@@ -3018,7 +3037,6 @@ const useFileUpload = _ref2 => {
3018
3037
  setProgress(0);
3019
3038
  try {
3020
3039
  if (useJobBasedFlow) {
3021
- const totalResources = config.skipValidationPolling ? 0 : await countUniqueResourcesInCsv(config.file);
3022
3040
  await jobUpload.upload({
3023
3041
  file: config.file,
3024
3042
  resourceType: config.resourceType,
@@ -3065,7 +3083,6 @@ const useFileUpload = _ref2 => {
3065
3083
  try {
3066
3084
  setValidationProgress({
3067
3085
  processed: 0,
3068
- total: totalResources,
3069
3086
  isValidating: true
3070
3087
  });
3071
3088
  const validatedJob = await pollJobUntilValidated({
@@ -3079,7 +3096,6 @@ const useFileUpload = _ref2 => {
3079
3096
  const processed = job.summary?.total ?? 0;
3080
3097
  setValidationProgress({
3081
3098
  processed,
3082
- total: totalResources,
3083
3099
  isValidating: true
3084
3100
  });
3085
3101
  config.onValidationProgress?.(job);
@@ -3109,7 +3125,6 @@ const useFileUpload = _ref2 => {
3109
3125
  setIsUploading(false);
3110
3126
  setValidationProgress({
3111
3127
  processed: 0,
3112
- total: 0,
3113
3128
  isValidating: false
3114
3129
  });
3115
3130
  config.onSuccess(result);
@@ -3192,4 +3207,4 @@ const useFileUpload = _ref2 => {
3192
3207
  };
3193
3208
  };
3194
3209
 
3195
- export { ActiveDragDropArea, COLUMN_DELIMITERS, CT_API_DOCS_URL, DELIMITERS, DisabledDropArea, DropAreaWrapper, EnabledDropArea, FILE_IMPORT_JOB_POLLING_INTERVAL, FileDropArea, FileDroppedArea, FileIcon, HttpError, IMPORT_LEGACY_MAX_FILE_SIZE_MB, IMPORT_LEGACY_MAX_ROW_COUNT, IMPORT_MAX_FILE_SIZE_MB, IMPORT_MAX_ITEM_COUNT, IMPORT_TAG_KEYS, IMPORT_TAG_VALUES, ImportStates, InfoBox, InvalidResponseError, LockIcon, NoResourcesToExportError, PollingAbortedError, ProjectKeyNotAvailableError, QueryPredicateError, RESOURCE_TYPE_DOCUMENTATION_LINKS, RESOURCE_TYPE_TEMPLATE_DOWNLOAD_LINKS, TAG_KEY_SOURCE_FILE_UPLOAD, UnexpectedColumnError, UnexpectedOperationStateError, UnexpectedResourceTypeError, UploadSeparator, UploadSettings, UploadingModal, allAutomatedImportOperations, allAutomatedImportOperationsResponse, allFileUploadImportOperations, allFileUploadImportOperationsResponse, appendCsvOrJsonExtensionIfAbsent, assertCancelContainerResponse, assertExportOperationsDownloadFileResponse, assertFileImportJob, assertFileImportJobRecordsResponse, assertFileUploadResponse, assertImportContainer, assertImportContainerPagedResponse, assertImportOperationPagedResponse, assertImportSummary, assertListFileImportJobsResponse, assertPaginatedExportOperationResponse, assertProcessFileImportJobResponse, assertProcessFileResponse, assertResourceType, automatedImportContainerKey, automatedImports, cancelImportContainerByKey, checkIfFileUploadImport, convertFileSizeToKB, countJsonFileItems, countUniqueResourcesInCsv, createFileImportJob, createImportContainerForFileUpload, decodeFileNameFromImportContainerKey, deleteFileImportJob, deleteImportContainer, dropAreaStyles, encodeFileNameWithTimestampToContainerKey, exportOperationsCompleted, exportOperationsProcessing, extractErrorDescriptionFromValidationMessage, fetchExportOperations, fetchImportContainerByKey, fetchImportContainerDetails, fetchImportContainers, fetchImportOperations, fetchImportSummaries, fetchImportSummary, fetchUsingXhr, fetcher, fileUploadImportContainerKey, fileUploadMissingKeysResponse, formatErrorCode, formatKeys, formatQueryString, getCreateImportContainerURL, getDeleteImportContainerURL, getExportOperationsURL, getFileImportJob, getFileImportJobByIdURL, getFileImportJobDeleteURL, getFileImportJobFileType, getFileImportJobProcessURL, getFileImportJobRecords, getFileImportJobRecordsURL, getFileImportJobsListURL, getFileImportJobsURL, getFileUploadErrorsCount, getFileUploadURL, getImportContainerByKeyURL, getImportContainerTasksURL, getImportContainersURL, getImportOperationsURL, getImportState, getImportSummaryURL, getMissingRequiredFields, getProccessFileURL, getRowCount, getValidatedColumns, hasImportJobStartedProcessing, hasOwnProperty, hasRequiredFields, hasSingleKeyColumn, importContainers, importStatesMap, importsSummaries, invalidFileImportJobRecordsResponse, invalidFileImportJobValidated, invalidFileUploadResponse, isAbortError, isError, isImportJobInitializing, isImportJobProcessing, isImportJobQueued, isImportJobReady, isImportJobRejected, isImportJobTerminal, isImportJobValidated, isResourceType, listFileImportJobs, manualImports, mapFileUploadErrorsToUploadFileErrorRows, mapFormikErrors, mapUploadFileErrorsResponseToUploadFileErrorRows, pollJobUntilProcessing, pollJobUntilValidated, processFileImportJob, processFileImportJobResponse, processUploadedFile, shouldContinuePollingForImportValidation, successfulAutomatedImportOperations, successfulAutomatedImportOperationsResponse, successfulFileUploadImportOperations, successfulFileUploadImportOperationsResponse, toBytes, toImportApiResourceType, uploadFileForImport, useFetchExportOperations, useFetchFileImportJob, useFetchFileImportJobRecords, useFetchImportContainerDetails, useFetchImportOperations, useFetchImportSummaries, useFileImportJobUpload, useFileUpload, useImportContainerUpload, validFileImportJobProcessing, validFileImportJobQueued, validFileImportJobRecordsResponse, validFileImportJobValidated, validFileUploadResponse, validProcessFileResponse, validateDelimiter };
3210
+ export { ActiveDragDropArea, COLUMN_DELIMITERS, CT_API_DOCS_URL, DELIMITERS, DisabledDropArea, DropAreaWrapper, EnabledDropArea, FILE_IMPORT_JOB_POLLING_INTERVAL, FileDropArea, FileDroppedArea, FileIcon, HttpError, IMPORT_LEGACY_MAX_FILE_SIZE_MB, IMPORT_LEGACY_MAX_ROW_COUNT, IMPORT_MAX_FILE_SIZE_MB, IMPORT_MAX_ITEM_COUNT, IMPORT_TAG_KEYS, IMPORT_TAG_VALUES, ImportStates, InfoBox, InvalidResponseError, LockIcon, NoResourcesToExportError, PollingAbortedError, ProjectKeyNotAvailableError, QueryPredicateError, RESOURCE_TYPE_DOCUMENTATION_LINKS, RESOURCE_TYPE_TEMPLATE_DOWNLOAD_LINKS, TAG_KEY_SOURCE_FILE_UPLOAD, UnexpectedColumnError, UnexpectedOperationStateError, UnexpectedResourceTypeError, UploadSeparator, UploadSettings, UploadingModal, allAutomatedImportOperations, allAutomatedImportOperationsResponse, allFileUploadImportOperations, allFileUploadImportOperationsResponse, appendCsvOrJsonExtensionIfAbsent, assertCancelContainerResponse, assertExportOperationsDownloadFileResponse, assertFileImportJob, assertFileImportJobRecordsResponse, assertFileUploadResponse, assertImportContainer, assertImportContainerPagedResponse, assertImportOperationPagedResponse, assertImportSummary, assertListFileImportJobsResponse, assertPaginatedExportOperationResponse, assertProcessFileImportJobResponse, assertProcessFileResponse, assertResourceType, automatedImportContainerKey, automatedImports, cancelImportContainerByKey, checkIfFileUploadImport, convertFileSizeToKB, countJsonFileItems, createFileImportJob, createImportContainerForFileUpload, decodeFileNameFromImportContainerKey, deleteFileImportJob, deleteImportContainer, dropAreaStyles, encodeFileNameWithTimestampToContainerKey, exportOperationsCompleted, exportOperationsProcessing, extractErrorDescriptionFromValidationMessage, fetchExportOperations, fetchImportContainerByKey, fetchImportContainerDetails, fetchImportContainers, fetchImportOperations, fetchImportSummaries, fetchImportSummary, fetchUsingXhr, fetcher, fileUploadImportContainerKey, fileUploadMissingKeysResponse, formatErrorCode, formatKeys, formatQueryString, getCreateImportContainerURL, getDeleteImportContainerURL, getExportOperationsURL, getFileImportJob, getFileImportJobByIdURL, getFileImportJobDeleteURL, getFileImportJobFileType, getFileImportJobInfoForContainer, getFileImportJobProcessURL, getFileImportJobRecords, getFileImportJobRecordsURL, getFileImportJobsListURL, getFileImportJobsURL, getFileUploadErrorsCount, getFileUploadURL, getImportContainerByKeyURL, getImportContainerTasksURL, getImportContainersURL, getImportOperationsURL, getImportState, getImportSummaryURL, getMissingRequiredFields, getProccessFileURL, getRowCount, getValidatedColumns, hasImportJobStartedProcessing, hasOwnProperty, hasRequiredFields, hasSingleKeyColumn, importContainers, importStatesMap, importsSummaries, invalidFileImportJobRecordsResponse, invalidFileImportJobValidated, invalidFileUploadResponse, isAbortError, isError, isImportJobInitializing, isImportJobProcessing, isImportJobQueued, isImportJobReady, isImportJobRejected, isImportJobTerminal, isImportJobValidated, isResourceType, listFileImportJobs, manualImports, mapFileUploadErrorsToUploadFileErrorRows, mapFormikErrors, mapUploadFileErrorsResponseToUploadFileErrorRows, pollJobUntilProcessing, pollJobUntilValidated, processFileImportJob, processFileImportJobResponse, processUploadedFile, shouldContinuePollingForImportValidation, successfulAutomatedImportOperations, successfulAutomatedImportOperationsResponse, successfulFileUploadImportOperations, successfulFileUploadImportOperationsResponse, toBytes, toImportApiResourceType, uploadFileForImport, useFetchExportOperations, useFetchFileImportJob, useFetchFileImportJobRecords, useFetchImportContainerDetails, useFetchImportOperations, useFetchImportSummaries, useFileImportJobUpload, useFileUpload, useImportContainerUpload, validFileImportJobProcessing, validFileImportJobQueued, validFileImportJobRecordsResponse, validFileImportJobValidated, validFileUploadResponse, validProcessFileResponse, validateDelimiter };
@@ -5,3 +5,24 @@ export declare function getFileImportJobRecords({ projectKey, importContainerKey
5
5
  export declare function processFileImportJob({ projectKey, resourceType, importContainerKey, jobId, action, }: ProcessFileImportJobParameters): Promise<ProcessFileImportJobResponse>;
6
6
  export declare function deleteFileImportJob({ projectKey, importContainerKey, jobId, }: DeleteFileImportJobParameters): Promise<void>;
7
7
  export declare function listFileImportJobs({ projectKey, importContainerKey, limit, offset, }: ListFileImportJobsParameters): Promise<ListFileImportJobsResponse>;
8
+ export type FileImportJobInfo = {
9
+ total: number;
10
+ isInitializing: boolean;
11
+ };
12
+ /**
13
+ * Gets the file import job info for an import container
14
+ *
15
+ * For the new file import job flow, import operations are created incrementally
16
+ * during the 'initialising' state. The import summary total
17
+ * reflects only the operations created so far, which can be misleading
18
+ *
19
+ * This helper fetches the file import job (if it exists) to get:
20
+ * - The true total from the job summary (known from initial CSV validation)
21
+ * - Whether the job is still initializing (creating import operations)
22
+ *
23
+ * @returns Job info if found, null otherwise
24
+ */
25
+ export declare function getFileImportJobInfoForContainer({ projectKey, importContainerKey, }: {
26
+ projectKey: string;
27
+ importContainerKey: string;
28
+ }): Promise<FileImportJobInfo | null>;
@@ -1,6 +1,9 @@
1
1
  import type { ImportContainer, ImportContainerPagedResponse } from '@commercetools/importapi-sdk';
2
2
  import { ImportStates, type ImportContainerQueryParams, type CancelContainerResponse, type ImportSummary, type ExtendedImportContainerDraft, type ImportContainerDetails, type ImportSummaries } from "../@types/index.js";
3
- export declare function getImportState(importSummary: ImportSummary): ImportStates;
3
+ type GetImportStateOptions = {
4
+ isJobInitializing?: boolean;
5
+ };
6
+ export declare function getImportState(importSummary: ImportSummary, options?: GetImportStateOptions): ImportStates;
4
7
  export declare function createImportContainerForFileUpload({ importContainerDraft, projectKey, }: {
5
8
  importContainerDraft: ExtendedImportContainerDraft;
6
9
  projectKey: string;
@@ -33,3 +36,4 @@ export declare function cancelImportContainerByKey({ projectKey, importContainer
33
36
  projectKey: string;
34
37
  importContainerKey: string;
35
38
  }): Promise<CancelContainerResponse>;
39
+ export {};
@@ -2,7 +2,6 @@ import type { ResourceTypeId } from '@commercetools/importapi-sdk';
2
2
  import type { ExtendedImportContainerDraft, FileUploadResult, FileImportJob } from "../@types/index.js";
3
3
  export type ValidationProgress = {
4
4
  processed: number;
5
- total: number;
6
5
  isValidating: boolean;
7
6
  };
8
7
  export type FileUploadConfig = {
@@ -93,7 +93,13 @@ export interface ListFileImportJobsParameters {
93
93
  limit?: number;
94
94
  offset?: number;
95
95
  }
96
- export type ListFileImportJobsResponse = FileImportJob[];
96
+ export interface ListFileImportJobsResponse {
97
+ results: FileImportJob[];
98
+ total: number;
99
+ limit: number;
100
+ offset: number;
101
+ count: number;
102
+ }
97
103
  export declare function assertFileImportJob(maybeJob: unknown): asserts maybeJob is FileImportJob;
98
104
  export declare function assertFileImportJobRecordsResponse(maybeRecords: unknown): asserts maybeRecords is FileImportJobRecordsResponse;
99
105
  export declare function assertProcessFileImportJobResponse(maybeResponse: unknown): asserts maybeResponse is ProcessFileImportJobResponse;
@@ -36,14 +36,6 @@ export declare const countJsonFileItems: (file: File) => Promise<{
36
36
  isValid: true;
37
37
  itemsCount: number;
38
38
  }>;
39
- /**
40
- * Count unique resources in a CSV file by counting unique values in the "key" column.
41
- * A single resource can span multiple rows (when it has array fields like variants, assets...),
42
- * so we count unique keys rather than rows.
43
- * @param file The CSV file to process
44
- * @returns A promise that resolves to the number of unique resources
45
- */
46
- export declare const countUniqueResourcesInCsv: (file: File) => Promise<number>;
47
39
  /**
48
40
  * Map file upload errors to upload file error rows with unique IDs
49
41
  * @param uploadFileErrors Array of file upload errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend-extensions/operations",
3
- "version": "3.1.2",
3
+ "version": "3.2.1",
4
4
  "license": "Proprietary",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -18,14 +18,14 @@
18
18
  "react-dropzone": "14.3.8"
19
19
  },
20
20
  "devDependencies": {
21
- "@commercetools-frontend/actions-global": "24.12.0",
22
- "@commercetools-frontend/application-components": "24.12.0",
23
- "@commercetools-frontend/application-shell": "24.12.0",
24
- "@commercetools-frontend/application-shell-connectors": "24.12.0",
25
- "@commercetools-frontend/constants": "24.12.0",
26
- "@commercetools-frontend/jest-preset-mc-app": "24.12.0",
27
- "@commercetools-frontend/permissions": "24.12.0",
28
- "@commercetools-frontend/sentry": "24.12.0",
21
+ "@commercetools-frontend/actions-global": "24.13.0",
22
+ "@commercetools-frontend/application-components": "24.13.0",
23
+ "@commercetools-frontend/application-shell": "24.13.0",
24
+ "@commercetools-frontend/application-shell-connectors": "24.13.0",
25
+ "@commercetools-frontend/constants": "24.13.0",
26
+ "@commercetools-frontend/jest-preset-mc-app": "24.13.0",
27
+ "@commercetools-frontend/permissions": "24.13.0",
28
+ "@commercetools-frontend/sentry": "24.13.0",
29
29
  "@commercetools-frontend/ui-kit": "20.3.0",
30
30
  "@emotion/react": "11.14.0",
31
31
  "@emotion/styled": "11.14.1",
@@ -217,3 +217,55 @@ export async function listFileImportJobs({
217
217
  assertListFileImportJobsResponse(response)
218
218
  return response
219
219
  }
220
+
221
+ export type FileImportJobInfo = {
222
+ total: number
223
+ isInitializing: boolean
224
+ }
225
+
226
+ /**
227
+ * Gets the file import job info for an import container
228
+ *
229
+ * For the new file import job flow, import operations are created incrementally
230
+ * during the 'initialising' state. The import summary total
231
+ * reflects only the operations created so far, which can be misleading
232
+ *
233
+ * This helper fetches the file import job (if it exists) to get:
234
+ * - The true total from the job summary (known from initial CSV validation)
235
+ * - Whether the job is still initializing (creating import operations)
236
+ *
237
+ * @returns Job info if found, null otherwise
238
+ */
239
+ export async function getFileImportJobInfoForContainer({
240
+ projectKey,
241
+ importContainerKey,
242
+ }: {
243
+ projectKey: string
244
+ importContainerKey: string
245
+ }): Promise<FileImportJobInfo | null> {
246
+ try {
247
+ const response = await listFileImportJobs({
248
+ projectKey,
249
+ importContainerKey,
250
+ limit: 1,
251
+ })
252
+
253
+ if (
254
+ response.results.length > 0 &&
255
+ response.results[0].summary?.total != null
256
+ ) {
257
+ const job = response.results[0]
258
+ const isInitializing = ['processing', 'initialising'].includes(job.state)
259
+
260
+ return {
261
+ total: job.summary.total,
262
+ isInitializing,
263
+ }
264
+ }
265
+
266
+ return null
267
+ } catch {
268
+ // Job might not exist (old flow)
269
+ return null
270
+ }
271
+ }
@@ -27,8 +27,16 @@ import {
27
27
  } from './urls'
28
28
  import { checkIfFileUploadImport } from '../@utils'
29
29
  import { fetcher } from './fetcher'
30
+ import { getFileImportJobInfoForContainer } from './file-import-jobs'
30
31
 
31
- export function getImportState(importSummary: ImportSummary): ImportStates {
32
+ type GetImportStateOptions = {
33
+ isJobInitializing?: boolean
34
+ }
35
+
36
+ export function getImportState(
37
+ importSummary: ImportSummary,
38
+ options: GetImportStateOptions = {}
39
+ ): ImportStates {
32
40
  const processing = importSummary.states.processing > 0
33
41
  if (processing) return ImportStates.Processing
34
42
 
@@ -48,6 +56,11 @@ export function getImportState(importSummary: ImportSummary): ImportStates {
48
56
  const noRunning = importSummary.total === 0
49
57
  if (noRunning) return ImportStates.NoRunningImports
50
58
 
59
+ // For the new flow: job is actively creating import operations (to show as Processing even if no operations exist yet)
60
+ if (options.isJobInitializing) {
61
+ return ImportStates.Processing
62
+ }
63
+
51
64
  const successfullyCompleted =
52
65
  importSummary.states.imported === importSummary.total ||
53
66
  importSummary.states.deleted === importSummary.total
@@ -240,13 +253,31 @@ async function importContainerToContainerDetails(
240
253
  projectKey: string,
241
254
  importContainer: ExtendedImportContainer
242
255
  ): Promise<ImportContainerDetails> {
243
- const importSummary = await fetchImportSummary({
256
+ let importSummary = await fetchImportSummary({
244
257
  projectKey,
245
258
  importContainerKey: importContainer.key,
246
259
  })
247
- const importState = getImportState(importSummary)
248
260
  const isFileUploadImport = checkIfFileUploadImport(importContainer.tags)
249
261
 
262
+ // For the new file import job flow the import operations are created incrementally
263
+ // The import summary total reflects only operations created so far
264
+ // Only override total when job is actively initializing (creating operations)
265
+ let isJobInitializing = false
266
+ if (isFileUploadImport) {
267
+ const jobInfo = await getFileImportJobInfoForContainer({
268
+ projectKey,
269
+ importContainerKey: importContainer.key,
270
+ })
271
+ if (jobInfo !== null) {
272
+ isJobInitializing = jobInfo.isInitializing
273
+ if (isJobInitializing || importSummary.total > 0) {
274
+ importSummary = { ...importSummary, total: jobInfo.total }
275
+ }
276
+ }
277
+ }
278
+
279
+ const importState = getImportState(importSummary, { isJobInitializing })
280
+
250
281
  return {
251
282
  importContainer: importContainer,
252
283
  importState,
@@ -56,10 +56,12 @@ export const UploadingModal = ({
56
56
  onClick={onCancel}
57
57
  />
58
58
  </Spacings.Inline>
59
- <ProgressBar barWidth="scale" height="10" progress={progress} />
60
- {statusMessage && (
61
- <Text.Detail tone="tertiary">{statusMessage}</Text.Detail>
62
- )}
59
+ <Spacings.Stack scale="xs">
60
+ <ProgressBar barWidth="scale" height="10" progress={progress} />
61
+ {statusMessage && (
62
+ <Text.Detail tone="tertiary">{statusMessage}</Text.Detail>
63
+ )}
64
+ </Spacings.Stack>
63
65
  </Spacings.Stack>
64
66
  </InfoDialog>
65
67
  )
@@ -4,11 +4,7 @@ import { useImportContainerUpload } from './use-import-container-upload'
4
4
  import { useFileImportJobUpload } from './use-file-import-job-upload'
5
5
  import { deleteImportContainer } from '../@api'
6
6
  import { HttpError, PollingAbortedError } from '../@errors'
7
- import {
8
- pollJobUntilValidated,
9
- pollJobUntilProcessing,
10
- countUniqueResourcesInCsv,
11
- } from '../@utils'
7
+ import { pollJobUntilValidated, pollJobUntilProcessing } from '../@utils'
12
8
  import type {
13
9
  ExtendedImportContainerDraft,
14
10
  FileUploadResult,
@@ -17,7 +13,6 @@ import type {
17
13
 
18
14
  export type ValidationProgress = {
19
15
  processed: number
20
- total: number
21
16
  isValidating: boolean
22
17
  }
23
18
 
@@ -67,7 +62,6 @@ export const useFileUpload = ({
67
62
  const [validationProgress, setValidationProgress] =
68
63
  React.useState<ValidationProgress>({
69
64
  processed: 0,
70
- total: 0,
71
65
  isValidating: false,
72
66
  })
73
67
 
@@ -77,7 +71,7 @@ export const useFileUpload = ({
77
71
  const resetState = React.useCallback(() => {
78
72
  setIsUploading(false)
79
73
  setProgress(0)
80
- setValidationProgress({ processed: 0, total: 0, isValidating: false })
74
+ setValidationProgress({ processed: 0, isValidating: false })
81
75
  }, [])
82
76
 
83
77
  const upload = React.useCallback(
@@ -87,10 +81,6 @@ export const useFileUpload = ({
87
81
 
88
82
  try {
89
83
  if (useJobBasedFlow) {
90
- const totalResources = config.skipValidationPolling
91
- ? 0
92
- : await countUniqueResourcesInCsv(config.file)
93
-
94
84
  await jobUpload.upload({
95
85
  file: config.file,
96
86
  resourceType: config.resourceType,
@@ -136,7 +126,6 @@ export const useFileUpload = ({
136
126
  try {
137
127
  setValidationProgress({
138
128
  processed: 0,
139
- total: totalResources,
140
129
  isValidating: true,
141
130
  })
142
131
 
@@ -151,7 +140,6 @@ export const useFileUpload = ({
151
140
  const processed = job.summary?.total ?? 0
152
141
  setValidationProgress({
153
142
  processed,
154
- total: totalResources,
155
143
  isValidating: true,
156
144
  })
157
145
  config.onValidationProgress?.(job)
@@ -186,7 +174,6 @@ export const useFileUpload = ({
186
174
  setIsUploading(false)
187
175
  setValidationProgress({
188
176
  processed: 0,
189
- total: 0,
190
177
  isValidating: false,
191
178
  })
192
179
  config.onSuccess(result)
@@ -120,7 +120,13 @@ export interface ListFileImportJobsParameters {
120
120
  offset?: number
121
121
  }
122
122
 
123
- export type ListFileImportJobsResponse = FileImportJob[]
123
+ export interface ListFileImportJobsResponse {
124
+ results: FileImportJob[]
125
+ total: number
126
+ limit: number
127
+ offset: number
128
+ count: number
129
+ }
124
130
 
125
131
  export function assertFileImportJob(
126
132
  maybeJob: unknown
@@ -152,15 +158,10 @@ export function assertProcessFileImportJobResponse(
152
158
  export function assertListFileImportJobsResponse(
153
159
  maybeResponse: unknown
154
160
  ): asserts maybeResponse is ListFileImportJobsResponse {
155
- if (!Array.isArray(maybeResponse)) {
156
- throw new Error('Invalid List File Import Jobs response: expected an array')
157
- }
158
- if (maybeResponse.length > 0) {
159
- const requiredFields = ['id', 'fileName', 'importContainerKey', 'state']
160
- if (!hasRequiredFields(maybeResponse[0], requiredFields)) {
161
- throw new Error(
162
- 'Invalid List File Import Jobs response: missing required fields'
163
- )
164
- }
161
+ const requiredFields = ['results', 'total', 'limit', 'offset', 'count']
162
+ if (!hasRequiredFields(maybeResponse, requiredFields)) {
163
+ throw new Error(
164
+ 'Invalid List File Import Jobs response: missing required fields'
165
+ )
165
166
  }
166
167
  }