@wix/bex-core 2.328.0 → 2.330.0
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.
- package/dist/cjs/assets/locale/messages_ar.json +12 -0
- package/dist/cjs/assets/locale/messages_bg.json +12 -0
- package/dist/cjs/assets/locale/messages_ca.json +12 -0
- package/dist/cjs/assets/locale/messages_cs.json +12 -0
- package/dist/cjs/assets/locale/messages_da.json +17 -5
- package/dist/cjs/assets/locale/messages_de.json +12 -0
- package/dist/cjs/assets/locale/messages_el.json +12 -0
- package/dist/cjs/assets/locale/messages_en.json +12 -0
- package/dist/cjs/assets/locale/messages_es.json +16 -4
- package/dist/cjs/assets/locale/messages_fi.json +12 -0
- package/dist/cjs/assets/locale/messages_fr.json +12 -0
- package/dist/cjs/assets/locale/messages_he.json +12 -0
- package/dist/cjs/assets/locale/messages_hi.json +17 -5
- package/dist/cjs/assets/locale/messages_hu.json +12 -0
- package/dist/cjs/assets/locale/messages_id.json +17 -5
- package/dist/cjs/assets/locale/messages_it.json +17 -5
- package/dist/cjs/assets/locale/messages_ja.json +12 -0
- package/dist/cjs/assets/locale/messages_ko.json +17 -5
- package/dist/cjs/assets/locale/messages_lt.json +12 -0
- package/dist/cjs/assets/locale/messages_ms.json +12 -0
- package/dist/cjs/assets/locale/messages_nl.json +16 -4
- package/dist/cjs/assets/locale/messages_no.json +12 -0
- package/dist/cjs/assets/locale/messages_pl.json +12 -0
- package/dist/cjs/assets/locale/messages_pt.json +16 -4
- package/dist/cjs/assets/locale/messages_ro.json +12 -0
- package/dist/cjs/assets/locale/messages_ru.json +12 -0
- package/dist/cjs/assets/locale/messages_sk.json +12 -0
- package/dist/cjs/assets/locale/messages_sl.json +12 -0
- package/dist/cjs/assets/locale/messages_sv.json +17 -5
- package/dist/cjs/assets/locale/messages_th.json +17 -5
- package/dist/cjs/assets/locale/messages_tl.json +12 -0
- package/dist/cjs/assets/locale/messages_tr.json +17 -5
- package/dist/cjs/assets/locale/messages_uk.json +12 -0
- package/dist/cjs/assets/locale/messages_vi.json +17 -5
- package/dist/cjs/assets/locale/messages_zh.json +12 -0
- package/dist/cjs/assets/locale/version_file +1 -1
- package/dist/cjs/assets/visual-e2e-locale/messages_visuale2e.json +5 -1
- package/dist/cjs/hooks/useImport.js +7 -5
- package/dist/cjs/hooks/useImport.js.map +1 -1
- package/dist/cjs/hooks/useImportConfig.js.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/DataExtensionFieldOperationsService.js +3 -3
- package/dist/cjs/services/DataExtensionFieldOperationsService.js.map +1 -1
- package/dist/cjs/services/translations.js.map +1 -1
- package/dist/cjs/state/ArchiveFieldModalState.js +5 -8
- package/dist/cjs/state/ArchiveFieldModalState.js.map +1 -1
- package/dist/cjs/state/CustomFieldModalBiReporter.js +5 -9
- package/dist/cjs/state/CustomFieldModalBiReporter.js.map +1 -1
- package/dist/cjs/state/CustomFieldModalState.js +32 -71
- package/dist/cjs/state/CustomFieldModalState.js.map +1 -1
- package/dist/cjs/state/DataExtensionState.js +0 -12
- package/dist/cjs/state/DataExtensionState.js.map +1 -1
- package/dist/cjs/state/DeleteFieldModalState.js +9 -11
- package/dist/cjs/state/DeleteFieldModalState.js.map +1 -1
- package/dist/cjs/state/{DataExtensionStateBIReporter.js → FieldOperationsBIReporter.js} +7 -32
- package/dist/cjs/state/FieldOperationsBIReporter.js.map +1 -0
- package/dist/cjs/state/ImportState/ImportState.js +383 -109
- package/dist/cjs/state/ImportState/ImportState.js.map +1 -1
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js +610 -0
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/__tests__/hubJobClient.spec.js +209 -0
- package/dist/cjs/state/ImportState/__tests__/hubJobClient.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/__tests__/importJobPoller.spec.js +86 -0
- package/dist/cjs/state/ImportState/__tests__/importJobPoller.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/__tests__/parseCsv.spec.js +52 -0
- package/dist/cjs/state/ImportState/__tests__/parseCsv.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/__tests__/rewriteCsv.spec.js +81 -0
- package/dist/cjs/state/ImportState/__tests__/rewriteCsv.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/fieldEdit.js +46 -0
- package/dist/cjs/state/ImportState/fieldEdit.js.map +1 -0
- package/dist/cjs/state/ImportState/hubJobClient.js +101 -0
- package/dist/cjs/state/ImportState/hubJobClient.js.map +1 -0
- package/dist/cjs/state/ImportState/importJobPoller.js +44 -0
- package/dist/cjs/state/ImportState/importJobPoller.js.map +1 -0
- package/dist/cjs/state/ImportState/index.js +12 -0
- package/dist/cjs/state/ImportState/index.js.map +1 -1
- package/dist/cjs/state/ImportState/parseCsv.js +39 -10
- package/dist/cjs/state/ImportState/parseCsv.js.map +1 -1
- package/dist/cjs/state/ImportState/rewriteCsv.js +46 -0
- package/dist/cjs/state/ImportState/rewriteCsv.js.map +1 -0
- package/dist/cjs/state/formatPermissionsForBi.js +36 -0
- package/dist/cjs/state/formatPermissionsForBi.js.map +1 -0
- package/dist/cjs/testkit/WixPatternsContainer.driver.js +4 -1
- package/dist/cjs/testkit/WixPatternsContainer.driver.js.map +1 -1
- package/dist/cjs/testkit/client.js +6 -0
- package/dist/cjs/testkit/client.js.map +1 -1
- package/dist/cjs/testkit/hubJobImportMocks.js +166 -0
- package/dist/cjs/testkit/hubJobImportMocks.js.map +1 -0
- package/dist/cjs/testkit/index.js +6 -0
- package/dist/cjs/testkit/index.js.map +1 -1
- package/dist/cjs/types/FieldsSource.js +4 -0
- package/dist/cjs/types/FieldsSource.js.map +1 -0
- package/dist/cjs/types/Schema.js.map +1 -1
- package/dist/cjs/types/SchemaSource.js +81 -0
- package/dist/cjs/types/SchemaSource.js.map +1 -0
- package/dist/esm/assets/locale/messages_ar.json +12 -0
- package/dist/esm/assets/locale/messages_bg.json +12 -0
- package/dist/esm/assets/locale/messages_ca.json +12 -0
- package/dist/esm/assets/locale/messages_cs.json +12 -0
- package/dist/esm/assets/locale/messages_da.json +17 -5
- package/dist/esm/assets/locale/messages_de.json +12 -0
- package/dist/esm/assets/locale/messages_el.json +12 -0
- package/dist/esm/assets/locale/messages_en.json +12 -0
- package/dist/esm/assets/locale/messages_es.json +16 -4
- package/dist/esm/assets/locale/messages_fi.json +12 -0
- package/dist/esm/assets/locale/messages_fr.json +12 -0
- package/dist/esm/assets/locale/messages_he.json +12 -0
- package/dist/esm/assets/locale/messages_hi.json +17 -5
- package/dist/esm/assets/locale/messages_hu.json +12 -0
- package/dist/esm/assets/locale/messages_id.json +17 -5
- package/dist/esm/assets/locale/messages_it.json +17 -5
- package/dist/esm/assets/locale/messages_ja.json +12 -0
- package/dist/esm/assets/locale/messages_ko.json +17 -5
- package/dist/esm/assets/locale/messages_lt.json +12 -0
- package/dist/esm/assets/locale/messages_ms.json +12 -0
- package/dist/esm/assets/locale/messages_nl.json +16 -4
- package/dist/esm/assets/locale/messages_no.json +12 -0
- package/dist/esm/assets/locale/messages_pl.json +12 -0
- package/dist/esm/assets/locale/messages_pt.json +16 -4
- package/dist/esm/assets/locale/messages_ro.json +12 -0
- package/dist/esm/assets/locale/messages_ru.json +12 -0
- package/dist/esm/assets/locale/messages_sk.json +12 -0
- package/dist/esm/assets/locale/messages_sl.json +12 -0
- package/dist/esm/assets/locale/messages_sv.json +17 -5
- package/dist/esm/assets/locale/messages_th.json +17 -5
- package/dist/esm/assets/locale/messages_tl.json +12 -0
- package/dist/esm/assets/locale/messages_tr.json +17 -5
- package/dist/esm/assets/locale/messages_uk.json +12 -0
- package/dist/esm/assets/locale/messages_vi.json +17 -5
- package/dist/esm/assets/locale/messages_zh.json +12 -0
- package/dist/esm/assets/locale/version_file +1 -1
- package/dist/esm/assets/visual-e2e-locale/messages_visuale2e.json +5 -1
- package/dist/esm/hooks/useImport.js +5 -4
- package/dist/esm/hooks/useImport.js.map +1 -1
- package/dist/esm/hooks/useImportConfig.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/services/DataExtensionFieldOperationsService.js +3 -3
- package/dist/esm/services/DataExtensionFieldOperationsService.js.map +1 -1
- package/dist/esm/state/ArchiveFieldModalState.js +4 -7
- package/dist/esm/state/ArchiveFieldModalState.js.map +1 -1
- package/dist/esm/state/CustomFieldModalBiReporter.js +5 -8
- package/dist/esm/state/CustomFieldModalBiReporter.js.map +1 -1
- package/dist/esm/state/CustomFieldModalState.js +28 -52
- package/dist/esm/state/CustomFieldModalState.js.map +1 -1
- package/dist/esm/state/DataExtensionState.js +0 -10
- package/dist/esm/state/DataExtensionState.js.map +1 -1
- package/dist/esm/state/DeleteFieldModalState.js +7 -10
- package/dist/esm/state/DeleteFieldModalState.js.map +1 -1
- package/dist/esm/state/{DataExtensionStateBIReporter.js → FieldOperationsBIReporter.js} +5 -25
- package/dist/esm/state/FieldOperationsBIReporter.js.map +1 -0
- package/dist/esm/state/ImportState/ImportState.js +314 -101
- package/dist/esm/state/ImportState/ImportState.js.map +1 -1
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js +434 -0
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -0
- package/dist/esm/state/ImportState/__tests__/hubJobClient.spec.js +139 -0
- package/dist/esm/state/ImportState/__tests__/hubJobClient.spec.js.map +1 -0
- package/dist/esm/state/ImportState/__tests__/importJobPoller.spec.js +62 -0
- package/dist/esm/state/ImportState/__tests__/importJobPoller.spec.js.map +1 -0
- package/dist/esm/state/ImportState/__tests__/parseCsv.spec.js +59 -0
- package/dist/esm/state/ImportState/__tests__/parseCsv.spec.js.map +1 -0
- package/dist/esm/state/ImportState/__tests__/rewriteCsv.spec.js +68 -0
- package/dist/esm/state/ImportState/__tests__/rewriteCsv.spec.js.map +1 -0
- package/dist/esm/state/ImportState/fieldEdit.js +33 -0
- package/dist/esm/state/ImportState/fieldEdit.js.map +1 -0
- package/dist/esm/state/ImportState/hubJobClient.js +43 -0
- package/dist/esm/state/ImportState/hubJobClient.js.map +1 -0
- package/dist/esm/state/ImportState/importJobPoller.js +34 -0
- package/dist/esm/state/ImportState/importJobPoller.js.map +1 -0
- package/dist/esm/state/ImportState/index.js +2 -0
- package/dist/esm/state/ImportState/index.js.map +1 -1
- package/dist/esm/state/ImportState/parseCsv.js +34 -10
- package/dist/esm/state/ImportState/parseCsv.js.map +1 -1
- package/dist/esm/state/ImportState/rewriteCsv.js +39 -0
- package/dist/esm/state/ImportState/rewriteCsv.js.map +1 -0
- package/dist/esm/state/formatPermissionsForBi.js +26 -0
- package/dist/esm/state/formatPermissionsForBi.js.map +1 -0
- package/dist/esm/testkit/WixPatternsContainer.driver.js +4 -1
- package/dist/esm/testkit/WixPatternsContainer.driver.js.map +1 -1
- package/dist/esm/testkit/client.js +1 -0
- package/dist/esm/testkit/client.js.map +1 -1
- package/dist/esm/testkit/hubJobImportMocks.js +173 -0
- package/dist/esm/testkit/hubJobImportMocks.js.map +1 -0
- package/dist/esm/testkit/index.js +1 -0
- package/dist/esm/testkit/index.js.map +1 -1
- package/dist/esm/types/FieldsSource.js +2 -0
- package/dist/esm/types/FieldsSource.js.map +1 -0
- package/dist/esm/types/SchemaSource.js +52 -0
- package/dist/esm/types/SchemaSource.js.map +1 -0
- package/dist/types/hooks/useImport.d.ts +5 -3
- package/dist/types/hooks/useImport.d.ts.map +1 -1
- package/dist/types/hooks/useImportConfig.d.ts +2 -3
- package/dist/types/hooks/useImportConfig.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/services/translations.d.ts +1 -1
- package/dist/types/services/translations.d.ts.map +1 -1
- package/dist/types/state/ArchiveFieldModalState.d.ts +7 -5
- package/dist/types/state/ArchiveFieldModalState.d.ts.map +1 -1
- package/dist/types/state/CustomFieldModalBiReporter.d.ts +4 -3
- package/dist/types/state/CustomFieldModalBiReporter.d.ts.map +1 -1
- package/dist/types/state/CustomFieldModalState.d.ts +12 -6
- package/dist/types/state/CustomFieldModalState.d.ts.map +1 -1
- package/dist/types/state/DataExtensionState.d.ts +0 -2
- package/dist/types/state/DataExtensionState.d.ts.map +1 -1
- package/dist/types/state/DeleteFieldModalState.d.ts +8 -5
- package/dist/types/state/DeleteFieldModalState.d.ts.map +1 -1
- package/dist/types/state/{DataExtensionStateBIReporter.d.ts → FieldOperationsBIReporter.d.ts} +4 -5
- package/dist/types/state/FieldOperationsBIReporter.d.ts.map +1 -0
- package/dist/types/state/ImportState/ImportState.d.ts +120 -20
- package/dist/types/state/ImportState/ImportState.d.ts.map +1 -1
- package/dist/types/state/ImportState/__tests__/ImportState.engine.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/ImportState.engine.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/__tests__/hubJobClient.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/hubJobClient.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/__tests__/importJobPoller.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/importJobPoller.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/__tests__/parseCsv.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/parseCsv.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/__tests__/rewriteCsv.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/rewriteCsv.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/fieldEdit.d.ts +10 -0
- package/dist/types/state/ImportState/fieldEdit.d.ts.map +1 -0
- package/dist/types/state/ImportState/hubJobClient.d.ts +47 -0
- package/dist/types/state/ImportState/hubJobClient.d.ts.map +1 -0
- package/dist/types/state/ImportState/importJobPoller.d.ts +20 -0
- package/dist/types/state/ImportState/importJobPoller.d.ts.map +1 -0
- package/dist/types/state/ImportState/index.d.ts +2 -0
- package/dist/types/state/ImportState/index.d.ts.map +1 -1
- package/dist/types/state/ImportState/parseCsv.d.ts +7 -0
- package/dist/types/state/ImportState/parseCsv.d.ts.map +1 -1
- package/dist/types/state/ImportState/rewriteCsv.d.ts +25 -0
- package/dist/types/state/ImportState/rewriteCsv.d.ts.map +1 -0
- package/dist/types/state/formatPermissionsForBi.d.ts +7 -0
- package/dist/types/state/formatPermissionsForBi.d.ts.map +1 -0
- package/dist/types/testkit/WixPatternsContainer.driver.d.ts +22 -0
- package/dist/types/testkit/WixPatternsContainer.driver.d.ts.map +1 -1
- package/dist/types/testkit/client.d.ts +1 -0
- package/dist/types/testkit/client.d.ts.map +1 -1
- package/dist/types/testkit/hubJobImportMocks.d.ts +40 -0
- package/dist/types/testkit/hubJobImportMocks.d.ts.map +1 -0
- package/dist/types/testkit/index.d.ts +1 -0
- package/dist/types/testkit/index.d.ts.map +1 -1
- package/dist/types/types/FieldsSource.d.ts +116 -0
- package/dist/types/types/FieldsSource.d.ts.map +1 -0
- package/dist/types/types/Schema.d.ts +28 -11
- package/dist/types/types/Schema.d.ts.map +1 -1
- package/dist/types/types/SchemaSource.d.ts +60 -0
- package/dist/types/types/SchemaSource.d.ts.map +1 -0
- package/package.json +4 -4
- package/dist/cjs/state/DataExtensionStateBIReporter.js.map +0 -1
- package/dist/esm/state/DataExtensionStateBIReporter.js.map +0 -1
- package/dist/types/state/DataExtensionStateBIReporter.d.ts.map +0 -1
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import { action, computed, makeObservable, observable, runInAction, } from 'mobx';
|
|
2
2
|
import { queryStatus, } from '../../model';
|
|
3
3
|
import { ConditionalModalState } from '../ConditionalModalState';
|
|
4
|
+
import { JobStatus, WritePolicy, } from '@wix/bex-utils/@wix/ambassador-hub-v1-job/types';
|
|
4
5
|
import { parseCsv, unparseCsv } from './parseCsv';
|
|
6
|
+
import { rewriteParsedCsvForImport } from './rewriteCsv';
|
|
7
|
+
import { createHubJobClient } from './hubJobClient';
|
|
8
|
+
import { pollImportJob } from './importJobPoller';
|
|
5
9
|
import { detectColumnType, TYPE_SAMPLE_SIZE, } from './detectFieldType';
|
|
6
|
-
import
|
|
7
|
-
|
|
10
|
+
import { toFieldEdit } from './fieldEdit';
|
|
11
|
+
// `NewFieldType`, `detectColumnType`, etc. are re-exported from the package via
|
|
12
|
+
// the `ImportState` barrel (`./index`), which also exports `./detectFieldType`.
|
|
13
|
+
// Re-export the write-policy enum so consumers (and the cairo UI) reference it
|
|
14
|
+
// from `@wix/bex-core` rather than the bundled ambassador path.
|
|
15
|
+
export { WritePolicy } from '@wix/bex-utils/@wix/ambassador-hub-v1-job/types';
|
|
8
16
|
// How long to keep the completed progress bar (100%) on screen before closing
|
|
9
17
|
// the modal on a clean import. Without it, an import of very few rows finishes
|
|
10
18
|
// near-instantly and the modal appears to vanish without ever showing the bar
|
|
11
19
|
// reaching completion.
|
|
12
20
|
const COMPLETION_LINGER_MS = 600;
|
|
21
|
+
// The Wix Data collection's id field key. A CSV column mapped to this field lets
|
|
22
|
+
// the user choose how rows that collide on `_id` are written (writePolicy).
|
|
23
|
+
const ID_FIELD_KEY = '_id';
|
|
13
24
|
function normalize(value) {
|
|
14
25
|
return value.trim().toLowerCase();
|
|
15
26
|
}
|
|
16
27
|
// camelCase key for a field created from a column header
|
|
17
28
|
// (e.g. "Product Name" -> "productName").
|
|
18
29
|
function toFieldKey(header) {
|
|
19
|
-
const words = header
|
|
30
|
+
const words = header
|
|
31
|
+
.trim()
|
|
32
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
33
|
+
.filter(Boolean);
|
|
20
34
|
if (words.length === 0) {
|
|
21
35
|
return 'field';
|
|
22
36
|
}
|
|
@@ -44,19 +58,6 @@ function uniqueFieldNameAndKey(header, usedNames, usedKeys) {
|
|
|
44
58
|
displayName = `${header}-${suffix}`;
|
|
45
59
|
}
|
|
46
60
|
}
|
|
47
|
-
function toMappedRows(rows, headers, mappingByHeader) {
|
|
48
|
-
return rows.map((row) => {
|
|
49
|
-
const mapped = {};
|
|
50
|
-
headers.forEach((header, index) => {
|
|
51
|
-
const mappedField = mappingByHeader[header];
|
|
52
|
-
if (mappedField == null) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
set(mapped, mappedField, row[index] ?? '');
|
|
56
|
-
});
|
|
57
|
-
return mapped;
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
61
|
export class ImportState {
|
|
61
62
|
constructor(props) {
|
|
62
63
|
this.discardChangesModal = new ConditionalModalState();
|
|
@@ -67,26 +68,43 @@ export class ImportState {
|
|
|
67
68
|
this.fileName = '';
|
|
68
69
|
this.csvHeaders = [];
|
|
69
70
|
this.csvRows = [];
|
|
71
|
+
// Header display order for the configuration step, snapshotted when the file
|
|
72
|
+
// is parsed. The column auto-mapped to `_id` floats to the top (CAIRO-4418);
|
|
73
|
+
// the order then stays fixed while the user edits mappings, so clearing or
|
|
74
|
+
// remapping the `_id` column can't reorder the row under the user's cursor.
|
|
75
|
+
// Recomputed only on a new file (`applyParsedCsv`) or a full `reset()`.
|
|
76
|
+
this.orderedHeaders = [];
|
|
70
77
|
this.mappingByHeader = {};
|
|
78
|
+
// How rows that collide on `_id` are written. Only meaningful (and only sent
|
|
79
|
+
// to the server) when a CSV column is mapped to the `_id` field — see
|
|
80
|
+
// `idMappedHeader` and `_destination`. Defaults to the server default
|
|
81
|
+
// (`OVERWRITE` = upsert by `_id`).
|
|
82
|
+
//
|
|
83
|
+
// Retention: the choice is deliberately kept while editing mappings within a
|
|
84
|
+
// file — remapping `_id` away and back keeps the user's selection rather than
|
|
85
|
+
// silently reverting it. It resets to the default only on a *new file* upload
|
|
86
|
+
// (`applyParsedCsv`) or a full `reset()`.
|
|
87
|
+
this.writePolicy = WritePolicy.OVERWRITE;
|
|
71
88
|
this.processedCount = 0;
|
|
72
89
|
this.totalCount = 0;
|
|
73
90
|
this.result = {
|
|
74
91
|
succeeded: 0,
|
|
75
92
|
failed: 0,
|
|
76
|
-
errors: [],
|
|
77
93
|
};
|
|
78
94
|
this.isBackingUp = false;
|
|
79
95
|
this.isRestoring = false;
|
|
80
96
|
this.restoreFn = null;
|
|
81
97
|
this._cancelled = false;
|
|
82
98
|
this._abortController = null;
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
99
|
+
// Id of the running data-movement job, set once submitted — used to cancel it
|
|
100
|
+
// (`terminateJob`) if the user closes mid-import.
|
|
101
|
+
this._jobId = null;
|
|
102
|
+
// The column whose pending new field the field editor is currently editing.
|
|
103
|
+
// Not observable — transient wiring between `beginFieldEdit` and the editor's
|
|
104
|
+
// `applyFieldEdit` callback, never rendered.
|
|
105
|
+
this._editingHeader = null;
|
|
88
106
|
this.fields = (props.fields ?? []);
|
|
89
|
-
this.
|
|
107
|
+
this.collectionId = props.collectionId;
|
|
90
108
|
this.backup = props.backup;
|
|
91
109
|
this.updateSchema = props.updateSchema;
|
|
92
110
|
this.collection = props.collection;
|
|
@@ -95,15 +113,21 @@ export class ImportState {
|
|
|
95
113
|
this.errorHandler = props.errorHandler;
|
|
96
114
|
this.showToast = props.showToast;
|
|
97
115
|
this.downloadCsv = props.downloadCsv;
|
|
98
|
-
this.
|
|
116
|
+
this.onJobError = props.onJobError;
|
|
99
117
|
this._detectColumnType = props.detectColumnType ?? detectColumnType;
|
|
118
|
+
this.hubJobClient = createHubJobClient({
|
|
119
|
+
httpClient: props.httpClient,
|
|
120
|
+
errorHandler: props.errorHandler,
|
|
121
|
+
});
|
|
100
122
|
makeObservable(this, {
|
|
101
123
|
currentStep: observable.ref,
|
|
102
124
|
importStatus: observable.ref,
|
|
103
125
|
fileName: observable.ref,
|
|
104
126
|
csvHeaders: observable.ref,
|
|
105
127
|
csvRows: observable.ref,
|
|
128
|
+
orderedHeaders: observable.ref,
|
|
106
129
|
mappingByHeader: observable.ref,
|
|
130
|
+
writePolicy: observable.ref,
|
|
107
131
|
processedCount: observable.ref,
|
|
108
132
|
totalCount: observable.ref,
|
|
109
133
|
result: observable.ref,
|
|
@@ -113,10 +137,15 @@ export class ImportState {
|
|
|
113
137
|
hasMappings: computed,
|
|
114
138
|
hasNoMappedData: computed,
|
|
115
139
|
selectedFieldIds: computed,
|
|
140
|
+
idMappedHeader: computed,
|
|
141
|
+
setWritePolicy: action.bound,
|
|
116
142
|
onUploadContinue: action.bound,
|
|
117
143
|
goToUpload: action.bound,
|
|
118
144
|
setMapping: action.bound,
|
|
119
145
|
setNewFieldMapping: action.bound,
|
|
146
|
+
setNewFieldConfig: action.bound,
|
|
147
|
+
beginFieldEdit: action.bound,
|
|
148
|
+
applyFieldEdit: action.bound,
|
|
120
149
|
close: action.bound,
|
|
121
150
|
onFileSelected: action.bound,
|
|
122
151
|
startImport: action.bound,
|
|
@@ -124,7 +153,15 @@ export class ImportState {
|
|
|
124
153
|
});
|
|
125
154
|
}
|
|
126
155
|
init() {
|
|
127
|
-
return () =>
|
|
156
|
+
return () => {
|
|
157
|
+
// Unmounting mid-import stops the poll loop (it checks `_cancelled`) so it
|
|
158
|
+
// doesn't run to terminal holding references. We deliberately do NOT abort
|
|
159
|
+
// the AbortController: during a cancel-triggered restore it holds the
|
|
160
|
+
// restore controller, and aborting it would cancel an in-progress restore.
|
|
161
|
+
// The server-side job is also left running — an unmount isn't an explicit
|
|
162
|
+
// user cancel.
|
|
163
|
+
this._cancelled = true;
|
|
164
|
+
};
|
|
128
165
|
}
|
|
129
166
|
get hasParsedRows() {
|
|
130
167
|
return this.csvHeaders.length > 0 && this.csvRows.length > 0;
|
|
@@ -156,6 +193,19 @@ export class ImportState {
|
|
|
156
193
|
.filter((m) => m?.kind === 'existing')
|
|
157
194
|
.map((m) => m.fieldId));
|
|
158
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* The CSV header currently mapped to the collection's `_id` field, or `null`
|
|
198
|
+
* when no column targets it. Drives both the "When items share the same ID"
|
|
199
|
+
* control (only shown on this row) and whether `writePolicy` is sent at all —
|
|
200
|
+
* with no `_id` mapped, every row is a fresh insert and collisions can't occur.
|
|
201
|
+
*/
|
|
202
|
+
get idMappedHeader() {
|
|
203
|
+
const entry = Object.entries(this.mappingByHeader).find(([, mapping]) => mapping?.kind === 'existing' && mapping.fieldId === ID_FIELD_KEY);
|
|
204
|
+
return entry ? entry[0] : null;
|
|
205
|
+
}
|
|
206
|
+
setWritePolicy(policy) {
|
|
207
|
+
this.writePolicy = policy;
|
|
208
|
+
}
|
|
159
209
|
/**
|
|
160
210
|
* Whether columns may be mapped to a brand-new field created during import.
|
|
161
211
|
* Driven purely by the presence of the `updateSchema` callback — providing the
|
|
@@ -175,24 +225,41 @@ export class ImportState {
|
|
|
175
225
|
this.fileName = '';
|
|
176
226
|
this.csvHeaders = [];
|
|
177
227
|
this.csvRows = [];
|
|
228
|
+
this.orderedHeaders = [];
|
|
178
229
|
this.mappingByHeader = {};
|
|
230
|
+
this.writePolicy = WritePolicy.OVERWRITE;
|
|
179
231
|
this.processedCount = 0;
|
|
180
232
|
this.totalCount = 0;
|
|
181
233
|
this.result = {
|
|
182
234
|
succeeded: 0,
|
|
183
235
|
failed: 0,
|
|
184
|
-
errors: [],
|
|
185
236
|
};
|
|
186
237
|
this.isBackingUp = false;
|
|
187
238
|
this.isRestoring = false;
|
|
188
239
|
this.restoreFn = null;
|
|
189
240
|
this._cancelled = false;
|
|
190
|
-
this.
|
|
241
|
+
this._jobId = null;
|
|
191
242
|
}
|
|
192
243
|
async close() {
|
|
193
|
-
|
|
244
|
+
// While on the progress step we treat close as a cancel — terminate the job
|
|
245
|
+
// and restore the backup — UNLESS the import already succeeded. On the
|
|
246
|
+
// success path `startImport` lingers ~600ms on the completed bar with the
|
|
247
|
+
// step still 'progress'; closing during that window must NOT restore (it
|
|
248
|
+
// would undo a clean import). A successful close just closes the modal and
|
|
249
|
+
// lets the success path do its collection refresh.
|
|
250
|
+
if (this.currentStep === 'progress' && !this.importStatus.isSuccess) {
|
|
194
251
|
this._cancelled = true;
|
|
195
252
|
this._abortController?.abort();
|
|
253
|
+
// Stop the server-side job. It does not roll back rows already written —
|
|
254
|
+
// that's what the backup restore below is for.
|
|
255
|
+
if (this._jobId) {
|
|
256
|
+
try {
|
|
257
|
+
await this.hubJobClient.terminateJob(this._jobId);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error('Terminate import job failed', error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
196
263
|
if (!this.isBackingUp) {
|
|
197
264
|
await this._restoreBackup();
|
|
198
265
|
}
|
|
@@ -278,11 +345,27 @@ export class ImportState {
|
|
|
278
345
|
.slice(0, TYPE_SAMPLE_SIZE)
|
|
279
346
|
.map((row) => row[columnIndex] ?? '');
|
|
280
347
|
}
|
|
348
|
+
// Puts the `_id`-mapped column first (CAIRO-4418), preserving CSV order for the
|
|
349
|
+
// rest. Returns a fresh array so the snapshot doesn't alias `csvHeaders`.
|
|
350
|
+
_orderHeadersWithIdFirst(headers) {
|
|
351
|
+
const idHeader = this.idMappedHeader;
|
|
352
|
+
return idHeader != null
|
|
353
|
+
? [idHeader, ...headers.filter((header) => header !== idHeader)]
|
|
354
|
+
: [...headers];
|
|
355
|
+
}
|
|
281
356
|
applyParsedCsv(fileName, parsedCsv) {
|
|
282
357
|
this.fileName = fileName;
|
|
283
358
|
this.csvHeaders = parsedCsv.headers;
|
|
284
359
|
this.csvRows = parsedCsv.rows;
|
|
285
360
|
this.mappingByHeader = this.getDefaultMapping(parsedCsv.headers);
|
|
361
|
+
// Snapshot the row order for this file with the auto-mapped `_id` column
|
|
362
|
+
// first. Reads `idMappedHeader`, so it must run after `mappingByHeader` is
|
|
363
|
+
// set. Kept stable until the next file so mapping edits don't reorder rows.
|
|
364
|
+
this.orderedHeaders = this._orderHeadersWithIdFirst(parsedCsv.headers);
|
|
365
|
+
// A new file is a fresh import: start from the default policy so a prior
|
|
366
|
+
// file's Skip choice doesn't silently carry over (only the new-file path
|
|
367
|
+
// resets — remapping `_id` within the same file keeps the user's choice).
|
|
368
|
+
this.writePolicy = WritePolicy.OVERWRITE;
|
|
286
369
|
this.currentStep = 'configuration';
|
|
287
370
|
}
|
|
288
371
|
async onFileSelected(file) {
|
|
@@ -334,6 +417,7 @@ export class ImportState {
|
|
|
334
417
|
this.fileName = file.name;
|
|
335
418
|
this.csvHeaders = [];
|
|
336
419
|
this.csvRows = [];
|
|
420
|
+
this.orderedHeaders = [];
|
|
337
421
|
this.mappingByHeader = {};
|
|
338
422
|
this.currentStep = 'upload';
|
|
339
423
|
});
|
|
@@ -354,12 +438,11 @@ export class ImportState {
|
|
|
354
438
|
});
|
|
355
439
|
}
|
|
356
440
|
_showPartialSuccessToast() {
|
|
357
|
-
const failed = this._failedRowIndices.length;
|
|
358
441
|
this.showToast?.({
|
|
359
442
|
type: 'WARNING',
|
|
360
443
|
message: this.translate('cairo.import.partialSuccessToast', {
|
|
361
|
-
failed: String(failed),
|
|
362
|
-
imported: String(this.
|
|
444
|
+
failed: String(this.result.failed),
|
|
445
|
+
imported: String(this.result.succeeded),
|
|
363
446
|
}),
|
|
364
447
|
action: {
|
|
365
448
|
text: this.translate('cairo.import.downloadFailedRows'),
|
|
@@ -368,11 +451,66 @@ export class ImportState {
|
|
|
368
451
|
},
|
|
369
452
|
});
|
|
370
453
|
}
|
|
371
|
-
|
|
372
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Builds a plain, re-importable CSV of the rows the server rejected — the
|
|
456
|
+
* original headers plus just the failed rows — and hands it to `downloadCsv`.
|
|
457
|
+
* The failed rows come from the job's movement logs (which row failed), mapped
|
|
458
|
+
* back to the parsed `csvRows` we still hold in memory.
|
|
459
|
+
*/
|
|
460
|
+
async _downloadFailedRows() {
|
|
461
|
+
const jobId = this._jobId;
|
|
462
|
+
if (!jobId) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
let indices;
|
|
466
|
+
try {
|
|
467
|
+
indices = await this._fetchFailedRowIndices(jobId);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
console.error('Fetching failed rows report failed', error);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const failedRows = indices
|
|
474
|
+
.map((index) => this.csvRows[index])
|
|
475
|
+
.filter((row) => row != null);
|
|
476
|
+
if (failedRows.length === 0) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
373
479
|
const content = unparseCsv(this.csvHeaders, failedRows);
|
|
374
480
|
this.downloadCsv?.(content, `failed-${this.fileName || 'import.csv'}`);
|
|
375
481
|
}
|
|
482
|
+
// Pages through the job's movement logs and returns the `csvRows` indices of
|
|
483
|
+
// the rows that failed. data-hub's CSV decoder
|
|
484
|
+
// (wix-data-hub: packages/wix-data-hub-executor-c/src/plugins/formats/csv/
|
|
485
|
+
// csv-binary-decoder.ts) parses with the `csv-parse` library (`columns: true`)
|
|
486
|
+
// and sets `sourceItemId = "row ${++index + 1}"`, incrementing once **per
|
|
487
|
+
// record** — not per physical line. So N is the 1-based record number with
|
|
488
|
+
// the header consumed as columns: the first data record is `"row 2"` →
|
|
489
|
+
// `csvRows[0]`, i.e. `csvRows[N - 2]`. Because `csv-parse` is RFC-4180,
|
|
490
|
+
// quoted cells containing embedded newlines are a single record, so they do
|
|
491
|
+
// NOT shift the numbering. Out-of-range indices are guarded below.
|
|
492
|
+
async _fetchFailedRowIndices(jobId) {
|
|
493
|
+
const indices = [];
|
|
494
|
+
let cursor;
|
|
495
|
+
do {
|
|
496
|
+
const { logs, pagingMetadata } = await this.hubJobClient.listMovementLogs(jobId, cursor ? { cursor } : undefined);
|
|
497
|
+
for (const log of logs ?? []) {
|
|
498
|
+
if (!log.failure || log.sourceItemId == null) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const fileLine = Number(/\d+/.exec(log.sourceItemId)?.[0]);
|
|
502
|
+
const index = fileLine - 2;
|
|
503
|
+
if (Number.isInteger(index) &&
|
|
504
|
+
index >= 0 &&
|
|
505
|
+
index < this.csvRows.length) {
|
|
506
|
+
indices.push(index);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
cursor =
|
|
510
|
+
(pagingMetadata?.hasNext && pagingMetadata.cursors?.next) || undefined;
|
|
511
|
+
} while (cursor);
|
|
512
|
+
return indices;
|
|
513
|
+
}
|
|
376
514
|
setMapping(header, fieldId) {
|
|
377
515
|
this.mappingByHeader = {
|
|
378
516
|
...this.mappingByHeader,
|
|
@@ -387,53 +525,133 @@ export class ImportState {
|
|
|
387
525
|
[header]: { kind: 'new', fieldType },
|
|
388
526
|
};
|
|
389
527
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
528
|
+
/**
|
|
529
|
+
* Persists the result of the "Edit field" editor for a column's pending new
|
|
530
|
+
* field: the (possibly overridden) type, an optional display-name override,
|
|
531
|
+
* and the per-type configuration. No-op if the column isn't currently mapped
|
|
532
|
+
* to a new field.
|
|
533
|
+
*/
|
|
534
|
+
setNewFieldConfig(header, edit) {
|
|
535
|
+
const mapping = this.mappingByHeader[header];
|
|
536
|
+
if (mapping?.kind !== 'new') {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
this.mappingByHeader = {
|
|
540
|
+
...this.mappingByHeader,
|
|
541
|
+
[header]: {
|
|
542
|
+
kind: 'new',
|
|
543
|
+
fieldType: edit.fieldType,
|
|
544
|
+
...(edit.displayName != null && { displayName: edit.displayName }),
|
|
545
|
+
...(edit.config && { config: edit.config }),
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Marks which column's pending new field the field editor is currently
|
|
551
|
+
* editing, so a later `applyFieldEdit` (the editor's `onSubmit`) knows where to
|
|
552
|
+
* write the result.
|
|
553
|
+
*/
|
|
554
|
+
beginFieldEdit(header) {
|
|
555
|
+
this._editingHeader = header;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* `onSubmit` handler for the field editor: maps the edited field spec into the
|
|
559
|
+
* column being edited (set via `beginFieldEdit`). No-op if nothing is being
|
|
560
|
+
* edited.
|
|
561
|
+
*/
|
|
562
|
+
applyFieldEdit(field) {
|
|
563
|
+
if (this._editingHeader == null) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
this.setNewFieldConfig(this._editingHeader, toFieldEdit(field));
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Runs the server-side import: align the CSV to the resolved field keys,
|
|
570
|
+
* upload it, submit a data-movement job, then poll until terminal. Throws on
|
|
571
|
+
* a failed job so `startImport` surfaces the error state; partial success is
|
|
572
|
+
* reported via `result.failed` and the partial toast.
|
|
573
|
+
*/
|
|
574
|
+
async _runImportJob(resolvedMapping) {
|
|
575
|
+
const csv = rewriteParsedCsvForImport(this.csvHeaders, this.csvRows, resolvedMapping);
|
|
576
|
+
const file = new File([csv], this.fileName || 'import.csv', {
|
|
577
|
+
type: 'text/csv',
|
|
578
|
+
});
|
|
579
|
+
const fileId = await this.hubJobClient.uploadFile(file);
|
|
580
|
+
if (this._cancelled) {
|
|
581
|
+
return this.result;
|
|
582
|
+
}
|
|
583
|
+
const jobId = await this.hubJobClient.submitImportJob({
|
|
584
|
+
fileId,
|
|
585
|
+
name: this.fileName,
|
|
586
|
+
destination: this._destination(),
|
|
587
|
+
});
|
|
588
|
+
runInAction(() => {
|
|
589
|
+
this._jobId = jobId;
|
|
590
|
+
});
|
|
591
|
+
if (this._cancelled) {
|
|
592
|
+
// The user cancelled while `submitImportJob` was in flight, so `close()`
|
|
593
|
+
// saw no `_jobId` and couldn't terminate it. Terminate it here, otherwise
|
|
594
|
+
// the job keeps importing server-side after cancel (and would overwrite a
|
|
595
|
+
// restored backup).
|
|
596
|
+
await this.hubJobClient.terminateJob(jobId).catch(() => undefined);
|
|
597
|
+
return this.result;
|
|
598
|
+
}
|
|
599
|
+
const job = await pollImportJob((id) => this.hubJobClient.getJob(id), jobId, {
|
|
600
|
+
isCancelled: () => this._cancelled,
|
|
601
|
+
onProgress: (polled) => this._applyJobProgress(polled),
|
|
602
|
+
});
|
|
603
|
+
// `null` means cancelled mid-poll — leave whatever progress we have.
|
|
604
|
+
if (!job) {
|
|
605
|
+
return this.result;
|
|
606
|
+
}
|
|
607
|
+
if (job.status === JobStatus.FAILED) {
|
|
608
|
+
// The job ran server-side, so a failure arrives on a 200 (the poll
|
|
609
|
+
// succeeded; the job didn't) with no HTTP error to carry the application
|
|
610
|
+
// error. Build a synthetic, HTTP-error-shaped error (the error handler
|
|
611
|
+
// reads the code from `response.data.details.applicationError`) and hand
|
|
612
|
+
// the throwing operation to the consumer's `onJobError` so THEY wrap it
|
|
613
|
+
// with their own `withErrorHandler` + error-code map — `job.error` is the
|
|
614
|
+
// consumer's application error (their import business logic), so only they
|
|
615
|
+
// can map its codes, and the map stays coupled to the `withErrorHandler`
|
|
616
|
+
// call as everywhere else (e.g. `fetchData`). The map is attached to the
|
|
617
|
+
// error object, which the progress step resolves via `getResolvedError`.
|
|
618
|
+
// Without a handler the same error still throws, falling back to the
|
|
619
|
+
// generic message.
|
|
620
|
+
const error = Object.assign(new Error(`ImportJob.${job.error?.code ?? 'failed'}`), { response: { data: { details: { applicationError: job.error } } } });
|
|
621
|
+
const run = () => Promise.reject(error);
|
|
622
|
+
if (this.onJobError) {
|
|
623
|
+
// The consumer wraps `run` with `withErrorHandler`; it re-rejects with
|
|
624
|
+
// `error` (now carrying their map), which propagates from here.
|
|
625
|
+
await this.onJobError(run);
|
|
428
626
|
}
|
|
429
|
-
|
|
430
|
-
runInAction(() => {
|
|
431
|
-
this.processedCount = Math.min(processedRows, this.totalCount);
|
|
432
|
-
});
|
|
627
|
+
throw error;
|
|
433
628
|
}
|
|
434
|
-
this.
|
|
629
|
+
this._applyJobProgress(job);
|
|
435
630
|
return this.result;
|
|
436
631
|
}
|
|
632
|
+
// Destination: the consumer's Wix Data collection. `writePolicy` is sent only
|
|
633
|
+
// when a CSV column is mapped to `_id` (the only case collisions can occur);
|
|
634
|
+
// otherwise it's omitted and the server applies its default (upsert by `_id`).
|
|
635
|
+
_destination() {
|
|
636
|
+
return {
|
|
637
|
+
wixDataCollection: {
|
|
638
|
+
collectionId: this.collectionId,
|
|
639
|
+
...(this.idMappedHeader != null && { writePolicy: this.writePolicy }),
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
_applyJobProgress(job) {
|
|
644
|
+
const progress = job.progress;
|
|
645
|
+
const succeeded = progress?.itemsSuccessful ?? 0;
|
|
646
|
+
const failed = progress?.itemsRejected ?? 0;
|
|
647
|
+
const current = progress?.current ?? succeeded + failed;
|
|
648
|
+
// `totalCount` is fixed up front in `startImport` from the CSV row count
|
|
649
|
+
// (one uploaded row per CSV row); polling only advances `processedCount`.
|
|
650
|
+
runInAction(() => {
|
|
651
|
+
this.processedCount = Math.min(current, this.totalCount);
|
|
652
|
+
this.result = { succeeded, failed };
|
|
653
|
+
});
|
|
654
|
+
}
|
|
437
655
|
async _runBackup() {
|
|
438
656
|
const backup = this.backup;
|
|
439
657
|
if (!backup) {
|
|
@@ -494,10 +712,18 @@ export class ImportState {
|
|
|
494
712
|
resolved[header] = mapping.fieldId;
|
|
495
713
|
}
|
|
496
714
|
else if (this.updateSchema) {
|
|
497
|
-
|
|
715
|
+
// The user's name override (from the field editor) seeds the unique
|
|
716
|
+
// name/key derivation; otherwise fall back to the column header.
|
|
717
|
+
const baseName = mapping.displayName?.trim() || header;
|
|
718
|
+
const { displayName, key } = uniqueFieldNameAndKey(baseName, usedNames, usedKeys);
|
|
498
719
|
usedKeys.add(key);
|
|
499
720
|
usedNames.add(displayName);
|
|
500
|
-
newFields.push({
|
|
721
|
+
newFields.push({
|
|
722
|
+
key,
|
|
723
|
+
type: mapping.fieldType,
|
|
724
|
+
displayName,
|
|
725
|
+
...(mapping.config && { config: mapping.config }),
|
|
726
|
+
});
|
|
501
727
|
resolved[header] = key;
|
|
502
728
|
}
|
|
503
729
|
else {
|
|
@@ -510,23 +736,14 @@ export class ImportState {
|
|
|
510
736
|
}
|
|
511
737
|
return resolved;
|
|
512
738
|
}
|
|
513
|
-
_createRowsBatches(rows) {
|
|
514
|
-
return rows.reduce((acc, row, index) => {
|
|
515
|
-
if (index % this.batchSize === 0) {
|
|
516
|
-
acc.push([]);
|
|
517
|
-
}
|
|
518
|
-
acc[acc.length - 1].push(row);
|
|
519
|
-
return acc;
|
|
520
|
-
}, []);
|
|
521
|
-
}
|
|
522
739
|
async startImport() {
|
|
523
740
|
if (!this.hasMappings) {
|
|
524
741
|
return;
|
|
525
742
|
}
|
|
526
743
|
this._cancelled = false;
|
|
527
744
|
this._abortController = new AbortController();
|
|
528
|
-
// One
|
|
529
|
-
//
|
|
745
|
+
// One uploaded row per CSV row (the rewrite drops columns, not rows), so the
|
|
746
|
+
// total is known up front from the parsed CSV and stays fixed through polling.
|
|
530
747
|
const totalCount = this.csvRows.length;
|
|
531
748
|
runInAction(() => {
|
|
532
749
|
this.currentStep = 'progress';
|
|
@@ -539,9 +756,8 @@ export class ImportState {
|
|
|
539
756
|
this.result = {
|
|
540
757
|
succeeded: 0,
|
|
541
758
|
failed: 0,
|
|
542
|
-
errors: [],
|
|
543
759
|
};
|
|
544
|
-
this.
|
|
760
|
+
this._jobId = null;
|
|
545
761
|
});
|
|
546
762
|
const backupFailed = await this._runBackup();
|
|
547
763
|
if (backupFailed) {
|
|
@@ -551,8 +767,8 @@ export class ImportState {
|
|
|
551
767
|
return;
|
|
552
768
|
}
|
|
553
769
|
// Create any new fields and resolve every column to its destination key
|
|
554
|
-
// before
|
|
555
|
-
//
|
|
770
|
+
// before uploading. A creation failure surfaces as the error state with a
|
|
771
|
+
// "Try again" CTA, like an upload/job failure.
|
|
556
772
|
let resolvedMapping;
|
|
557
773
|
try {
|
|
558
774
|
resolvedMapping = await this._resolveFieldMapping();
|
|
@@ -569,9 +785,7 @@ export class ImportState {
|
|
|
569
785
|
if (this._cancelled) {
|
|
570
786
|
return;
|
|
571
787
|
}
|
|
572
|
-
const
|
|
573
|
-
const batches = this._createRowsBatches(rows);
|
|
574
|
-
const promise = this._uploadBatches(batches);
|
|
788
|
+
const promise = this._runImportJob(resolvedMapping);
|
|
575
789
|
this.importStatus = {
|
|
576
790
|
...queryStatus.loading,
|
|
577
791
|
promise,
|
|
@@ -591,17 +805,16 @@ export class ImportState {
|
|
|
591
805
|
// briefly so the completion is perceptible even for very few rows. Skip
|
|
592
806
|
// the wait when there are failed rows — that path closes and surfaces a
|
|
593
807
|
// toast instead.
|
|
594
|
-
if (this.
|
|
808
|
+
if (this.result.failed === 0) {
|
|
595
809
|
await this._lingerOnCompletion();
|
|
596
810
|
if (this._cancelled) {
|
|
597
811
|
return;
|
|
598
812
|
}
|
|
599
813
|
}
|
|
600
814
|
runInAction(() => {
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
if (this._failedRowIndices.length > 0) {
|
|
815
|
+
// Rows the server rejected are reported via a toast (the downloadable
|
|
816
|
+
// failed-rows report is added by the failed-rows-report task).
|
|
817
|
+
if (this.result.failed > 0) {
|
|
605
818
|
this._showPartialSuccessToast();
|
|
606
819
|
}
|
|
607
820
|
this.importModalState.close();
|