@wix/bex-core 2.327.0 → 2.329.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 +4 -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/services/translations.js.map +1 -1
- package/dist/cjs/state/ImportState/ImportState.js +363 -109
- package/dist/cjs/state/ImportState/ImportState.js.map +1 -1
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js +536 -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/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/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 +4 -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/state/ImportState/ImportState.js +293 -101
- package/dist/esm/state/ImportState/ImportState.js.map +1 -1
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js +385 -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/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/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/services/translations.d.ts +1 -1
- package/dist/types/services/translations.d.ts.map +1 -1
- package/dist/types/state/ImportState/ImportState.d.ts +118 -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/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/package.json +4 -4
|
@@ -7,16 +7,60 @@ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/de
|
|
|
7
7
|
var _mobx = require("mobx");
|
|
8
8
|
var _model = require("../../model");
|
|
9
9
|
var _ConditionalModalState = require("../ConditionalModalState");
|
|
10
|
+
var _types = require("@wix/bex-utils/@wix/ambassador-hub-v1-job/types");
|
|
11
|
+
exports.WritePolicy = _types.WritePolicy;
|
|
10
12
|
var _parseCsv = require("./parseCsv");
|
|
13
|
+
var _rewriteCsv = require("./rewriteCsv");
|
|
14
|
+
var _hubJobClient = require("./hubJobClient");
|
|
15
|
+
var _importJobPoller = require("./importJobPoller");
|
|
11
16
|
var _detectFieldType = require("./detectFieldType");
|
|
12
|
-
var
|
|
17
|
+
var _fieldEdit = require("./fieldEdit");
|
|
18
|
+
/** Outcome of a server-side import job, derived from the job's progress. */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Optional snapshot taken before the import runs, returning a `restore` used to
|
|
22
|
+
* undo the import on cancel. See {@link BackupResult.restore} for the revert
|
|
23
|
+
* contract under upsert.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Consumer hook for resolving a failed import job's message. An import runs
|
|
28
|
+
* server-side, so a failure arrives on a 200 (the poll succeeds; the job
|
|
29
|
+
* doesn't) with no HTTP error to carry the application error. Cairo hands the
|
|
30
|
+
* consumer `run` — an operation that rejects with a synthetic, HTTP-error-shaped
|
|
31
|
+
* error carrying the job's application error — and the consumer wraps it with
|
|
32
|
+
* their own `withErrorHandler` + error-code map (their codes, their
|
|
33
|
+
* translations), keeping the map coupled to the `withErrorHandler` call as
|
|
34
|
+
* everywhere else (e.g. `fetchData`). The map is keyed by application-error code
|
|
35
|
+
* (the v2 errorHandler shape, like `getExportAsyncJobErrors`):
|
|
36
|
+
*
|
|
37
|
+
* onJobError: (run) =>
|
|
38
|
+
* errorHandler.withErrorHandler(run, {
|
|
39
|
+
* [jobErrorCode]: (error) => ({ message: translate(...) }),
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* `withErrorHandler` attaches the map to the error object and re-rejects it, so
|
|
43
|
+
* the progress step resolves a consumer-specific message via `getResolvedError`.
|
|
44
|
+
* When omitted, the error still surfaces but resolves to the generic message.
|
|
45
|
+
*/
|
|
46
|
+
|
|
13
47
|
// `NewFieldType`, `detectColumnType`, etc. are re-exported from the package via
|
|
14
48
|
// the `ImportState` barrel (`./index`), which also exports `./detectFieldType`.
|
|
15
49
|
|
|
50
|
+
// Re-export the write-policy enum so consumers (and the cairo UI) reference it
|
|
51
|
+
// from `@wix/bex-core` rather than the bundled ambassador path.
|
|
52
|
+
|
|
53
|
+
// Per-type configuration for a new field, captured from the "Edit field" editor
|
|
54
|
+
// (CAIRO-4398). All optional — an unedited field carries none and is created
|
|
55
|
+
// from detected defaults only. `displayName` is intentionally not here: the
|
|
56
|
+
// (possibly user-overridden) name is deduped and surfaced as
|
|
57
|
+
// `NewFieldDefinition.displayName`.
|
|
58
|
+
|
|
16
59
|
// A new field to add to the collection schema during import. `key` is decided by
|
|
17
|
-
// Cairo (derived from the column header
|
|
18
|
-
// column's values are written under, so the consumer
|
|
19
|
-
// exactly this key.
|
|
60
|
+
// Cairo (derived from the column header — or the user's name override — deduped)
|
|
61
|
+
// and is also the key the column's values are written under, so the consumer
|
|
62
|
+
// must add the field under exactly this key. `config` carries any per-type
|
|
63
|
+
// configuration the field editor produced; create the field with it.
|
|
20
64
|
|
|
21
65
|
/**
|
|
22
66
|
* Consumer-supplied callback that adds the given new fields to the collection
|
|
@@ -30,17 +74,23 @@ var _set = _interopRequireDefault(require("lodash/set"));
|
|
|
30
74
|
/**
|
|
31
75
|
* How a CSV column is mapped:
|
|
32
76
|
* - `existing` — into an existing collection field by id
|
|
33
|
-
* - `new` — into a new field created during import
|
|
77
|
+
* - `new` — into a new field created during import. `fieldType` is the detected
|
|
78
|
+
* type, or the type the user picked in the field editor. `displayName` and
|
|
79
|
+
* `config` are present only once the user edited the pending field via the
|
|
80
|
+
* "Edit field" editor — `displayName` overrides the header-derived name and
|
|
81
|
+
* `config` carries the per-type configuration.
|
|
34
82
|
* - `null` — don't import (skip)
|
|
35
83
|
*/
|
|
36
84
|
|
|
37
|
-
const DEFAULT_BATCH_SIZE = 100;
|
|
38
|
-
|
|
39
85
|
// How long to keep the completed progress bar (100%) on screen before closing
|
|
40
86
|
// the modal on a clean import. Without it, an import of very few rows finishes
|
|
41
87
|
// near-instantly and the modal appears to vanish without ever showing the bar
|
|
42
88
|
// reaching completion.
|
|
43
89
|
const COMPLETION_LINGER_MS = 600;
|
|
90
|
+
|
|
91
|
+
// The Wix Data collection's id field key. A CSV column mapped to this field lets
|
|
92
|
+
// the user choose how rows that collide on `_id` are written (writePolicy).
|
|
93
|
+
const ID_FIELD_KEY = '_id';
|
|
44
94
|
function normalize(value) {
|
|
45
95
|
return value.trim().toLowerCase();
|
|
46
96
|
}
|
|
@@ -76,23 +126,10 @@ function uniqueFieldNameAndKey(header, usedNames, usedKeys) {
|
|
|
76
126
|
displayName = `${header}-${suffix}`;
|
|
77
127
|
}
|
|
78
128
|
}
|
|
79
|
-
function toMappedRows(rows, headers, mappingByHeader) {
|
|
80
|
-
return rows.map(row => {
|
|
81
|
-
const mapped = {};
|
|
82
|
-
headers.forEach((header, index) => {
|
|
83
|
-
const mappedField = mappingByHeader[header];
|
|
84
|
-
if (mappedField == null) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
(0, _set.default)(mapped, mappedField, row[index] ?? '');
|
|
88
|
-
});
|
|
89
|
-
return mapped;
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
129
|
class ImportState {
|
|
93
130
|
constructor(props) {
|
|
94
131
|
(0, _defineProperty2.default)(this, "fields", void 0);
|
|
95
|
-
(0, _defineProperty2.default)(this, "
|
|
132
|
+
(0, _defineProperty2.default)(this, "collectionId", void 0);
|
|
96
133
|
(0, _defineProperty2.default)(this, "backup", void 0);
|
|
97
134
|
(0, _defineProperty2.default)(this, "updateSchema", void 0);
|
|
98
135
|
(0, _defineProperty2.default)(this, "collection", void 0);
|
|
@@ -101,7 +138,8 @@ class ImportState {
|
|
|
101
138
|
(0, _defineProperty2.default)(this, "errorHandler", void 0);
|
|
102
139
|
(0, _defineProperty2.default)(this, "showToast", void 0);
|
|
103
140
|
(0, _defineProperty2.default)(this, "downloadCsv", void 0);
|
|
104
|
-
(0, _defineProperty2.default)(this, "
|
|
141
|
+
(0, _defineProperty2.default)(this, "onJobError", void 0);
|
|
142
|
+
(0, _defineProperty2.default)(this, "hubJobClient", void 0);
|
|
105
143
|
(0, _defineProperty2.default)(this, "_detectColumnType", void 0);
|
|
106
144
|
(0, _defineProperty2.default)(this, "discardChangesModal", new _ConditionalModalState.ConditionalModalState());
|
|
107
145
|
(0, _defineProperty2.default)(this, "currentStep", 'upload');
|
|
@@ -112,25 +150,36 @@ class ImportState {
|
|
|
112
150
|
(0, _defineProperty2.default)(this, "csvHeaders", []);
|
|
113
151
|
(0, _defineProperty2.default)(this, "csvRows", []);
|
|
114
152
|
(0, _defineProperty2.default)(this, "mappingByHeader", {});
|
|
153
|
+
// How rows that collide on `_id` are written. Only meaningful (and only sent
|
|
154
|
+
// to the server) when a CSV column is mapped to the `_id` field — see
|
|
155
|
+
// `idMappedHeader` and `_destination`. Defaults to the server default
|
|
156
|
+
// (`OVERWRITE` = upsert by `_id`).
|
|
157
|
+
//
|
|
158
|
+
// Retention: the choice is deliberately kept while editing mappings within a
|
|
159
|
+
// file — remapping `_id` away and back keeps the user's selection rather than
|
|
160
|
+
// silently reverting it. It resets to the default only on a *new file* upload
|
|
161
|
+
// (`applyParsedCsv`) or a full `reset()`.
|
|
162
|
+
(0, _defineProperty2.default)(this, "writePolicy", _types.WritePolicy.OVERWRITE);
|
|
115
163
|
(0, _defineProperty2.default)(this, "processedCount", 0);
|
|
116
164
|
(0, _defineProperty2.default)(this, "totalCount", 0);
|
|
117
165
|
(0, _defineProperty2.default)(this, "result", {
|
|
118
166
|
succeeded: 0,
|
|
119
|
-
failed: 0
|
|
120
|
-
errors: []
|
|
167
|
+
failed: 0
|
|
121
168
|
});
|
|
122
169
|
(0, _defineProperty2.default)(this, "isBackingUp", false);
|
|
123
170
|
(0, _defineProperty2.default)(this, "isRestoring", false);
|
|
124
171
|
(0, _defineProperty2.default)(this, "restoreFn", null);
|
|
125
172
|
(0, _defineProperty2.default)(this, "_cancelled", false);
|
|
126
173
|
(0, _defineProperty2.default)(this, "_abortController", null);
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
|
|
174
|
+
// Id of the running data-movement job, set once submitted — used to cancel it
|
|
175
|
+
// (`terminateJob`) if the user closes mid-import.
|
|
176
|
+
(0, _defineProperty2.default)(this, "_jobId", null);
|
|
177
|
+
// The column whose pending new field the field editor is currently editing.
|
|
178
|
+
// Not observable — transient wiring between `beginFieldEdit` and the editor's
|
|
179
|
+
// `applyFieldEdit` callback, never rendered.
|
|
180
|
+
(0, _defineProperty2.default)(this, "_editingHeader", null);
|
|
132
181
|
this.fields = props.fields ?? [];
|
|
133
|
-
this.
|
|
182
|
+
this.collectionId = props.collectionId;
|
|
134
183
|
this.backup = props.backup;
|
|
135
184
|
this.updateSchema = props.updateSchema;
|
|
136
185
|
this.collection = props.collection;
|
|
@@ -139,8 +188,12 @@ class ImportState {
|
|
|
139
188
|
this.errorHandler = props.errorHandler;
|
|
140
189
|
this.showToast = props.showToast;
|
|
141
190
|
this.downloadCsv = props.downloadCsv;
|
|
142
|
-
this.
|
|
191
|
+
this.onJobError = props.onJobError;
|
|
143
192
|
this._detectColumnType = props.detectColumnType ?? _detectFieldType.detectColumnType;
|
|
193
|
+
this.hubJobClient = (0, _hubJobClient.createHubJobClient)({
|
|
194
|
+
httpClient: props.httpClient,
|
|
195
|
+
errorHandler: props.errorHandler
|
|
196
|
+
});
|
|
144
197
|
(0, _mobx.makeObservable)(this, {
|
|
145
198
|
currentStep: _mobx.observable.ref,
|
|
146
199
|
importStatus: _mobx.observable.ref,
|
|
@@ -148,6 +201,7 @@ class ImportState {
|
|
|
148
201
|
csvHeaders: _mobx.observable.ref,
|
|
149
202
|
csvRows: _mobx.observable.ref,
|
|
150
203
|
mappingByHeader: _mobx.observable.ref,
|
|
204
|
+
writePolicy: _mobx.observable.ref,
|
|
151
205
|
processedCount: _mobx.observable.ref,
|
|
152
206
|
totalCount: _mobx.observable.ref,
|
|
153
207
|
result: _mobx.observable.ref,
|
|
@@ -157,10 +211,15 @@ class ImportState {
|
|
|
157
211
|
hasMappings: _mobx.computed,
|
|
158
212
|
hasNoMappedData: _mobx.computed,
|
|
159
213
|
selectedFieldIds: _mobx.computed,
|
|
214
|
+
idMappedHeader: _mobx.computed,
|
|
215
|
+
setWritePolicy: _mobx.action.bound,
|
|
160
216
|
onUploadContinue: _mobx.action.bound,
|
|
161
217
|
goToUpload: _mobx.action.bound,
|
|
162
218
|
setMapping: _mobx.action.bound,
|
|
163
219
|
setNewFieldMapping: _mobx.action.bound,
|
|
220
|
+
setNewFieldConfig: _mobx.action.bound,
|
|
221
|
+
beginFieldEdit: _mobx.action.bound,
|
|
222
|
+
applyFieldEdit: _mobx.action.bound,
|
|
164
223
|
close: _mobx.action.bound,
|
|
165
224
|
onFileSelected: _mobx.action.bound,
|
|
166
225
|
startImport: _mobx.action.bound,
|
|
@@ -168,7 +227,15 @@ class ImportState {
|
|
|
168
227
|
});
|
|
169
228
|
}
|
|
170
229
|
init() {
|
|
171
|
-
return () =>
|
|
230
|
+
return () => {
|
|
231
|
+
// Unmounting mid-import stops the poll loop (it checks `_cancelled`) so it
|
|
232
|
+
// doesn't run to terminal holding references. We deliberately do NOT abort
|
|
233
|
+
// the AbortController: during a cancel-triggered restore it holds the
|
|
234
|
+
// restore controller, and aborting it would cancel an in-progress restore.
|
|
235
|
+
// The server-side job is also left running — an unmount isn't an explicit
|
|
236
|
+
// user cancel.
|
|
237
|
+
this._cancelled = true;
|
|
238
|
+
};
|
|
172
239
|
}
|
|
173
240
|
get hasParsedRows() {
|
|
174
241
|
return this.csvHeaders.length > 0 && this.csvRows.length > 0;
|
|
@@ -200,6 +267,20 @@ class ImportState {
|
|
|
200
267
|
return new Set(Object.values(this.mappingByHeader).filter(m => (m == null ? void 0 : m.kind) === 'existing').map(m => m.fieldId));
|
|
201
268
|
}
|
|
202
269
|
|
|
270
|
+
/**
|
|
271
|
+
* The CSV header currently mapped to the collection's `_id` field, or `null`
|
|
272
|
+
* when no column targets it. Drives both the "When items share the same ID"
|
|
273
|
+
* control (only shown on this row) and whether `writePolicy` is sent at all —
|
|
274
|
+
* with no `_id` mapped, every row is a fresh insert and collisions can't occur.
|
|
275
|
+
*/
|
|
276
|
+
get idMappedHeader() {
|
|
277
|
+
const entry = Object.entries(this.mappingByHeader).find(([, mapping]) => (mapping == null ? void 0 : mapping.kind) === 'existing' && mapping.fieldId === ID_FIELD_KEY);
|
|
278
|
+
return entry ? entry[0] : null;
|
|
279
|
+
}
|
|
280
|
+
setWritePolicy(policy) {
|
|
281
|
+
this.writePolicy = policy;
|
|
282
|
+
}
|
|
283
|
+
|
|
203
284
|
/**
|
|
204
285
|
* Whether columns may be mapped to a brand-new field created during import.
|
|
205
286
|
* Driven purely by the presence of the `updateSchema` callback — providing the
|
|
@@ -221,24 +302,40 @@ class ImportState {
|
|
|
221
302
|
this.csvHeaders = [];
|
|
222
303
|
this.csvRows = [];
|
|
223
304
|
this.mappingByHeader = {};
|
|
305
|
+
this.writePolicy = _types.WritePolicy.OVERWRITE;
|
|
224
306
|
this.processedCount = 0;
|
|
225
307
|
this.totalCount = 0;
|
|
226
308
|
this.result = {
|
|
227
309
|
succeeded: 0,
|
|
228
|
-
failed: 0
|
|
229
|
-
errors: []
|
|
310
|
+
failed: 0
|
|
230
311
|
};
|
|
231
312
|
this.isBackingUp = false;
|
|
232
313
|
this.isRestoring = false;
|
|
233
314
|
this.restoreFn = null;
|
|
234
315
|
this._cancelled = false;
|
|
235
|
-
this.
|
|
316
|
+
this._jobId = null;
|
|
236
317
|
}
|
|
237
318
|
async close() {
|
|
238
|
-
|
|
319
|
+
// While on the progress step we treat close as a cancel — terminate the job
|
|
320
|
+
// and restore the backup — UNLESS the import already succeeded. On the
|
|
321
|
+
// success path `startImport` lingers ~600ms on the completed bar with the
|
|
322
|
+
// step still 'progress'; closing during that window must NOT restore (it
|
|
323
|
+
// would undo a clean import). A successful close just closes the modal and
|
|
324
|
+
// lets the success path do its collection refresh.
|
|
325
|
+
if (this.currentStep === 'progress' && !this.importStatus.isSuccess) {
|
|
239
326
|
var _this$_abortControlle2;
|
|
240
327
|
this._cancelled = true;
|
|
241
328
|
(_this$_abortControlle2 = this._abortController) == null || _this$_abortControlle2.abort();
|
|
329
|
+
|
|
330
|
+
// Stop the server-side job. It does not roll back rows already written —
|
|
331
|
+
// that's what the backup restore below is for.
|
|
332
|
+
if (this._jobId) {
|
|
333
|
+
try {
|
|
334
|
+
await this.hubJobClient.terminateJob(this._jobId);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('Terminate import job failed', error);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
242
339
|
if (!this.isBackingUp) {
|
|
243
340
|
await this._restoreBackup();
|
|
244
341
|
}
|
|
@@ -330,6 +427,10 @@ class ImportState {
|
|
|
330
427
|
this.csvHeaders = parsedCsv.headers;
|
|
331
428
|
this.csvRows = parsedCsv.rows;
|
|
332
429
|
this.mappingByHeader = this.getDefaultMapping(parsedCsv.headers);
|
|
430
|
+
// A new file is a fresh import: start from the default policy so a prior
|
|
431
|
+
// file's Skip choice doesn't silently carry over (only the new-file path
|
|
432
|
+
// resets — remapping `_id` within the same file keeps the user's choice).
|
|
433
|
+
this.writePolicy = _types.WritePolicy.OVERWRITE;
|
|
333
434
|
this.currentStep = 'configuration';
|
|
334
435
|
}
|
|
335
436
|
async onFileSelected(file) {
|
|
@@ -404,12 +505,11 @@ class ImportState {
|
|
|
404
505
|
}
|
|
405
506
|
_showPartialSuccessToast() {
|
|
406
507
|
var _this$showToast3;
|
|
407
|
-
const failed = this._failedRowIndices.length;
|
|
408
508
|
(_this$showToast3 = this.showToast) == null || _this$showToast3.call(this, {
|
|
409
509
|
type: 'WARNING',
|
|
410
510
|
message: this.translate('cairo.import.partialSuccessToast', {
|
|
411
|
-
failed: String(failed),
|
|
412
|
-
imported: String(this.
|
|
511
|
+
failed: String(this.result.failed),
|
|
512
|
+
imported: String(this.result.succeeded)
|
|
413
513
|
}),
|
|
414
514
|
action: {
|
|
415
515
|
text: this.translate('cairo.import.downloadFailedRows'),
|
|
@@ -418,12 +518,70 @@ class ImportState {
|
|
|
418
518
|
}
|
|
419
519
|
});
|
|
420
520
|
}
|
|
421
|
-
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Builds a plain, re-importable CSV of the rows the server rejected — the
|
|
524
|
+
* original headers plus just the failed rows — and hands it to `downloadCsv`.
|
|
525
|
+
* The failed rows come from the job's movement logs (which row failed), mapped
|
|
526
|
+
* back to the parsed `csvRows` we still hold in memory.
|
|
527
|
+
*/
|
|
528
|
+
async _downloadFailedRows() {
|
|
422
529
|
var _this$downloadCsv;
|
|
423
|
-
const
|
|
530
|
+
const jobId = this._jobId;
|
|
531
|
+
if (!jobId) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
let indices;
|
|
535
|
+
try {
|
|
536
|
+
indices = await this._fetchFailedRowIndices(jobId);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error('Fetching failed rows report failed', error);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const failedRows = indices.map(index => this.csvRows[index]).filter(row => row != null);
|
|
542
|
+
if (failedRows.length === 0) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
424
545
|
const content = (0, _parseCsv.unparseCsv)(this.csvHeaders, failedRows);
|
|
425
546
|
(_this$downloadCsv = this.downloadCsv) == null || _this$downloadCsv.call(this, content, `failed-${this.fileName || 'import.csv'}`);
|
|
426
547
|
}
|
|
548
|
+
|
|
549
|
+
// Pages through the job's movement logs and returns the `csvRows` indices of
|
|
550
|
+
// the rows that failed. data-hub's CSV decoder
|
|
551
|
+
// (wix-data-hub: packages/wix-data-hub-executor-c/src/plugins/formats/csv/
|
|
552
|
+
// csv-binary-decoder.ts) parses with the `csv-parse` library (`columns: true`)
|
|
553
|
+
// and sets `sourceItemId = "row ${++index + 1}"`, incrementing once **per
|
|
554
|
+
// record** — not per physical line. So N is the 1-based record number with
|
|
555
|
+
// the header consumed as columns: the first data record is `"row 2"` →
|
|
556
|
+
// `csvRows[0]`, i.e. `csvRows[N - 2]`. Because `csv-parse` is RFC-4180,
|
|
557
|
+
// quoted cells containing embedded newlines are a single record, so they do
|
|
558
|
+
// NOT shift the numbering. Out-of-range indices are guarded below.
|
|
559
|
+
async _fetchFailedRowIndices(jobId) {
|
|
560
|
+
const indices = [];
|
|
561
|
+
let cursor;
|
|
562
|
+
do {
|
|
563
|
+
var _pagingMetadata$curso;
|
|
564
|
+
const {
|
|
565
|
+
logs,
|
|
566
|
+
pagingMetadata
|
|
567
|
+
} = await this.hubJobClient.listMovementLogs(jobId, cursor ? {
|
|
568
|
+
cursor
|
|
569
|
+
} : undefined);
|
|
570
|
+
for (const log of logs ?? []) {
|
|
571
|
+
var _exec;
|
|
572
|
+
if (!log.failure || log.sourceItemId == null) {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const fileLine = Number((_exec = /\d+/.exec(log.sourceItemId)) == null ? void 0 : _exec[0]);
|
|
576
|
+
const index = fileLine - 2;
|
|
577
|
+
if (Number.isInteger(index) && index >= 0 && index < this.csvRows.length) {
|
|
578
|
+
indices.push(index);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
cursor = (pagingMetadata == null ? void 0 : pagingMetadata.hasNext) && ((_pagingMetadata$curso = pagingMetadata.cursors) == null ? void 0 : _pagingMetadata$curso.next) || undefined;
|
|
582
|
+
} while (cursor);
|
|
583
|
+
return indices;
|
|
584
|
+
}
|
|
427
585
|
setMapping(header, fieldId) {
|
|
428
586
|
this.mappingByHeader = {
|
|
429
587
|
...this.mappingByHeader,
|
|
@@ -444,56 +602,158 @@ class ImportState {
|
|
|
444
602
|
}
|
|
445
603
|
};
|
|
446
604
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Persists the result of the "Edit field" editor for a column's pending new
|
|
608
|
+
* field: the (possibly overridden) type, an optional display-name override,
|
|
609
|
+
* and the per-type configuration. No-op if the column isn't currently mapped
|
|
610
|
+
* to a new field.
|
|
611
|
+
*/
|
|
612
|
+
setNewFieldConfig(header, edit) {
|
|
613
|
+
const mapping = this.mappingByHeader[header];
|
|
614
|
+
if ((mapping == null ? void 0 : mapping.kind) !== 'new') {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.mappingByHeader = {
|
|
618
|
+
...this.mappingByHeader,
|
|
619
|
+
[header]: {
|
|
620
|
+
kind: 'new',
|
|
621
|
+
fieldType: edit.fieldType,
|
|
622
|
+
...(edit.displayName != null && {
|
|
623
|
+
displayName: edit.displayName
|
|
624
|
+
}),
|
|
625
|
+
...(edit.config && {
|
|
626
|
+
config: edit.config
|
|
627
|
+
})
|
|
454
628
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const batchResult = await this.bulkUploadItems(batch);
|
|
458
|
-
receivedOkResponse = true;
|
|
459
|
-
this.result = {
|
|
460
|
-
succeeded: this.result.succeeded + batchResult.succeeded,
|
|
461
|
-
failed: this.result.failed + batchResult.failed,
|
|
462
|
-
errors: [...this.result.errors, ...batchResult.errors]
|
|
463
|
-
};
|
|
629
|
+
};
|
|
630
|
+
}
|
|
464
631
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
632
|
+
/**
|
|
633
|
+
* Marks which column's pending new field the field editor is currently
|
|
634
|
+
* editing, so a later `applyFieldEdit` (the editor's `onSubmit`) knows where to
|
|
635
|
+
* write the result.
|
|
636
|
+
*/
|
|
637
|
+
beginFieldEdit(header) {
|
|
638
|
+
this._editingHeader = header;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* `onSubmit` handler for the field editor: maps the edited field spec into the
|
|
643
|
+
* column being edited (set via `beginFieldEdit`). No-op if nothing is being
|
|
644
|
+
* edited.
|
|
645
|
+
*/
|
|
646
|
+
applyFieldEdit(field) {
|
|
647
|
+
if (this._editingHeader == null) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.setNewFieldConfig(this._editingHeader, (0, _fieldEdit.toFieldEdit)(field));
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Runs the server-side import: align the CSV to the resolved field keys,
|
|
655
|
+
* upload it, submit a data-movement job, then poll until terminal. Throws on
|
|
656
|
+
* a failed job so `startImport` surfaces the error state; partial success is
|
|
657
|
+
* reported via `result.failed` and the partial toast.
|
|
658
|
+
*/
|
|
659
|
+
async _runImportJob(resolvedMapping) {
|
|
660
|
+
const csv = (0, _rewriteCsv.rewriteParsedCsvForImport)(this.csvHeaders, this.csvRows, resolvedMapping);
|
|
661
|
+
const file = new File([csv], this.fileName || 'import.csv', {
|
|
662
|
+
type: 'text/csv'
|
|
663
|
+
});
|
|
664
|
+
const fileId = await this.hubJobClient.uploadFile(file);
|
|
665
|
+
if (this._cancelled) {
|
|
666
|
+
return this.result;
|
|
667
|
+
}
|
|
668
|
+
const jobId = await this.hubJobClient.submitImportJob({
|
|
669
|
+
fileId,
|
|
670
|
+
name: this.fileName,
|
|
671
|
+
destination: this._destination()
|
|
672
|
+
});
|
|
673
|
+
(0, _mobx.runInAction)(() => {
|
|
674
|
+
this._jobId = jobId;
|
|
675
|
+
});
|
|
676
|
+
if (this._cancelled) {
|
|
677
|
+
// The user cancelled while `submitImportJob` was in flight, so `close()`
|
|
678
|
+
// saw no `_jobId` and couldn't terminate it. Terminate it here, otherwise
|
|
679
|
+
// the job keeps importing server-side after cancel (and would overwrite a
|
|
680
|
+
// restored backup).
|
|
681
|
+
await this.hubJobClient.terminateJob(jobId).catch(() => undefined);
|
|
682
|
+
return this.result;
|
|
683
|
+
}
|
|
684
|
+
const job = await (0, _importJobPoller.pollImportJob)(id => this.hubJobClient.getJob(id), jobId, {
|
|
685
|
+
isCancelled: () => this._cancelled,
|
|
686
|
+
onProgress: polled => this._applyJobProgress(polled)
|
|
687
|
+
});
|
|
479
688
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
689
|
+
// `null` means cancelled mid-poll — leave whatever progress we have.
|
|
690
|
+
if (!job) {
|
|
691
|
+
return this.result;
|
|
692
|
+
}
|
|
693
|
+
if (job.status === _types.JobStatus.FAILED) {
|
|
694
|
+
var _job$error;
|
|
695
|
+
// The job ran server-side, so a failure arrives on a 200 (the poll
|
|
696
|
+
// succeeded; the job didn't) with no HTTP error to carry the application
|
|
697
|
+
// error. Build a synthetic, HTTP-error-shaped error (the error handler
|
|
698
|
+
// reads the code from `response.data.details.applicationError`) and hand
|
|
699
|
+
// the throwing operation to the consumer's `onJobError` so THEY wrap it
|
|
700
|
+
// with their own `withErrorHandler` + error-code map — `job.error` is the
|
|
701
|
+
// consumer's application error (their import business logic), so only they
|
|
702
|
+
// can map its codes, and the map stays coupled to the `withErrorHandler`
|
|
703
|
+
// call as everywhere else (e.g. `fetchData`). The map is attached to the
|
|
704
|
+
// error object, which the progress step resolves via `getResolvedError`.
|
|
705
|
+
// Without a handler the same error still throws, falling back to the
|
|
706
|
+
// generic message.
|
|
707
|
+
const error = Object.assign(new Error(`ImportJob.${((_job$error = job.error) == null ? void 0 : _job$error.code) ?? 'failed'}`), {
|
|
708
|
+
response: {
|
|
709
|
+
data: {
|
|
710
|
+
details: {
|
|
711
|
+
applicationError: job.error
|
|
712
|
+
}
|
|
713
|
+
}
|
|
486
714
|
}
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
489
|
-
processedRows += batch.length;
|
|
490
|
-
(0, _mobx.runInAction)(() => {
|
|
491
|
-
this.processedCount = Math.min(processedRows, this.totalCount);
|
|
492
715
|
});
|
|
716
|
+
const run = () => Promise.reject(error);
|
|
717
|
+
if (this.onJobError) {
|
|
718
|
+
// The consumer wraps `run` with `withErrorHandler`; it re-rejects with
|
|
719
|
+
// `error` (now carrying their map), which propagates from here.
|
|
720
|
+
await this.onJobError(run);
|
|
721
|
+
}
|
|
722
|
+
throw error;
|
|
493
723
|
}
|
|
494
|
-
this.
|
|
724
|
+
this._applyJobProgress(job);
|
|
495
725
|
return this.result;
|
|
496
726
|
}
|
|
727
|
+
|
|
728
|
+
// Destination: the consumer's Wix Data collection. `writePolicy` is sent only
|
|
729
|
+
// when a CSV column is mapped to `_id` (the only case collisions can occur);
|
|
730
|
+
// otherwise it's omitted and the server applies its default (upsert by `_id`).
|
|
731
|
+
_destination() {
|
|
732
|
+
return {
|
|
733
|
+
wixDataCollection: {
|
|
734
|
+
collectionId: this.collectionId,
|
|
735
|
+
...(this.idMappedHeader != null && {
|
|
736
|
+
writePolicy: this.writePolicy
|
|
737
|
+
})
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
_applyJobProgress(job) {
|
|
742
|
+
const progress = job.progress;
|
|
743
|
+
const succeeded = (progress == null ? void 0 : progress.itemsSuccessful) ?? 0;
|
|
744
|
+
const failed = (progress == null ? void 0 : progress.itemsRejected) ?? 0;
|
|
745
|
+
const current = (progress == null ? void 0 : progress.current) ?? succeeded + failed;
|
|
746
|
+
|
|
747
|
+
// `totalCount` is fixed up front in `startImport` from the CSV row count
|
|
748
|
+
// (one uploaded row per CSV row); polling only advances `processedCount`.
|
|
749
|
+
(0, _mobx.runInAction)(() => {
|
|
750
|
+
this.processedCount = Math.min(current, this.totalCount);
|
|
751
|
+
this.result = {
|
|
752
|
+
succeeded,
|
|
753
|
+
failed
|
|
754
|
+
};
|
|
755
|
+
});
|
|
756
|
+
}
|
|
497
757
|
async _runBackup() {
|
|
498
758
|
const backup = this.backup;
|
|
499
759
|
if (!backup) {
|
|
@@ -550,16 +810,23 @@ class ImportState {
|
|
|
550
810
|
} else if (mapping.kind === 'existing') {
|
|
551
811
|
resolved[header] = mapping.fieldId;
|
|
552
812
|
} else if (this.updateSchema) {
|
|
813
|
+
var _mapping$displayName;
|
|
814
|
+
// The user's name override (from the field editor) seeds the unique
|
|
815
|
+
// name/key derivation; otherwise fall back to the column header.
|
|
816
|
+
const baseName = ((_mapping$displayName = mapping.displayName) == null ? void 0 : _mapping$displayName.trim()) || header;
|
|
553
817
|
const {
|
|
554
818
|
displayName,
|
|
555
819
|
key
|
|
556
|
-
} = uniqueFieldNameAndKey(
|
|
820
|
+
} = uniqueFieldNameAndKey(baseName, usedNames, usedKeys);
|
|
557
821
|
usedKeys.add(key);
|
|
558
822
|
usedNames.add(displayName);
|
|
559
823
|
newFields.push({
|
|
560
824
|
key,
|
|
561
825
|
type: mapping.fieldType,
|
|
562
|
-
displayName
|
|
826
|
+
displayName,
|
|
827
|
+
...(mapping.config && {
|
|
828
|
+
config: mapping.config
|
|
829
|
+
})
|
|
563
830
|
});
|
|
564
831
|
resolved[header] = key;
|
|
565
832
|
} else {
|
|
@@ -572,15 +839,6 @@ class ImportState {
|
|
|
572
839
|
}
|
|
573
840
|
return resolved;
|
|
574
841
|
}
|
|
575
|
-
_createRowsBatches(rows) {
|
|
576
|
-
return rows.reduce((acc, row, index) => {
|
|
577
|
-
if (index % this.batchSize === 0) {
|
|
578
|
-
acc.push([]);
|
|
579
|
-
}
|
|
580
|
-
acc[acc.length - 1].push(row);
|
|
581
|
-
return acc;
|
|
582
|
-
}, []);
|
|
583
|
-
}
|
|
584
842
|
async startImport() {
|
|
585
843
|
if (!this.hasMappings) {
|
|
586
844
|
return;
|
|
@@ -588,8 +846,8 @@ class ImportState {
|
|
|
588
846
|
this._cancelled = false;
|
|
589
847
|
this._abortController = new AbortController();
|
|
590
848
|
|
|
591
|
-
// One
|
|
592
|
-
//
|
|
849
|
+
// One uploaded row per CSV row (the rewrite drops columns, not rows), so the
|
|
850
|
+
// total is known up front from the parsed CSV and stays fixed through polling.
|
|
593
851
|
const totalCount = this.csvRows.length;
|
|
594
852
|
(0, _mobx.runInAction)(() => {
|
|
595
853
|
this.currentStep = 'progress';
|
|
@@ -603,10 +861,9 @@ class ImportState {
|
|
|
603
861
|
};
|
|
604
862
|
this.result = {
|
|
605
863
|
succeeded: 0,
|
|
606
|
-
failed: 0
|
|
607
|
-
errors: []
|
|
864
|
+
failed: 0
|
|
608
865
|
};
|
|
609
|
-
this.
|
|
866
|
+
this._jobId = null;
|
|
610
867
|
});
|
|
611
868
|
const backupFailed = await this._runBackup();
|
|
612
869
|
if (backupFailed) {
|
|
@@ -617,8 +874,8 @@ class ImportState {
|
|
|
617
874
|
}
|
|
618
875
|
|
|
619
876
|
// Create any new fields and resolve every column to its destination key
|
|
620
|
-
// before
|
|
621
|
-
//
|
|
877
|
+
// before uploading. A creation failure surfaces as the error state with a
|
|
878
|
+
// "Try again" CTA, like an upload/job failure.
|
|
622
879
|
let resolvedMapping;
|
|
623
880
|
try {
|
|
624
881
|
resolvedMapping = await this._resolveFieldMapping();
|
|
@@ -634,9 +891,7 @@ class ImportState {
|
|
|
634
891
|
if (this._cancelled) {
|
|
635
892
|
return;
|
|
636
893
|
}
|
|
637
|
-
const
|
|
638
|
-
const batches = this._createRowsBatches(rows);
|
|
639
|
-
const promise = this._uploadBatches(batches);
|
|
894
|
+
const promise = this._runImportJob(resolvedMapping);
|
|
640
895
|
this.importStatus = {
|
|
641
896
|
..._model.queryStatus.loading,
|
|
642
897
|
promise
|
|
@@ -657,17 +912,16 @@ class ImportState {
|
|
|
657
912
|
// briefly so the completion is perceptible even for very few rows. Skip
|
|
658
913
|
// the wait when there are failed rows — that path closes and surfaces a
|
|
659
914
|
// toast instead.
|
|
660
|
-
if (this.
|
|
915
|
+
if (this.result.failed === 0) {
|
|
661
916
|
await this._lingerOnCompletion();
|
|
662
917
|
if (this._cancelled) {
|
|
663
918
|
return;
|
|
664
919
|
}
|
|
665
920
|
}
|
|
666
921
|
(0, _mobx.runInAction)(() => {
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
|
|
670
|
-
if (this._failedRowIndices.length > 0) {
|
|
922
|
+
// Rows the server rejected are reported via a toast (the downloadable
|
|
923
|
+
// failed-rows report is added by the failed-rows-report task).
|
|
924
|
+
if (this.result.failed > 0) {
|
|
671
925
|
this._showPartialSuccessToast();
|
|
672
926
|
}
|
|
673
927
|
this.importModalState.close();
|