@wix/bex-core 2.332.0 → 2.333.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 +3 -0
- package/dist/cjs/assets/locale/messages_bg.json +3 -0
- package/dist/cjs/assets/locale/messages_ca.json +3 -0
- package/dist/cjs/assets/locale/messages_cs.json +3 -0
- package/dist/cjs/assets/locale/messages_da.json +3 -0
- package/dist/cjs/assets/locale/messages_de.json +3 -0
- package/dist/cjs/assets/locale/messages_el.json +3 -0
- package/dist/cjs/assets/locale/messages_en.json +3 -0
- package/dist/cjs/assets/locale/messages_es.json +3 -0
- package/dist/cjs/assets/locale/messages_fi.json +3 -0
- package/dist/cjs/assets/locale/messages_fr.json +3 -0
- package/dist/cjs/assets/locale/messages_he.json +3 -0
- package/dist/cjs/assets/locale/messages_hi.json +3 -0
- package/dist/cjs/assets/locale/messages_hu.json +3 -0
- package/dist/cjs/assets/locale/messages_id.json +3 -0
- package/dist/cjs/assets/locale/messages_it.json +3 -0
- package/dist/cjs/assets/locale/messages_ja.json +3 -0
- package/dist/cjs/assets/locale/messages_ko.json +3 -0
- package/dist/cjs/assets/locale/messages_lt.json +3 -0
- package/dist/cjs/assets/locale/messages_ms.json +3 -0
- package/dist/cjs/assets/locale/messages_nl.json +3 -0
- package/dist/cjs/assets/locale/messages_no.json +3 -0
- package/dist/cjs/assets/locale/messages_pl.json +3 -0
- package/dist/cjs/assets/locale/messages_pt.json +3 -0
- package/dist/cjs/assets/locale/messages_ro.json +3 -0
- package/dist/cjs/assets/locale/messages_ru.json +3 -0
- package/dist/cjs/assets/locale/messages_sk.json +3 -0
- package/dist/cjs/assets/locale/messages_sl.json +3 -0
- package/dist/cjs/assets/locale/messages_sv.json +3 -0
- package/dist/cjs/assets/locale/messages_th.json +3 -0
- package/dist/cjs/assets/locale/messages_tl.json +3 -0
- package/dist/cjs/assets/locale/messages_tr.json +3 -0
- package/dist/cjs/assets/locale/messages_uk.json +3 -0
- package/dist/cjs/assets/locale/messages_vi.json +3 -0
- package/dist/cjs/assets/locale/messages_zh.json +3 -0
- package/dist/cjs/assets/locale/version_file +1 -1
- package/dist/cjs/assets/visual-e2e-locale/messages_visuale2e.json +29 -2
- package/dist/cjs/hooks/index.js +0 -6
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/index.js +0 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/translations.js.map +1 -1
- package/dist/cjs/state/CustomFieldModalState.js +12 -0
- package/dist/cjs/state/CustomFieldModalState.js.map +1 -1
- package/dist/cjs/state/ImportState/ImportState.js +324 -133
- package/dist/cjs/state/ImportState/ImportState.js.map +1 -1
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js +413 -79
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -1
- package/dist/cjs/state/ImportState/detectFieldType.js +23 -0
- package/dist/cjs/state/ImportState/detectFieldType.js.map +1 -1
- package/dist/cjs/state/ImportState/fieldEdit.js +49 -0
- package/dist/cjs/state/ImportState/fieldEdit.js.map +1 -1
- package/dist/cjs/testkit/createInMemorySchemaSource.js +186 -0
- package/dist/cjs/testkit/createInMemorySchemaSource.js.map +1 -0
- package/dist/cjs/testkit/hubJobImportMocks.js +18 -2
- package/dist/cjs/testkit/hubJobImportMocks.js.map +1 -1
- package/dist/cjs/testkit/index.js +6 -0
- package/dist/cjs/testkit/index.js.map +1 -1
- package/dist/cjs/types/SchemaSource.js +2 -1
- package/dist/cjs/types/SchemaSource.js.map +1 -1
- package/dist/esm/assets/locale/messages_ar.json +3 -0
- package/dist/esm/assets/locale/messages_bg.json +3 -0
- package/dist/esm/assets/locale/messages_ca.json +3 -0
- package/dist/esm/assets/locale/messages_cs.json +3 -0
- package/dist/esm/assets/locale/messages_da.json +3 -0
- package/dist/esm/assets/locale/messages_de.json +3 -0
- package/dist/esm/assets/locale/messages_el.json +3 -0
- package/dist/esm/assets/locale/messages_en.json +3 -0
- package/dist/esm/assets/locale/messages_es.json +3 -0
- package/dist/esm/assets/locale/messages_fi.json +3 -0
- package/dist/esm/assets/locale/messages_fr.json +3 -0
- package/dist/esm/assets/locale/messages_he.json +3 -0
- package/dist/esm/assets/locale/messages_hi.json +3 -0
- package/dist/esm/assets/locale/messages_hu.json +3 -0
- package/dist/esm/assets/locale/messages_id.json +3 -0
- package/dist/esm/assets/locale/messages_it.json +3 -0
- package/dist/esm/assets/locale/messages_ja.json +3 -0
- package/dist/esm/assets/locale/messages_ko.json +3 -0
- package/dist/esm/assets/locale/messages_lt.json +3 -0
- package/dist/esm/assets/locale/messages_ms.json +3 -0
- package/dist/esm/assets/locale/messages_nl.json +3 -0
- package/dist/esm/assets/locale/messages_no.json +3 -0
- package/dist/esm/assets/locale/messages_pl.json +3 -0
- package/dist/esm/assets/locale/messages_pt.json +3 -0
- package/dist/esm/assets/locale/messages_ro.json +3 -0
- package/dist/esm/assets/locale/messages_ru.json +3 -0
- package/dist/esm/assets/locale/messages_sk.json +3 -0
- package/dist/esm/assets/locale/messages_sl.json +3 -0
- package/dist/esm/assets/locale/messages_sv.json +3 -0
- package/dist/esm/assets/locale/messages_th.json +3 -0
- package/dist/esm/assets/locale/messages_tl.json +3 -0
- package/dist/esm/assets/locale/messages_tr.json +3 -0
- package/dist/esm/assets/locale/messages_uk.json +3 -0
- package/dist/esm/assets/locale/messages_vi.json +3 -0
- package/dist/esm/assets/locale/messages_zh.json +3 -0
- package/dist/esm/assets/locale/version_file +1 -1
- package/dist/esm/assets/visual-e2e-locale/messages_visuale2e.json +29 -2
- package/dist/esm/hooks/index.js +0 -1
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/state/CustomFieldModalState.js +8 -0
- package/dist/esm/state/CustomFieldModalState.js.map +1 -1
- package/dist/esm/state/ImportState/ImportState.js +246 -111
- package/dist/esm/state/ImportState/ImportState.js.map +1 -1
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js +293 -67
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -1
- package/dist/esm/state/ImportState/detectFieldType.js +19 -0
- package/dist/esm/state/ImportState/detectFieldType.js.map +1 -1
- package/dist/esm/state/ImportState/fieldEdit.js +44 -0
- package/dist/esm/state/ImportState/fieldEdit.js.map +1 -1
- package/dist/esm/testkit/createInMemorySchemaSource.js +140 -0
- package/dist/esm/testkit/createInMemorySchemaSource.js.map +1 -0
- package/dist/esm/testkit/hubJobImportMocks.js +17 -1
- package/dist/esm/testkit/hubJobImportMocks.js.map +1 -1
- package/dist/esm/testkit/index.js +1 -0
- package/dist/esm/testkit/index.js.map +1 -1
- package/dist/esm/types/SchemaSource.js.map +1 -1
- package/dist/types/hooks/index.d.ts +0 -1
- package/dist/types/hooks/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/services/translations.d.ts +7 -1
- package/dist/types/services/translations.d.ts.map +1 -1
- package/dist/types/state/CustomFieldModalState.d.ts +9 -0
- package/dist/types/state/CustomFieldModalState.d.ts.map +1 -1
- package/dist/types/state/ImportState/ImportState.d.ts +165 -61
- package/dist/types/state/ImportState/ImportState.d.ts.map +1 -1
- package/dist/types/state/ImportState/detectFieldType.d.ts +2 -0
- package/dist/types/state/ImportState/detectFieldType.d.ts.map +1 -1
- package/dist/types/state/ImportState/fieldEdit.d.ts +6 -0
- package/dist/types/state/ImportState/fieldEdit.d.ts.map +1 -1
- package/dist/types/testkit/WixPatternsContainer.driver.d.ts +4 -0
- package/dist/types/testkit/WixPatternsContainer.driver.d.ts.map +1 -1
- package/dist/types/testkit/createInMemorySchemaSource.d.ts +41 -0
- package/dist/types/testkit/createInMemorySchemaSource.d.ts.map +1 -0
- package/dist/types/testkit/hubJobImportMocks.d.ts +11 -1
- package/dist/types/testkit/hubJobImportMocks.d.ts.map +1 -1
- package/dist/types/testkit/index.d.ts +1 -0
- package/dist/types/testkit/index.d.ts.map +1 -1
- package/dist/types/types/SchemaSource.d.ts +6 -4
- package/dist/types/types/SchemaSource.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/cjs/hooks/useImportConfig.js +0 -12
- package/dist/cjs/hooks/useImportConfig.js.map +0 -1
- package/dist/esm/hooks/useImportConfig.js +0 -6
- package/dist/esm/hooks/useImportConfig.js.map +0 -1
- package/dist/types/hooks/useImportConfig.d.ts +0 -5
- package/dist/types/hooks/useImportConfig.d.ts.map +0 -1
|
@@ -17,33 +17,39 @@ var _detectFieldType = require("./detectFieldType");
|
|
|
17
17
|
var _fieldEdit = require("./fieldEdit");
|
|
18
18
|
/** Outcome of a server-side import job, derived from the job's progress. */
|
|
19
19
|
|
|
20
|
+
/** A rejected row: its index into `csvRows` and the server's failure reason. */
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Optional snapshot taken before the import runs, returning a `restore` used to
|
|
22
24
|
* undo the import on cancel. See {@link BackupResult.restore} for the revert
|
|
23
25
|
* contract under upsert.
|
|
24
26
|
*/
|
|
25
27
|
|
|
28
|
+
/** The application error from a failed import job, handed to a resolver. */
|
|
29
|
+
|
|
26
30
|
/**
|
|
27
|
-
* Consumer
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* (the v2 errorHandler shape, like `getExportAsyncJobErrors`):
|
|
31
|
+
* Consumer-supplied map from a failed import job's application-error code to a
|
|
32
|
+
* resolver that returns the message to show. An import runs server-side, so a
|
|
33
|
+
* failure arrives on a 200 (the poll succeeds; the job doesn't) with the
|
|
34
|
+
* application error in the job status. The consumer supplies only the
|
|
35
|
+
* code→message map (their codes, their translations) — ImportState applies it
|
|
36
|
+
* through the container's `errorHandler` internally, so the progress step can
|
|
37
|
+
* resolve the message via `getResolvedError`. The resolver receives the job's
|
|
38
|
+
* application error (its `code`/`description`/`data`):
|
|
36
39
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* })
|
|
40
|
+
* jobApplicationErrorMap: {
|
|
41
|
+
* [jobErrorCode]: (error) => ({ message: translate(..., error.data) }),
|
|
42
|
+
* }
|
|
41
43
|
*
|
|
42
|
-
*
|
|
43
|
-
* the progress step resolves a consumer-specific message via `getResolvedError`.
|
|
44
|
-
* When omitted, the error still surfaces but resolves to the generic message.
|
|
44
|
+
* When omitted, a failed job falls back to the generic error message.
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
|
+
// `required` is added on top of the ambassador descriptor (which carries only
|
|
48
|
+
// `id`/`header`): a field the destination mandates. Consumers set it — the
|
|
49
|
+
// schema-backed config forwards `validation.required`, manual/platformized
|
|
50
|
+
// configs set it per field — and the mapping step blocks import while any
|
|
51
|
+
// required field has no column mapped to it (CAIRO-4452).
|
|
52
|
+
|
|
47
53
|
// `NewFieldType`, `detectColumnType`, etc. are re-exported from the package via
|
|
48
54
|
// the `ImportState` barrel (`./index`), which also exports `./detectFieldType`.
|
|
49
55
|
|
|
@@ -56,8 +62,16 @@ var _fieldEdit = require("./fieldEdit");
|
|
|
56
62
|
// Per-type configuration for a new field, captured from the "Edit field" editor
|
|
57
63
|
// (CAIRO-4398). All optional — an unedited field carries none and is created
|
|
58
64
|
// from detected defaults only. `displayName` is intentionally not here: the
|
|
59
|
-
// (possibly user-overridden) name is deduped and surfaced as
|
|
60
|
-
// `
|
|
65
|
+
// (possibly user-overridden) name is deduped and surfaced as the field def's
|
|
66
|
+
// `displayName`.
|
|
67
|
+
//
|
|
68
|
+
// This is core-internal storage on the column mapping, shared by both sources —
|
|
69
|
+
// the CMS editor fills `validation`/`isPii`/`helpText`, the DataExtension editor
|
|
70
|
+
// fills `permissions`/`isPii`/`helpText`/`deType`. The two never cross: which
|
|
71
|
+
// subset is read is decided by the config's `dataSource` branch when the field
|
|
72
|
+
// def is built (`_resolveFieldMapping`). The consumer-facing create callbacks
|
|
73
|
+
// still receive distinct def types (`NewFieldDefinition` vs
|
|
74
|
+
// `NewDataExtensionFieldDef`), so this superset is not part of the public API.
|
|
61
75
|
|
|
62
76
|
// A new field to add to the collection schema during import. `key` is decided by
|
|
63
77
|
// Cairo (derived from the column header — or the user's name override — deduped)
|
|
@@ -80,6 +94,17 @@ var _fieldEdit = require("./fieldEdit");
|
|
|
80
94
|
* load.
|
|
81
95
|
*/
|
|
82
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Creates the pending new DataExtension custom fields before the rows are
|
|
99
|
+
* uploaded — the `destination` branch's counterpart to `updateSchema`. Its
|
|
100
|
+
* presence is the "create new field" opt-in for a DataExtension import. Receives
|
|
101
|
+
* the source-neutral pending columns (Cairo-decided keys + names + the edited
|
|
102
|
+
* config); the DataExtension source (`DataExtensionImportRef`) builds its own
|
|
103
|
+
* field shape from them, so no DataExtension-specific shape leaks into core.
|
|
104
|
+
* Optionally returns the updated fields map to merge into the table (CAIRO-4400);
|
|
105
|
+
* DataExtension refreshes its own schema in place, so returning `void` is fine.
|
|
106
|
+
*/
|
|
107
|
+
|
|
83
108
|
/**
|
|
84
109
|
* How a CSV column is mapped:
|
|
85
110
|
* - `existing` — into an existing collection field by id
|
|
@@ -91,18 +116,31 @@ var _fieldEdit = require("./fieldEdit");
|
|
|
91
116
|
* - `null` — don't import (skip)
|
|
92
117
|
*/
|
|
93
118
|
|
|
119
|
+
/** A `new`-mapped column with its deduped key + display name, handed to a
|
|
120
|
+
* source's create callback so it can build its own field shape. */
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Where the import writes and how it creates new fields, split per source. The
|
|
124
|
+
* discriminant (`collectionId` vs `destination`) makes the two mutually
|
|
125
|
+
* exclusive and pairs each target with its own create-fields callback, so a CMS
|
|
126
|
+
* target can't be paired with a DataExtension create callback (or vice versa).
|
|
127
|
+
* Each branch's create members are optional — a plain import supplies none and
|
|
128
|
+
* is mapping-only.
|
|
129
|
+
*/
|
|
130
|
+
|
|
94
131
|
/**
|
|
95
132
|
* The import inputs a consumer supplies. Satisfied both by a plain object (the
|
|
96
|
-
* manual case) and by a live
|
|
97
|
-
*
|
|
98
|
-
* schema load needn't resolve before `ImportState` is
|
|
99
|
-
* it by reference (never spreads it) so those getters
|
|
133
|
+
* manual case) and by a live reference (`useSchemaImport` in `@wix/patterns`, or
|
|
134
|
+
* the internally-built `DataExtensionImportRef`) whose members are getters read
|
|
135
|
+
* on demand — so the async schema load needn't resolve before `ImportState` is
|
|
136
|
+
* built. `ImportState` holds it by reference (never spreads it) so those getters
|
|
137
|
+
* stay live.
|
|
100
138
|
*/
|
|
101
139
|
|
|
102
|
-
// How long to
|
|
103
|
-
// the
|
|
104
|
-
//
|
|
105
|
-
//
|
|
140
|
+
// How long to hold the completed (100%) progress bar on screen before moving to
|
|
141
|
+
// the summary step (CAIRO-4476). The job reports counts only when it finishes,
|
|
142
|
+
// so the bar sits at 0 for the whole import and would otherwise flip straight to
|
|
143
|
+
// the summary without the user ever seeing it reach completion.
|
|
106
144
|
const COMPLETION_LINGER_MS = 600;
|
|
107
145
|
|
|
108
146
|
// The Wix Data collection's id field key. A CSV column mapped to this field lets
|
|
@@ -185,7 +223,9 @@ class ImportState {
|
|
|
185
223
|
(0, _defineProperty2.default)(this, "totalCount", 0);
|
|
186
224
|
(0, _defineProperty2.default)(this, "result", {
|
|
187
225
|
succeeded: 0,
|
|
188
|
-
failed: 0
|
|
226
|
+
failed: 0,
|
|
227
|
+
created: 0,
|
|
228
|
+
updated: 0
|
|
189
229
|
});
|
|
190
230
|
(0, _defineProperty2.default)(this, "isBackingUp", false);
|
|
191
231
|
(0, _defineProperty2.default)(this, "isRestoring", false);
|
|
@@ -195,10 +235,11 @@ class ImportState {
|
|
|
195
235
|
// Id of the running data-movement job, set once submitted — used to cancel it
|
|
196
236
|
// (`terminateJob`) if the user closes mid-import.
|
|
197
237
|
(0, _defineProperty2.default)(this, "_jobId", null);
|
|
198
|
-
// The column whose pending new field the field editor is currently editing
|
|
199
|
-
//
|
|
200
|
-
// `
|
|
201
|
-
|
|
238
|
+
// The column whose pending new field the field editor is currently editing, or
|
|
239
|
+
// `null` when the editor is closed. Observable so the source's field-editor
|
|
240
|
+
// component (rendered via `renderFieldEditor`) opens/prefills itself when this
|
|
241
|
+
// is set and closes when it clears — the editor is decoupled from the layout.
|
|
242
|
+
(0, _defineProperty2.default)(this, "editingHeader", null);
|
|
202
243
|
// Holds `updateSchema`'s returned fields map between field creation (pre-upload)
|
|
203
244
|
// and success. Kept private/non-observable so it can't update columns mid-import;
|
|
204
245
|
// it's promoted to the observable `createdFieldsMap` only on success.
|
|
@@ -233,7 +274,12 @@ class ImportState {
|
|
|
233
274
|
hasMappings: _mobx.computed,
|
|
234
275
|
hasNoMappedData: _mobx.computed,
|
|
235
276
|
selectedFieldIds: _mobx.computed,
|
|
277
|
+
missingRequiredFields: _mobx.computed,
|
|
278
|
+
hasMissingRequiredFields: _mobx.computed,
|
|
236
279
|
idMappedHeader: _mobx.computed,
|
|
280
|
+
editingHeader: _mobx.observable.ref,
|
|
281
|
+
editingMapping: _mobx.computed,
|
|
282
|
+
endFieldEdit: _mobx.action.bound,
|
|
237
283
|
setWritePolicy: _mobx.action.bound,
|
|
238
284
|
onUploadContinue: _mobx.action.bound,
|
|
239
285
|
goToUpload: _mobx.action.bound,
|
|
@@ -242,7 +288,10 @@ class ImportState {
|
|
|
242
288
|
setNewFieldConfig: _mobx.action.bound,
|
|
243
289
|
beginFieldEdit: _mobx.action.bound,
|
|
244
290
|
applyFieldEdit: _mobx.action.bound,
|
|
291
|
+
applyDataExtensionFieldEdit: _mobx.action.bound,
|
|
245
292
|
close: _mobx.action.bound,
|
|
293
|
+
done: _mobx.action.bound,
|
|
294
|
+
downloadFailedRows: _mobx.action.bound,
|
|
246
295
|
onFileSelected: _mobx.action.bound,
|
|
247
296
|
startImport: _mobx.action.bound,
|
|
248
297
|
reset: _mobx.action.bound
|
|
@@ -259,19 +308,36 @@ class ImportState {
|
|
|
259
308
|
|
|
260
309
|
/** Wix Data collection the import writes into (unset for platformized). */
|
|
261
310
|
get collectionId() {
|
|
262
|
-
|
|
311
|
+
const ds = this.config.dataSource;
|
|
312
|
+
return 'collectionId' in ds ? ds.collectionId : undefined;
|
|
263
313
|
}
|
|
264
314
|
get destination() {
|
|
265
|
-
|
|
315
|
+
const ds = this.config.dataSource;
|
|
316
|
+
return 'destination' in ds ? ds.destination : undefined;
|
|
266
317
|
}
|
|
267
318
|
get backup() {
|
|
268
319
|
return this.config.backup;
|
|
269
320
|
}
|
|
321
|
+
|
|
322
|
+
/** CMS create-fields callback, present only on the `collectionId` branch. */
|
|
270
323
|
get updateSchema() {
|
|
271
|
-
|
|
324
|
+
const ds = this.config.dataSource;
|
|
325
|
+
return 'collectionId' in ds ? ds.updateSchema : undefined;
|
|
272
326
|
}
|
|
273
|
-
|
|
274
|
-
|
|
327
|
+
|
|
328
|
+
/** DataExtension create-fields callback, present only on the `destination` branch. */
|
|
329
|
+
get createDataExtensionFields() {
|
|
330
|
+
const ds = this.config.dataSource;
|
|
331
|
+
return 'destination' in ds ? ds.createFields : undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** DataExtension field-management for the editor, on the `destination` branch. */
|
|
335
|
+
get dataExtensionFieldManagement() {
|
|
336
|
+
const ds = this.config.dataSource;
|
|
337
|
+
return 'destination' in ds ? ds.fieldManagement : undefined;
|
|
338
|
+
}
|
|
339
|
+
get jobApplicationErrorMap() {
|
|
340
|
+
return this.config.jobApplicationErrorMap;
|
|
275
341
|
}
|
|
276
342
|
|
|
277
343
|
// The collection's id-field key; a schema-backed config may report a non-`_id`
|
|
@@ -320,6 +386,27 @@ class ImportState {
|
|
|
320
386
|
return new Set(Object.values(this.mappingByHeader).filter(m => (m == null ? void 0 : m.kind) === 'existing').map(m => m.fieldId));
|
|
321
387
|
}
|
|
322
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Required destination fields that no CSV column is mapped to (CAIRO-4452).
|
|
391
|
+
* A required field is an existing collection field, so only an `existing`
|
|
392
|
+
* mapping can satisfy it — a "create new field" column can't. Covers both
|
|
393
|
+
* ways a required field ends up unmapped: the CSV has no matching column at
|
|
394
|
+
* all, or the user set its column to "don't import". Drives the footer
|
|
395
|
+
* indicator and blocks `startImport`.
|
|
396
|
+
*/
|
|
397
|
+
get missingRequiredFields() {
|
|
398
|
+
return this.fields.filter(field => field.required && field.id != null && !this.selectedFieldIds.has(field.id));
|
|
399
|
+
}
|
|
400
|
+
get hasMissingRequiredFields() {
|
|
401
|
+
return this.missingRequiredFields.length > 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Comma-separated display names of the missing required fields, for the block
|
|
405
|
+
// toast. Falls back to the field id when a field carries no header.
|
|
406
|
+
_missingRequiredFieldNames() {
|
|
407
|
+
return this.missingRequiredFields.map(field => field.header || field.id || '').filter(Boolean).join(', ');
|
|
408
|
+
}
|
|
409
|
+
|
|
323
410
|
/**
|
|
324
411
|
* The CSV header currently mapped to the collection's `_id` field, or `null`
|
|
325
412
|
* when no column targets it. Drives both the "When items share the same ID"
|
|
@@ -337,12 +424,13 @@ class ImportState {
|
|
|
337
424
|
|
|
338
425
|
/**
|
|
339
426
|
* Whether columns may be mapped to a brand-new field created during import.
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
427
|
+
* The opt-in is the presence of the active `dataSource` branch's create
|
|
428
|
+
* callback — `updateSchema` (CMS) or `createFields` (DataExtension). A source
|
|
429
|
+
* that can't create fields (e.g. no create permission) supplies neither, so
|
|
430
|
+
* columns fall back to "don't import" (see `getDefaultMapping`).
|
|
343
431
|
*/
|
|
344
432
|
get canCreateNewField() {
|
|
345
|
-
return this.
|
|
433
|
+
return this.updateSchema != null || this.createDataExtensionFields != null;
|
|
346
434
|
}
|
|
347
435
|
|
|
348
436
|
/**
|
|
@@ -375,7 +463,9 @@ class ImportState {
|
|
|
375
463
|
this.totalCount = 0;
|
|
376
464
|
this.result = {
|
|
377
465
|
succeeded: 0,
|
|
378
|
-
failed: 0
|
|
466
|
+
failed: 0,
|
|
467
|
+
created: 0,
|
|
468
|
+
updated: 0
|
|
379
469
|
};
|
|
380
470
|
this._pendingFieldsMap = undefined;
|
|
381
471
|
this.isBackingUp = false;
|
|
@@ -386,11 +476,10 @@ class ImportState {
|
|
|
386
476
|
}
|
|
387
477
|
async close() {
|
|
388
478
|
// While on the progress step we treat close as a cancel — terminate the job
|
|
389
|
-
// and restore the backup — UNLESS the import already succeeded. On
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
// lets the success path do its collection refresh.
|
|
479
|
+
// and restore the backup — UNLESS the import already succeeded. On success
|
|
480
|
+
// `startImport` moves straight to the 'summary' step, so a close from there
|
|
481
|
+
// just closes the modal (the summary's own `done` is the normal exit); this
|
|
482
|
+
// guard covers only closing mid-import.
|
|
394
483
|
if (this.currentStep === 'progress' && !this.importStatus.isSuccess) {
|
|
395
484
|
var _this$_abortControlle2;
|
|
396
485
|
this._cancelled = true;
|
|
@@ -414,6 +503,14 @@ class ImportState {
|
|
|
414
503
|
}
|
|
415
504
|
this.importModalState.close();
|
|
416
505
|
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Closes the summary step at the end of a completed import. The collection was
|
|
509
|
+
* already refreshed when the summary opened, so this only dismisses the modal.
|
|
510
|
+
*/
|
|
511
|
+
done() {
|
|
512
|
+
this.importModalState.close();
|
|
513
|
+
}
|
|
417
514
|
async _restoreBackup() {
|
|
418
515
|
const restoreFn = this.restoreFn;
|
|
419
516
|
if (!restoreFn) {
|
|
@@ -584,51 +681,44 @@ class ImportState {
|
|
|
584
681
|
}
|
|
585
682
|
});
|
|
586
683
|
}
|
|
587
|
-
_showPartialSuccessToast() {
|
|
588
|
-
var _this$showToast3;
|
|
589
|
-
(_this$showToast3 = this.showToast) == null || _this$showToast3.call(this, {
|
|
590
|
-
type: 'WARNING',
|
|
591
|
-
message: this.translate('cairo.import.partialSuccessToast', {
|
|
592
|
-
failed: String(this.result.failed),
|
|
593
|
-
imported: String(this.result.succeeded)
|
|
594
|
-
}),
|
|
595
|
-
action: {
|
|
596
|
-
text: this.translate('cairo.import.downloadFailedRows'),
|
|
597
|
-
onClick: () => this._downloadFailedRows(),
|
|
598
|
-
removeToastOnClick: true
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
684
|
|
|
603
685
|
/**
|
|
604
|
-
* Builds a
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
686
|
+
* Builds a re-importable CSV of the rows the server rejected and hands it to
|
|
687
|
+
* `downloadCsv`. Each failed row is prefixed with a leading "why it failed"
|
|
688
|
+
* column holding the server's error message, so the user can see and fix each
|
|
689
|
+
* reason. The failed rows (and their reasons) come from the job's movement
|
|
690
|
+
* logs, mapped back to the parsed `csvRows` we still hold in memory.
|
|
608
691
|
*/
|
|
609
|
-
async
|
|
692
|
+
async downloadFailedRows() {
|
|
610
693
|
var _this$downloadCsv;
|
|
611
694
|
const jobId = this._jobId;
|
|
612
695
|
if (!jobId) {
|
|
613
696
|
return;
|
|
614
697
|
}
|
|
615
|
-
let
|
|
698
|
+
let failures;
|
|
616
699
|
try {
|
|
617
|
-
|
|
700
|
+
failures = await this._fetchFailedRows(jobId);
|
|
618
701
|
} catch (error) {
|
|
619
702
|
console.error('Fetching failed rows report failed', error);
|
|
620
703
|
return;
|
|
621
704
|
}
|
|
622
|
-
const
|
|
623
|
-
|
|
705
|
+
const rows = failures.map(({
|
|
706
|
+
index,
|
|
707
|
+
reason
|
|
708
|
+
}) => {
|
|
709
|
+
const row = this.csvRows[index];
|
|
710
|
+
return row != null ? [reason, ...row] : null;
|
|
711
|
+
}).filter(row => row != null);
|
|
712
|
+
if (rows.length === 0) {
|
|
624
713
|
return;
|
|
625
714
|
}
|
|
626
|
-
const
|
|
715
|
+
const headers = [this.translate('cairo.import.failureReasonColumn'), ...this.csvHeaders];
|
|
716
|
+
const content = (0, _parseCsv.unparseCsv)(headers, rows);
|
|
627
717
|
(_this$downloadCsv = this.downloadCsv) == null || _this$downloadCsv.call(this, content, `failed-${this.fileName || 'import.csv'}`);
|
|
628
718
|
}
|
|
629
719
|
|
|
630
|
-
// Pages through the job's movement logs and returns
|
|
631
|
-
// the
|
|
720
|
+
// Pages through the job's movement logs and returns, for each row that failed,
|
|
721
|
+
// its `csvRows` index and the server's failure reason. data-hub's CSV decoder
|
|
632
722
|
// (wix-data-hub: packages/wix-data-hub-executor-c/src/plugins/formats/csv/
|
|
633
723
|
// csv-binary-decoder.ts) parses with the `csv-parse` library (`columns: true`)
|
|
634
724
|
// and sets `sourceItemId = "row ${++index + 1}"`, incrementing once **per
|
|
@@ -637,8 +727,8 @@ class ImportState {
|
|
|
637
727
|
// `csvRows[0]`, i.e. `csvRows[N - 2]`. Because `csv-parse` is RFC-4180,
|
|
638
728
|
// quoted cells containing embedded newlines are a single record, so they do
|
|
639
729
|
// NOT shift the numbering. Out-of-range indices are guarded below.
|
|
640
|
-
async
|
|
641
|
-
const
|
|
730
|
+
async _fetchFailedRows(jobId) {
|
|
731
|
+
const failures = [];
|
|
642
732
|
let cursor;
|
|
643
733
|
do {
|
|
644
734
|
var _pagingMetadata$curso;
|
|
@@ -656,12 +746,15 @@ class ImportState {
|
|
|
656
746
|
const fileLine = Number((_exec = /\d+/.exec(log.sourceItemId)) == null ? void 0 : _exec[0]);
|
|
657
747
|
const index = fileLine - 2;
|
|
658
748
|
if (Number.isInteger(index) && index >= 0 && index < this.csvRows.length) {
|
|
659
|
-
|
|
749
|
+
failures.push({
|
|
750
|
+
index,
|
|
751
|
+
reason: log.failure.description ?? ''
|
|
752
|
+
});
|
|
660
753
|
}
|
|
661
754
|
}
|
|
662
755
|
cursor = (pagingMetadata == null ? void 0 : pagingMetadata.hasNext) && ((_pagingMetadata$curso = pagingMetadata.cursors) == null ? void 0 : _pagingMetadata$curso.next) || undefined;
|
|
663
756
|
} while (cursor);
|
|
664
|
-
return
|
|
757
|
+
return failures;
|
|
665
758
|
}
|
|
666
759
|
setMapping(header, fieldId) {
|
|
667
760
|
this.mappingByHeader = {
|
|
@@ -711,24 +804,56 @@ class ImportState {
|
|
|
711
804
|
}
|
|
712
805
|
|
|
713
806
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
807
|
+
* Opens the field editor for a column's pending new field: sets
|
|
808
|
+
* `editingHeader`, which the source's editor component watches to open and
|
|
809
|
+
* prefill itself. No-op if the column isn't mapped to a new field.
|
|
717
810
|
*/
|
|
718
811
|
beginFieldEdit(header) {
|
|
719
|
-
|
|
812
|
+
var _this$mappingByHeader;
|
|
813
|
+
if (((_this$mappingByHeader = this.mappingByHeader[header]) == null ? void 0 : _this$mappingByHeader.kind) === 'new') {
|
|
814
|
+
this.editingHeader = header;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** Closes the field editor (clears `editingHeader`). */
|
|
819
|
+
endFieldEdit() {
|
|
820
|
+
this.editingHeader = null;
|
|
720
821
|
}
|
|
721
822
|
|
|
722
823
|
/**
|
|
723
|
-
* `
|
|
724
|
-
*
|
|
824
|
+
* The `new` mapping currently being edited, or `null` when the editor is
|
|
825
|
+
* closed. The editor component reads this to prefill its form.
|
|
826
|
+
*/
|
|
827
|
+
get editingMapping() {
|
|
828
|
+
if (this.editingHeader == null) {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
const mapping = this.mappingByHeader[this.editingHeader];
|
|
832
|
+
return (mapping == null ? void 0 : mapping.kind) === 'new' ? mapping : null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* `onSubmit` handler for the CMS field editor: maps the edited field spec into
|
|
837
|
+
* the column being edited (set via `beginFieldEdit`). No-op if nothing is being
|
|
725
838
|
* edited.
|
|
726
839
|
*/
|
|
727
840
|
applyFieldEdit(field) {
|
|
728
|
-
if (this.
|
|
841
|
+
if (this.editingHeader == null) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.setNewFieldConfig(this.editingHeader, (0, _fieldEdit.toFieldEdit)(field));
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* `onSubmit` handler for the DataExtension field editor: maps the edited
|
|
849
|
+
* DataExtension draft (which carries per-audience permissions) into the column
|
|
850
|
+
* being edited. The `destination`-branch counterpart to `applyFieldEdit`.
|
|
851
|
+
*/
|
|
852
|
+
applyDataExtensionFieldEdit(draft) {
|
|
853
|
+
if (this.editingHeader == null) {
|
|
729
854
|
return;
|
|
730
855
|
}
|
|
731
|
-
this.setNewFieldConfig(this.
|
|
856
|
+
this.setNewFieldConfig(this.editingHeader, (0, _fieldEdit.toDataExtensionFieldEdit)(draft));
|
|
732
857
|
}
|
|
733
858
|
|
|
734
859
|
/**
|
|
@@ -776,15 +901,15 @@ class ImportState {
|
|
|
776
901
|
// The job ran server-side, so a failure arrives on a 200 (the poll
|
|
777
902
|
// succeeded; the job didn't) with no HTTP error to carry the application
|
|
778
903
|
// error. Build a synthetic, HTTP-error-shaped error (the error handler
|
|
779
|
-
// reads the code from `response.data.details.applicationError`)
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
786
|
-
// Without a
|
|
787
|
-
// generic message.
|
|
904
|
+
// reads the code from `response.data.details.applicationError`), then
|
|
905
|
+
// resolve it through the consumer's per-code map via `withErrorHandler` —
|
|
906
|
+
// the same way as `fetchData` etc. `job.error` is the consumer's
|
|
907
|
+
// application error (their import business logic), so only they can map
|
|
908
|
+
// its codes; they supply just the code→message map and we do the
|
|
909
|
+
// wrapping. `withErrorHandler` attaches the map to the error and
|
|
910
|
+
// re-rejects it, so the progress step resolves a consumer-specific
|
|
911
|
+
// message via `getResolvedError`. Without a map the same error still
|
|
912
|
+
// throws, falling back to the generic message.
|
|
788
913
|
const error = Object.assign(new Error(`ImportJob.${((_job$error = job.error) == null ? void 0 : _job$error.code) ?? 'failed'}`), {
|
|
789
914
|
response: {
|
|
790
915
|
data: {
|
|
@@ -794,11 +919,17 @@ class ImportState {
|
|
|
794
919
|
}
|
|
795
920
|
}
|
|
796
921
|
});
|
|
797
|
-
const
|
|
798
|
-
if (
|
|
799
|
-
//
|
|
800
|
-
// `
|
|
801
|
-
|
|
922
|
+
const jobApplicationErrorMap = this.jobApplicationErrorMap;
|
|
923
|
+
if (jobApplicationErrorMap) {
|
|
924
|
+
// Adapt the consumer's code→message map into the error handler's
|
|
925
|
+
// `errorCodesMap`: each entry, when its code matches the job's
|
|
926
|
+
// application error, resolves with that error. `withErrorHandler`
|
|
927
|
+
// attaches the resolved message to `error` and re-rejects it, so the
|
|
928
|
+
// progress step surfaces it via `getResolvedError` — the consumer only
|
|
929
|
+
// supplies the map, not the wrapping.
|
|
930
|
+
const appError = job.error ?? {};
|
|
931
|
+
const errorCodesMap = Object.fromEntries(Object.entries(jobApplicationErrorMap).map(([code, resolve]) => [code, () => resolve(appError)]));
|
|
932
|
+
await this.errorHandler.withErrorHandler(() => Promise.reject(error), errorCodesMap);
|
|
802
933
|
}
|
|
803
934
|
throw error;
|
|
804
935
|
}
|
|
@@ -826,10 +957,16 @@ class ImportState {
|
|
|
826
957
|
};
|
|
827
958
|
}
|
|
828
959
|
_applyJobProgress(job) {
|
|
960
|
+
var _progress$itemsSucces, _progress$itemsSucces2;
|
|
829
961
|
const progress = job.progress;
|
|
830
962
|
const succeeded = (progress == null ? void 0 : progress.itemsSuccessful) ?? 0;
|
|
831
963
|
const failed = (progress == null ? void 0 : progress.itemsRejected) ?? 0;
|
|
832
964
|
const current = (progress == null ? void 0 : progress.current) ?? succeeded + failed;
|
|
965
|
+
// The server splits successful items into created (new) and updated
|
|
966
|
+
// (existing) — the summary step reports each. Absent on older jobs, in
|
|
967
|
+
// which case both stay 0 and only the total `succeeded` is shown.
|
|
968
|
+
const created = (progress == null || (_progress$itemsSucces = progress.itemsSuccessfulInfo) == null ? void 0 : _progress$itemsSucces.itemsCreated) ?? 0;
|
|
969
|
+
const updated = (progress == null || (_progress$itemsSucces2 = progress.itemsSuccessfulInfo) == null ? void 0 : _progress$itemsSucces2.itemsUpdated) ?? 0;
|
|
833
970
|
|
|
834
971
|
// `totalCount` is fixed up front in `startImport` from the CSV row count
|
|
835
972
|
// (one uploaded row per CSV row); polling only advances `processedCount`.
|
|
@@ -837,10 +974,15 @@ class ImportState {
|
|
|
837
974
|
this.processedCount = Math.min(current, this.totalCount);
|
|
838
975
|
this.result = {
|
|
839
976
|
succeeded,
|
|
840
|
-
failed
|
|
977
|
+
failed,
|
|
978
|
+
created,
|
|
979
|
+
updated
|
|
841
980
|
};
|
|
842
981
|
});
|
|
843
982
|
}
|
|
983
|
+
_lingerOnCompletion() {
|
|
984
|
+
return new Promise(resolve => setTimeout(resolve, COMPLETION_LINGER_MS));
|
|
985
|
+
}
|
|
844
986
|
async _runBackup() {
|
|
845
987
|
const backup = this.backup;
|
|
846
988
|
if (!backup) {
|
|
@@ -872,9 +1014,6 @@ class ImportState {
|
|
|
872
1014
|
});
|
|
873
1015
|
return false;
|
|
874
1016
|
}
|
|
875
|
-
_lingerOnCompletion() {
|
|
876
|
-
return new Promise(resolve => setTimeout(resolve, COMPLETION_LINGER_MS));
|
|
877
|
-
}
|
|
878
1017
|
|
|
879
1018
|
/**
|
|
880
1019
|
* Resolves every column to the final destination key used in the upload
|
|
@@ -885,18 +1024,21 @@ class ImportState {
|
|
|
885
1024
|
*/
|
|
886
1025
|
async _resolveFieldMapping() {
|
|
887
1026
|
const resolved = {};
|
|
888
|
-
|
|
1027
|
+
// Pending new columns with their deduped key + display name. Source-neutral;
|
|
1028
|
+
// the per-source field def is built from these in `_createPendingFields`.
|
|
1029
|
+
const pending = [];
|
|
889
1030
|
// Seed with existing field ids/display names so derived keys and names never
|
|
890
1031
|
// collide with them or with each other.
|
|
891
1032
|
const usedKeys = new Set(this.fields.map(field => field.id).filter(id => !!id));
|
|
892
1033
|
const usedNames = new Set(this.fields.map(field => field.header).filter(header => !!header));
|
|
1034
|
+
const canCreate = this.canCreateNewField;
|
|
893
1035
|
for (const header of this.csvHeaders) {
|
|
894
1036
|
const mapping = this.mappingByHeader[header];
|
|
895
1037
|
if (mapping == null) {
|
|
896
1038
|
resolved[header] = null;
|
|
897
1039
|
} else if (mapping.kind === 'existing') {
|
|
898
1040
|
resolved[header] = mapping.fieldId;
|
|
899
|
-
} else if (
|
|
1041
|
+
} else if (canCreate) {
|
|
900
1042
|
var _mapping$displayName;
|
|
901
1043
|
// The user's name override (from the field editor) seeds the unique
|
|
902
1044
|
// name/key derivation; otherwise fall back to the column header.
|
|
@@ -907,34 +1049,79 @@ class ImportState {
|
|
|
907
1049
|
} = uniqueFieldNameAndKey(baseName, usedNames, usedKeys);
|
|
908
1050
|
usedKeys.add(key);
|
|
909
1051
|
usedNames.add(displayName);
|
|
910
|
-
|
|
1052
|
+
pending.push({
|
|
911
1053
|
key,
|
|
912
|
-
type: mapping.fieldType,
|
|
913
1054
|
displayName,
|
|
914
|
-
|
|
915
|
-
config: mapping.config
|
|
916
|
-
})
|
|
1055
|
+
mapping
|
|
917
1056
|
});
|
|
918
1057
|
resolved[header] = key;
|
|
919
1058
|
} else {
|
|
920
|
-
// "new" mapping
|
|
1059
|
+
// "new" mapping with no create callback on this source — nothing to map into.
|
|
921
1060
|
resolved[header] = null;
|
|
922
1061
|
}
|
|
923
1062
|
}
|
|
924
|
-
if (
|
|
1063
|
+
if (pending.length > 0) {
|
|
925
1064
|
// Stash the returned fields map to merge into the table on success; kept off
|
|
926
|
-
// the observable until then so columns don't update mid-import.
|
|
927
|
-
|
|
928
|
-
// wipe every column — treat a missing/empty return as "nothing to merge".
|
|
929
|
-
const updated = await this.updateSchema(newFields);
|
|
930
|
-
this._pendingFieldsMap = updated && Object.keys(updated).length > 0 ? updated : undefined;
|
|
1065
|
+
// the observable until then so columns don't update mid-import.
|
|
1066
|
+
this._pendingFieldsMap = await this._createPendingFields(pending);
|
|
931
1067
|
}
|
|
932
1068
|
return resolved;
|
|
933
1069
|
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Creates the pending new fields via the active `dataSource` branch's callback
|
|
1073
|
+
* — `updateSchema` (CMS) or `createFields` (DataExtension) — building the
|
|
1074
|
+
* source-specific field def from the shared, already-deduped pending columns.
|
|
1075
|
+
* The callback may throw; `startImport` surfaces that as the error state.
|
|
1076
|
+
*/
|
|
1077
|
+
async _createPendingFields(pending) {
|
|
1078
|
+
const updateSchema = this.updateSchema;
|
|
1079
|
+
const createDataExtensionFields = this.createDataExtensionFields;
|
|
1080
|
+
let updated;
|
|
1081
|
+
if (updateSchema) {
|
|
1082
|
+
updated = await updateSchema(pending.map(({
|
|
1083
|
+
key,
|
|
1084
|
+
displayName,
|
|
1085
|
+
mapping
|
|
1086
|
+
}) => ({
|
|
1087
|
+
key,
|
|
1088
|
+
type: mapping.fieldType,
|
|
1089
|
+
displayName,
|
|
1090
|
+
...(mapping.config && {
|
|
1091
|
+
config: mapping.config
|
|
1092
|
+
})
|
|
1093
|
+
})));
|
|
1094
|
+
} else if (createDataExtensionFields) {
|
|
1095
|
+
// The DataExtension source builds its own field shape from the neutral
|
|
1096
|
+
// pending columns — no DataExtension-specific def-building in core.
|
|
1097
|
+
updated = await createDataExtensionFields(pending);
|
|
1098
|
+
} else {
|
|
1099
|
+
return undefined;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Guard the empty map: SchemaState.mergeFields is a full replace, so merging
|
|
1103
|
+
// `{}` would wipe every column — treat a missing/empty return as "nothing to
|
|
1104
|
+
// merge".
|
|
1105
|
+
return updated && Object.keys(updated).length > 0 ? updated : undefined;
|
|
1106
|
+
}
|
|
934
1107
|
async startImport() {
|
|
935
1108
|
if (!this.hasMappings) {
|
|
936
1109
|
return;
|
|
937
1110
|
}
|
|
1111
|
+
|
|
1112
|
+
// Block import while any required destination field is unmapped and tell the
|
|
1113
|
+
// user which (CAIRO-4452). The footer already flags this; the Import button
|
|
1114
|
+
// stays enabled, so this is where the click is stopped.
|
|
1115
|
+
if (this.hasMissingRequiredFields) {
|
|
1116
|
+
var _this$showToast3;
|
|
1117
|
+
(_this$showToast3 = this.showToast) == null || _this$showToast3.call(this, {
|
|
1118
|
+
type: 'ERROR',
|
|
1119
|
+
message: this.translate('cairo.import.missingRequiredMessage', {
|
|
1120
|
+
fields: this._missingRequiredFieldNames()
|
|
1121
|
+
})
|
|
1122
|
+
});
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
938
1125
|
this._cancelled = false;
|
|
939
1126
|
this._abortController = new AbortController();
|
|
940
1127
|
|
|
@@ -953,7 +1140,9 @@ class ImportState {
|
|
|
953
1140
|
};
|
|
954
1141
|
this.result = {
|
|
955
1142
|
succeeded: 0,
|
|
956
|
-
failed: 0
|
|
1143
|
+
failed: 0,
|
|
1144
|
+
created: 0,
|
|
1145
|
+
updated: 0
|
|
957
1146
|
};
|
|
958
1147
|
this._jobId = null;
|
|
959
1148
|
// Drop any fields map from a prior attempt so a no-new-fields "Try again"
|
|
@@ -1007,17 +1196,19 @@ class ImportState {
|
|
|
1007
1196
|
..._model.queryStatus.success,
|
|
1008
1197
|
data: this.result
|
|
1009
1198
|
};
|
|
1199
|
+
// The job reports counts only when it finishes, so the bar sat at 0 for
|
|
1200
|
+
// the whole import (CAIRO-4476). Snap it to 100% and hold it (below)
|
|
1201
|
+
// before the summary step replaces the progress step, so the user sees
|
|
1202
|
+
// the import reach completion.
|
|
1203
|
+
this.processedCount = this.totalCount;
|
|
1010
1204
|
});
|
|
1011
1205
|
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
// the
|
|
1015
|
-
|
|
1016
|
-
if (this.
|
|
1017
|
-
|
|
1018
|
-
if (this._cancelled) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1206
|
+
// Keep the completed bar on screen briefly before moving on. `importStatus`
|
|
1207
|
+
// is already success, so a close during this window just dismisses the
|
|
1208
|
+
// modal (the `close()` guard doesn't treat it as a cancel).
|
|
1209
|
+
await this._lingerOnCompletion();
|
|
1210
|
+
if (this._cancelled) {
|
|
1211
|
+
return;
|
|
1021
1212
|
}
|
|
1022
1213
|
|
|
1023
1214
|
// Success path only. `_pendingFieldsMap` holds this run's returned map — it's
|
|
@@ -1025,12 +1216,12 @@ class ImportState {
|
|
|
1025
1216
|
// `_resolveFieldMapping`; snapshotting here keeps the merge success-gated.
|
|
1026
1217
|
createdFields = this._pendingFieldsMap;
|
|
1027
1218
|
(0, _mobx.runInAction)(() => {
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
this.
|
|
1219
|
+
// Import finished: show the summary step (created / updated / failed
|
|
1220
|
+
// counts, plus a "Download CSV" of the failed rows when any failed).
|
|
1221
|
+
// The modal stays open; the user dismisses it from the summary via
|
|
1222
|
+
// `done`. Refresh the collection now so the table behind the modal is
|
|
1223
|
+
// up to date by the time it closes.
|
|
1224
|
+
this.currentStep = 'summary';
|
|
1034
1225
|
this.collection.clearResultAndMoveToStart({
|
|
1035
1226
|
force: true
|
|
1036
1227
|
});
|