@wix/bex-core 2.332.0 → 2.336.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 +13 -2
- package/dist/cjs/assets/locale/messages_bg.json +13 -2
- package/dist/cjs/assets/locale/messages_ca.json +13 -2
- package/dist/cjs/assets/locale/messages_cs.json +11 -0
- package/dist/cjs/assets/locale/messages_da.json +11 -0
- package/dist/cjs/assets/locale/messages_de.json +11 -0
- package/dist/cjs/assets/locale/messages_el.json +13 -2
- package/dist/cjs/assets/locale/messages_en.json +13 -2
- package/dist/cjs/assets/locale/messages_es.json +11 -0
- package/dist/cjs/assets/locale/messages_fi.json +13 -2
- package/dist/cjs/assets/locale/messages_fr.json +11 -0
- package/dist/cjs/assets/locale/messages_he.json +13 -2
- package/dist/cjs/assets/locale/messages_hi.json +11 -0
- package/dist/cjs/assets/locale/messages_hu.json +13 -2
- package/dist/cjs/assets/locale/messages_id.json +11 -0
- package/dist/cjs/assets/locale/messages_it.json +11 -0
- package/dist/cjs/assets/locale/messages_ja.json +11 -0
- package/dist/cjs/assets/locale/messages_ko.json +11 -0
- package/dist/cjs/assets/locale/messages_lt.json +13 -2
- package/dist/cjs/assets/locale/messages_ms.json +13 -2
- package/dist/cjs/assets/locale/messages_nl.json +11 -0
- package/dist/cjs/assets/locale/messages_no.json +11 -0
- package/dist/cjs/assets/locale/messages_pl.json +11 -0
- package/dist/cjs/assets/locale/messages_pt.json +11 -0
- package/dist/cjs/assets/locale/messages_ro.json +13 -2
- package/dist/cjs/assets/locale/messages_ru.json +11 -0
- package/dist/cjs/assets/locale/messages_sk.json +13 -2
- package/dist/cjs/assets/locale/messages_sl.json +13 -2
- package/dist/cjs/assets/locale/messages_sv.json +11 -0
- package/dist/cjs/assets/locale/messages_th.json +11 -0
- package/dist/cjs/assets/locale/messages_tl.json +13 -2
- package/dist/cjs/assets/locale/messages_tr.json +11 -0
- package/dist/cjs/assets/locale/messages_uk.json +11 -0
- package/dist/cjs/assets/locale/messages_vi.json +11 -0
- package/dist/cjs/assets/locale/messages_zh.json +11 -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 +6 -6
- package/dist/cjs/hooks/index.js.map +1 -1
- package/dist/cjs/hooks/useImport.js +4 -0
- package/dist/cjs/hooks/useImport.js.map +1 -1
- package/dist/cjs/hooks/useSchemaSource.js +11 -0
- package/dist/cjs/hooks/useSchemaSource.js.map +1 -0
- package/dist/cjs/index.js +4 -9
- 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 +415 -136
- package/dist/cjs/state/ImportState/ImportState.js.map +1 -1
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js +417 -82
- package/dist/cjs/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -1
- package/dist/cjs/state/ImportState/__tests__/buildImportConfig.spec.js +156 -0
- package/dist/cjs/state/ImportState/__tests__/buildImportConfig.spec.js.map +1 -0
- package/dist/cjs/state/ImportState/detectFieldType.js +41 -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/state/SourceState.js +150 -0
- package/dist/cjs/state/SourceState.js.map +1 -0
- package/dist/cjs/state/index.js +7 -0
- package/dist/cjs/state/index.js.map +1 -1
- package/dist/cjs/testkit/createInMemorySchemaSource.js +190 -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/Field.js.map +1 -1
- package/dist/cjs/types/Schema.js.map +1 -1
- package/dist/cjs/types/SchemaSource.js +31 -21
- package/dist/cjs/types/SchemaSource.js.map +1 -1
- package/dist/esm/assets/locale/messages_ar.json +13 -2
- package/dist/esm/assets/locale/messages_bg.json +13 -2
- package/dist/esm/assets/locale/messages_ca.json +13 -2
- package/dist/esm/assets/locale/messages_cs.json +11 -0
- package/dist/esm/assets/locale/messages_da.json +11 -0
- package/dist/esm/assets/locale/messages_de.json +11 -0
- package/dist/esm/assets/locale/messages_el.json +13 -2
- package/dist/esm/assets/locale/messages_en.json +13 -2
- package/dist/esm/assets/locale/messages_es.json +11 -0
- package/dist/esm/assets/locale/messages_fi.json +13 -2
- package/dist/esm/assets/locale/messages_fr.json +11 -0
- package/dist/esm/assets/locale/messages_he.json +13 -2
- package/dist/esm/assets/locale/messages_hi.json +11 -0
- package/dist/esm/assets/locale/messages_hu.json +13 -2
- package/dist/esm/assets/locale/messages_id.json +11 -0
- package/dist/esm/assets/locale/messages_it.json +11 -0
- package/dist/esm/assets/locale/messages_ja.json +11 -0
- package/dist/esm/assets/locale/messages_ko.json +11 -0
- package/dist/esm/assets/locale/messages_lt.json +13 -2
- package/dist/esm/assets/locale/messages_ms.json +13 -2
- package/dist/esm/assets/locale/messages_nl.json +11 -0
- package/dist/esm/assets/locale/messages_no.json +11 -0
- package/dist/esm/assets/locale/messages_pl.json +11 -0
- package/dist/esm/assets/locale/messages_pt.json +11 -0
- package/dist/esm/assets/locale/messages_ro.json +13 -2
- package/dist/esm/assets/locale/messages_ru.json +11 -0
- package/dist/esm/assets/locale/messages_sk.json +13 -2
- package/dist/esm/assets/locale/messages_sl.json +13 -2
- package/dist/esm/assets/locale/messages_sv.json +11 -0
- package/dist/esm/assets/locale/messages_th.json +11 -0
- package/dist/esm/assets/locale/messages_tl.json +13 -2
- package/dist/esm/assets/locale/messages_tr.json +11 -0
- package/dist/esm/assets/locale/messages_uk.json +11 -0
- package/dist/esm/assets/locale/messages_vi.json +11 -0
- package/dist/esm/assets/locale/messages_zh.json +11 -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 +1 -1
- package/dist/esm/hooks/index.js.map +1 -1
- package/dist/esm/hooks/useImport.js +3 -1
- package/dist/esm/hooks/useImport.js.map +1 -1
- package/dist/esm/hooks/useSchemaSource.js +7 -0
- package/dist/esm/hooks/useSchemaSource.js.map +1 -0
- package/dist/esm/index.js +1 -2
- 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 +306 -113
- package/dist/esm/state/ImportState/ImportState.js.map +1 -1
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js +297 -70
- package/dist/esm/state/ImportState/__tests__/ImportState.engine.spec.js.map +1 -1
- package/dist/esm/state/ImportState/__tests__/buildImportConfig.spec.js +100 -0
- package/dist/esm/state/ImportState/__tests__/buildImportConfig.spec.js.map +1 -0
- package/dist/esm/state/ImportState/detectFieldType.js +34 -15
- 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/state/SourceState.js +131 -0
- package/dist/esm/state/SourceState.js.map +1 -0
- package/dist/esm/state/index.js +1 -0
- package/dist/esm/state/index.js.map +1 -1
- package/dist/esm/testkit/createInMemorySchemaSource.js +141 -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 +21 -14
- package/dist/esm/types/SchemaSource.js.map +1 -1
- package/dist/types/hooks/index.d.ts +1 -1
- package/dist/types/hooks/index.d.ts.map +1 -1
- package/dist/types/hooks/useImport.d.ts +9 -3
- package/dist/types/hooks/useImport.d.ts.map +1 -1
- package/dist/types/hooks/useSchemaSource.d.ts +4 -0
- package/dist/types/hooks/useSchemaSource.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -3
- 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 +208 -65
- package/dist/types/state/ImportState/ImportState.d.ts.map +1 -1
- package/dist/types/state/ImportState/__tests__/buildImportConfig.spec.d.ts +2 -0
- package/dist/types/state/ImportState/__tests__/buildImportConfig.spec.d.ts.map +1 -0
- package/dist/types/state/ImportState/detectFieldType.d.ts +5 -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/state/SourceState.d.ts +39 -0
- package/dist/types/state/SourceState.d.ts.map +1 -0
- package/dist/types/state/index.d.ts +1 -0
- package/dist/types/state/index.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 +43 -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/Field.d.ts +3 -0
- package/dist/types/types/Field.d.ts.map +1 -1
- package/dist/types/types/Schema.d.ts +10 -6
- package/dist/types/types/Schema.d.ts.map +1 -1
- package/dist/types/types/SchemaSource.d.ts +30 -13
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
4
|
exports.__esModule = true;
|
|
5
|
-
exports.ImportState = void 0;
|
|
5
|
+
exports.buildImportConfig = exports.ImportState = void 0;
|
|
6
6
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
7
7
|
var _mobx = require("mobx");
|
|
8
8
|
var _model = require("../../model");
|
|
@@ -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 builds its own field shape from them, so no
|
|
103
|
+
* 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,117 @@ 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
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The effective import config. Satisfied both by a plain object (a fully
|
|
133
|
+
* hand-built config) and by the object `buildImportConfig` assembles, whose
|
|
134
|
+
* members are getters read on demand — so the async schema load needn't resolve
|
|
135
|
+
* before `ImportState` is built. `ImportState` holds it by reference (never
|
|
136
|
+
* spreads it) so those getters stay live.
|
|
137
|
+
*/
|
|
138
|
+
|
|
94
139
|
/**
|
|
95
|
-
* The
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
140
|
+
* The inputs a consumer passes to `<ImportButton config={...} />`. Everything the
|
|
141
|
+
* table's source provides — the collection, its fields, create-new-field and
|
|
142
|
+
* merge-back — is read live off the `fieldsSource`, so only what isn't derivable
|
|
143
|
+
* lives here. Omit `destination` to import into the table's own collection (Wix
|
|
144
|
+
* Data); set it to write to a platformized target instead.
|
|
100
145
|
*/
|
|
101
146
|
|
|
102
|
-
//
|
|
103
|
-
// the
|
|
104
|
-
//
|
|
105
|
-
|
|
147
|
+
// Builds the Field to create from a pending new column: import decides the key,
|
|
148
|
+
// and the detected NewFieldType maps to the schema's PatternsFieldType. Import
|
|
149
|
+
// only ever creates non-reference fields, so the cast is safe.
|
|
150
|
+
const toField = def => {
|
|
151
|
+
var _def$config, _def$config2, _def$config3;
|
|
152
|
+
return {
|
|
153
|
+
id: def.key,
|
|
154
|
+
displayName: def.displayName,
|
|
155
|
+
type: (0, _detectFieldType.toPatternsFieldType)(def.type),
|
|
156
|
+
...(((_def$config = def.config) == null ? void 0 : _def$config.validation) && {
|
|
157
|
+
validation: def.config.validation
|
|
158
|
+
}),
|
|
159
|
+
...(((_def$config2 = def.config) == null ? void 0 : _def$config2.isPii) && {
|
|
160
|
+
isPii: true
|
|
161
|
+
}),
|
|
162
|
+
...(((_def$config3 = def.config) == null ? void 0 : _def$config3.helpText) && {
|
|
163
|
+
helpText: def.config.helpText
|
|
164
|
+
})
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Assembles a `ResolvedImportConfig` from the table's live `fieldsSource` and the
|
|
170
|
+
* consumer `inputs`. The schema-derived members are getters so they read the
|
|
171
|
+
* source live — fields import creates and fields added through the
|
|
172
|
+
* field-management panel show up immediately. Empty before a source attaches;
|
|
173
|
+
* nothing imports until then.
|
|
174
|
+
*
|
|
175
|
+
* Exported for tests.
|
|
176
|
+
*/
|
|
177
|
+
const buildImportConfig = (fieldsSource, collection, inputs = {}) => {
|
|
178
|
+
const {
|
|
179
|
+
destination,
|
|
180
|
+
fields = [],
|
|
181
|
+
backup,
|
|
182
|
+
idFieldKey,
|
|
183
|
+
jobApplicationErrorMap
|
|
184
|
+
} = inputs;
|
|
185
|
+
return {
|
|
186
|
+
get fields() {
|
|
187
|
+
const seen = new Set(fields.map(f => f.id));
|
|
188
|
+
const live = ((fieldsSource == null ? void 0 : fieldsSource.fields) ?? []).filter(f => !seen.has(f.id)).map(f => {
|
|
189
|
+
var _f$validation;
|
|
190
|
+
return {
|
|
191
|
+
id: f.id,
|
|
192
|
+
header: f.displayName,
|
|
193
|
+
required: (_f$validation = f.validation) == null ? void 0 : _f$validation.required
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
return [...fields, ...live];
|
|
197
|
+
},
|
|
198
|
+
get dataSource() {
|
|
199
|
+
var _fieldsSource$fieldMa, _fieldsSource$fieldAc;
|
|
200
|
+
if (destination) {
|
|
201
|
+
return {
|
|
202
|
+
destination
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const add = fieldsSource != null && (_fieldsSource$fieldMa = fieldsSource.fieldManagement) != null && _fieldsSource$fieldMa.canAddFields ? (_fieldsSource$fieldAc = fieldsSource.fieldActions) == null ? void 0 : _fieldsSource$fieldAc.addFields : undefined;
|
|
206
|
+
return {
|
|
207
|
+
collectionId: collection.queryName,
|
|
208
|
+
...(add && {
|
|
209
|
+
updateSchema: defs => add(defs.map(toField))
|
|
210
|
+
})
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
mergeFields: f => fieldsSource == null || fieldsSource.mergeFields == null ? void 0 : fieldsSource.mergeFields(f),
|
|
214
|
+
...(backup && {
|
|
215
|
+
backup
|
|
216
|
+
}),
|
|
217
|
+
...(idFieldKey && {
|
|
218
|
+
idFieldKey
|
|
219
|
+
}),
|
|
220
|
+
...(jobApplicationErrorMap && {
|
|
221
|
+
jobApplicationErrorMap
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
exports.buildImportConfig = buildImportConfig;
|
|
226
|
+
// How long to hold the completed (100%) progress bar on screen before moving to
|
|
227
|
+
// the summary step (CAIRO-4476). The job reports counts only when it finishes,
|
|
228
|
+
// so the bar sits at 0 for the whole import and would otherwise flip straight to
|
|
229
|
+
// the summary without the user ever seeing it reach completion.
|
|
106
230
|
const COMPLETION_LINGER_MS = 600;
|
|
107
231
|
|
|
108
232
|
// The Wix Data collection's id field key. A CSV column mapped to this field lets
|
|
@@ -185,7 +309,9 @@ class ImportState {
|
|
|
185
309
|
(0, _defineProperty2.default)(this, "totalCount", 0);
|
|
186
310
|
(0, _defineProperty2.default)(this, "result", {
|
|
187
311
|
succeeded: 0,
|
|
188
|
-
failed: 0
|
|
312
|
+
failed: 0,
|
|
313
|
+
created: 0,
|
|
314
|
+
updated: 0
|
|
189
315
|
});
|
|
190
316
|
(0, _defineProperty2.default)(this, "isBackingUp", false);
|
|
191
317
|
(0, _defineProperty2.default)(this, "isRestoring", false);
|
|
@@ -195,16 +321,19 @@ class ImportState {
|
|
|
195
321
|
// Id of the running data-movement job, set once submitted — used to cancel it
|
|
196
322
|
// (`terminateJob`) if the user closes mid-import.
|
|
197
323
|
(0, _defineProperty2.default)(this, "_jobId", null);
|
|
198
|
-
// The column whose pending new field the field editor is currently editing
|
|
199
|
-
//
|
|
200
|
-
// `
|
|
201
|
-
|
|
324
|
+
// The column whose pending new field the field editor is currently editing, or
|
|
325
|
+
// `null` when the editor is closed. Observable so the source's field-editor
|
|
326
|
+
// component (rendered via `renderFieldEditor`) opens/prefills itself when this
|
|
327
|
+
// is set and closes when it clears — the editor is decoupled from the layout.
|
|
328
|
+
(0, _defineProperty2.default)(this, "editingHeader", null);
|
|
202
329
|
// Holds `updateSchema`'s returned fields map between field creation (pre-upload)
|
|
203
330
|
// and success. Kept private/non-observable so it can't update columns mid-import;
|
|
204
331
|
// it's promoted to the observable `createdFieldsMap` only on success.
|
|
205
332
|
(0, _defineProperty2.default)(this, "_pendingFieldsMap", void 0);
|
|
206
|
-
this.config = props.config;
|
|
207
333
|
this.collection = props.collection;
|
|
334
|
+
// A pre-built config wins (hand-built / tests); otherwise assemble it from
|
|
335
|
+
// the live source + inputs.
|
|
336
|
+
this.config = props.config ?? buildImportConfig(props.fieldsSource, props.collection, props.importInputs);
|
|
208
337
|
this.importModalState = props.importModalState;
|
|
209
338
|
this.translate = props.translate;
|
|
210
339
|
this.errorHandler = props.errorHandler;
|
|
@@ -233,7 +362,12 @@ class ImportState {
|
|
|
233
362
|
hasMappings: _mobx.computed,
|
|
234
363
|
hasNoMappedData: _mobx.computed,
|
|
235
364
|
selectedFieldIds: _mobx.computed,
|
|
365
|
+
missingRequiredFields: _mobx.computed,
|
|
366
|
+
hasMissingRequiredFields: _mobx.computed,
|
|
236
367
|
idMappedHeader: _mobx.computed,
|
|
368
|
+
editingHeader: _mobx.observable.ref,
|
|
369
|
+
editingMapping: _mobx.computed,
|
|
370
|
+
endFieldEdit: _mobx.action.bound,
|
|
237
371
|
setWritePolicy: _mobx.action.bound,
|
|
238
372
|
onUploadContinue: _mobx.action.bound,
|
|
239
373
|
goToUpload: _mobx.action.bound,
|
|
@@ -242,7 +376,10 @@ class ImportState {
|
|
|
242
376
|
setNewFieldConfig: _mobx.action.bound,
|
|
243
377
|
beginFieldEdit: _mobx.action.bound,
|
|
244
378
|
applyFieldEdit: _mobx.action.bound,
|
|
379
|
+
applyDataExtensionFieldEdit: _mobx.action.bound,
|
|
245
380
|
close: _mobx.action.bound,
|
|
381
|
+
done: _mobx.action.bound,
|
|
382
|
+
downloadFailedRows: _mobx.action.bound,
|
|
246
383
|
onFileSelected: _mobx.action.bound,
|
|
247
384
|
startImport: _mobx.action.bound,
|
|
248
385
|
reset: _mobx.action.bound
|
|
@@ -259,19 +396,36 @@ class ImportState {
|
|
|
259
396
|
|
|
260
397
|
/** Wix Data collection the import writes into (unset for platformized). */
|
|
261
398
|
get collectionId() {
|
|
262
|
-
|
|
399
|
+
const ds = this.config.dataSource;
|
|
400
|
+
return 'collectionId' in ds ? ds.collectionId : undefined;
|
|
263
401
|
}
|
|
264
402
|
get destination() {
|
|
265
|
-
|
|
403
|
+
const ds = this.config.dataSource;
|
|
404
|
+
return 'destination' in ds ? ds.destination : undefined;
|
|
266
405
|
}
|
|
267
406
|
get backup() {
|
|
268
407
|
return this.config.backup;
|
|
269
408
|
}
|
|
409
|
+
|
|
410
|
+
/** CMS create-fields callback, present only on the `collectionId` branch. */
|
|
270
411
|
get updateSchema() {
|
|
271
|
-
|
|
412
|
+
const ds = this.config.dataSource;
|
|
413
|
+
return 'collectionId' in ds ? ds.updateSchema : undefined;
|
|
272
414
|
}
|
|
273
|
-
|
|
274
|
-
|
|
415
|
+
|
|
416
|
+
/** DataExtension create-fields callback, present only on the `destination` branch. */
|
|
417
|
+
get createDataExtensionFields() {
|
|
418
|
+
const ds = this.config.dataSource;
|
|
419
|
+
return 'destination' in ds ? ds.createFields : undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** DataExtension field-management for the editor, on the `destination` branch. */
|
|
423
|
+
get dataExtensionFieldManagement() {
|
|
424
|
+
const ds = this.config.dataSource;
|
|
425
|
+
return 'destination' in ds ? ds.fieldManagement : undefined;
|
|
426
|
+
}
|
|
427
|
+
get jobApplicationErrorMap() {
|
|
428
|
+
return this.config.jobApplicationErrorMap;
|
|
275
429
|
}
|
|
276
430
|
|
|
277
431
|
// The collection's id-field key; a schema-backed config may report a non-`_id`
|
|
@@ -320,6 +474,27 @@ class ImportState {
|
|
|
320
474
|
return new Set(Object.values(this.mappingByHeader).filter(m => (m == null ? void 0 : m.kind) === 'existing').map(m => m.fieldId));
|
|
321
475
|
}
|
|
322
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Required destination fields that no CSV column is mapped to (CAIRO-4452).
|
|
479
|
+
* A required field is an existing collection field, so only an `existing`
|
|
480
|
+
* mapping can satisfy it — a "create new field" column can't. Covers both
|
|
481
|
+
* ways a required field ends up unmapped: the CSV has no matching column at
|
|
482
|
+
* all, or the user set its column to "don't import". Drives the footer
|
|
483
|
+
* indicator and blocks `startImport`.
|
|
484
|
+
*/
|
|
485
|
+
get missingRequiredFields() {
|
|
486
|
+
return this.fields.filter(field => field.required && field.id != null && !this.selectedFieldIds.has(field.id));
|
|
487
|
+
}
|
|
488
|
+
get hasMissingRequiredFields() {
|
|
489
|
+
return this.missingRequiredFields.length > 0;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Comma-separated display names of the missing required fields, for the block
|
|
493
|
+
// toast. Falls back to the field id when a field carries no header.
|
|
494
|
+
_missingRequiredFieldNames() {
|
|
495
|
+
return this.missingRequiredFields.map(field => field.header || field.id || '').filter(Boolean).join(', ');
|
|
496
|
+
}
|
|
497
|
+
|
|
323
498
|
/**
|
|
324
499
|
* The CSV header currently mapped to the collection's `_id` field, or `null`
|
|
325
500
|
* when no column targets it. Drives both the "When items share the same ID"
|
|
@@ -337,12 +512,13 @@ class ImportState {
|
|
|
337
512
|
|
|
338
513
|
/**
|
|
339
514
|
* Whether columns may be mapped to a brand-new field created during import.
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
515
|
+
* The opt-in is the presence of the active `dataSource` branch's create
|
|
516
|
+
* callback — `updateSchema` (CMS) or `createFields` (DataExtension). A source
|
|
517
|
+
* that can't create fields (e.g. no create permission) supplies neither, so
|
|
518
|
+
* columns fall back to "don't import" (see `getDefaultMapping`).
|
|
343
519
|
*/
|
|
344
520
|
get canCreateNewField() {
|
|
345
|
-
return this.
|
|
521
|
+
return this.updateSchema != null || this.createDataExtensionFields != null;
|
|
346
522
|
}
|
|
347
523
|
|
|
348
524
|
/**
|
|
@@ -375,7 +551,9 @@ class ImportState {
|
|
|
375
551
|
this.totalCount = 0;
|
|
376
552
|
this.result = {
|
|
377
553
|
succeeded: 0,
|
|
378
|
-
failed: 0
|
|
554
|
+
failed: 0,
|
|
555
|
+
created: 0,
|
|
556
|
+
updated: 0
|
|
379
557
|
};
|
|
380
558
|
this._pendingFieldsMap = undefined;
|
|
381
559
|
this.isBackingUp = false;
|
|
@@ -386,11 +564,10 @@ class ImportState {
|
|
|
386
564
|
}
|
|
387
565
|
async close() {
|
|
388
566
|
// 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.
|
|
567
|
+
// and restore the backup — UNLESS the import already succeeded. On success
|
|
568
|
+
// `startImport` moves straight to the 'summary' step, so a close from there
|
|
569
|
+
// just closes the modal (the summary's own `done` is the normal exit); this
|
|
570
|
+
// guard covers only closing mid-import.
|
|
394
571
|
if (this.currentStep === 'progress' && !this.importStatus.isSuccess) {
|
|
395
572
|
var _this$_abortControlle2;
|
|
396
573
|
this._cancelled = true;
|
|
@@ -414,6 +591,14 @@ class ImportState {
|
|
|
414
591
|
}
|
|
415
592
|
this.importModalState.close();
|
|
416
593
|
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Closes the summary step at the end of a completed import. The collection was
|
|
597
|
+
* already refreshed when the summary opened, so this only dismisses the modal.
|
|
598
|
+
*/
|
|
599
|
+
done() {
|
|
600
|
+
this.importModalState.close();
|
|
601
|
+
}
|
|
417
602
|
async _restoreBackup() {
|
|
418
603
|
const restoreFn = this.restoreFn;
|
|
419
604
|
if (!restoreFn) {
|
|
@@ -584,51 +769,44 @@ class ImportState {
|
|
|
584
769
|
}
|
|
585
770
|
});
|
|
586
771
|
}
|
|
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
772
|
|
|
603
773
|
/**
|
|
604
|
-
* Builds a
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
774
|
+
* Builds a re-importable CSV of the rows the server rejected and hands it to
|
|
775
|
+
* `downloadCsv`. Each failed row is prefixed with a leading "why it failed"
|
|
776
|
+
* column holding the server's error message, so the user can see and fix each
|
|
777
|
+
* reason. The failed rows (and their reasons) come from the job's movement
|
|
778
|
+
* logs, mapped back to the parsed `csvRows` we still hold in memory.
|
|
608
779
|
*/
|
|
609
|
-
async
|
|
780
|
+
async downloadFailedRows() {
|
|
610
781
|
var _this$downloadCsv;
|
|
611
782
|
const jobId = this._jobId;
|
|
612
783
|
if (!jobId) {
|
|
613
784
|
return;
|
|
614
785
|
}
|
|
615
|
-
let
|
|
786
|
+
let failures;
|
|
616
787
|
try {
|
|
617
|
-
|
|
788
|
+
failures = await this._fetchFailedRows(jobId);
|
|
618
789
|
} catch (error) {
|
|
619
790
|
console.error('Fetching failed rows report failed', error);
|
|
620
791
|
return;
|
|
621
792
|
}
|
|
622
|
-
const
|
|
623
|
-
|
|
793
|
+
const rows = failures.map(({
|
|
794
|
+
index,
|
|
795
|
+
reason
|
|
796
|
+
}) => {
|
|
797
|
+
const row = this.csvRows[index];
|
|
798
|
+
return row != null ? [reason, ...row] : null;
|
|
799
|
+
}).filter(row => row != null);
|
|
800
|
+
if (rows.length === 0) {
|
|
624
801
|
return;
|
|
625
802
|
}
|
|
626
|
-
const
|
|
803
|
+
const headers = [this.translate('cairo.import.failureReasonColumn'), ...this.csvHeaders];
|
|
804
|
+
const content = (0, _parseCsv.unparseCsv)(headers, rows);
|
|
627
805
|
(_this$downloadCsv = this.downloadCsv) == null || _this$downloadCsv.call(this, content, `failed-${this.fileName || 'import.csv'}`);
|
|
628
806
|
}
|
|
629
807
|
|
|
630
|
-
// Pages through the job's movement logs and returns
|
|
631
|
-
// the
|
|
808
|
+
// Pages through the job's movement logs and returns, for each row that failed,
|
|
809
|
+
// its `csvRows` index and the server's failure reason. data-hub's CSV decoder
|
|
632
810
|
// (wix-data-hub: packages/wix-data-hub-executor-c/src/plugins/formats/csv/
|
|
633
811
|
// csv-binary-decoder.ts) parses with the `csv-parse` library (`columns: true`)
|
|
634
812
|
// and sets `sourceItemId = "row ${++index + 1}"`, incrementing once **per
|
|
@@ -637,8 +815,8 @@ class ImportState {
|
|
|
637
815
|
// `csvRows[0]`, i.e. `csvRows[N - 2]`. Because `csv-parse` is RFC-4180,
|
|
638
816
|
// quoted cells containing embedded newlines are a single record, so they do
|
|
639
817
|
// NOT shift the numbering. Out-of-range indices are guarded below.
|
|
640
|
-
async
|
|
641
|
-
const
|
|
818
|
+
async _fetchFailedRows(jobId) {
|
|
819
|
+
const failures = [];
|
|
642
820
|
let cursor;
|
|
643
821
|
do {
|
|
644
822
|
var _pagingMetadata$curso;
|
|
@@ -656,12 +834,15 @@ class ImportState {
|
|
|
656
834
|
const fileLine = Number((_exec = /\d+/.exec(log.sourceItemId)) == null ? void 0 : _exec[0]);
|
|
657
835
|
const index = fileLine - 2;
|
|
658
836
|
if (Number.isInteger(index) && index >= 0 && index < this.csvRows.length) {
|
|
659
|
-
|
|
837
|
+
failures.push({
|
|
838
|
+
index,
|
|
839
|
+
reason: log.failure.description ?? ''
|
|
840
|
+
});
|
|
660
841
|
}
|
|
661
842
|
}
|
|
662
843
|
cursor = (pagingMetadata == null ? void 0 : pagingMetadata.hasNext) && ((_pagingMetadata$curso = pagingMetadata.cursors) == null ? void 0 : _pagingMetadata$curso.next) || undefined;
|
|
663
844
|
} while (cursor);
|
|
664
|
-
return
|
|
845
|
+
return failures;
|
|
665
846
|
}
|
|
666
847
|
setMapping(header, fieldId) {
|
|
667
848
|
this.mappingByHeader = {
|
|
@@ -711,24 +892,56 @@ class ImportState {
|
|
|
711
892
|
}
|
|
712
893
|
|
|
713
894
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
895
|
+
* Opens the field editor for a column's pending new field: sets
|
|
896
|
+
* `editingHeader`, which the source's editor component watches to open and
|
|
897
|
+
* prefill itself. No-op if the column isn't mapped to a new field.
|
|
717
898
|
*/
|
|
718
899
|
beginFieldEdit(header) {
|
|
719
|
-
|
|
900
|
+
var _this$mappingByHeader;
|
|
901
|
+
if (((_this$mappingByHeader = this.mappingByHeader[header]) == null ? void 0 : _this$mappingByHeader.kind) === 'new') {
|
|
902
|
+
this.editingHeader = header;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/** Closes the field editor (clears `editingHeader`). */
|
|
907
|
+
endFieldEdit() {
|
|
908
|
+
this.editingHeader = null;
|
|
720
909
|
}
|
|
721
910
|
|
|
722
911
|
/**
|
|
723
|
-
* `
|
|
724
|
-
*
|
|
912
|
+
* The `new` mapping currently being edited, or `null` when the editor is
|
|
913
|
+
* closed. The editor component reads this to prefill its form.
|
|
914
|
+
*/
|
|
915
|
+
get editingMapping() {
|
|
916
|
+
if (this.editingHeader == null) {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
const mapping = this.mappingByHeader[this.editingHeader];
|
|
920
|
+
return (mapping == null ? void 0 : mapping.kind) === 'new' ? mapping : null;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* `onSubmit` handler for the CMS field editor: maps the edited field spec into
|
|
925
|
+
* the column being edited (set via `beginFieldEdit`). No-op if nothing is being
|
|
725
926
|
* edited.
|
|
726
927
|
*/
|
|
727
928
|
applyFieldEdit(field) {
|
|
728
|
-
if (this.
|
|
929
|
+
if (this.editingHeader == null) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
this.setNewFieldConfig(this.editingHeader, (0, _fieldEdit.toFieldEdit)(field));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* `onSubmit` handler for the DataExtension field editor: maps the edited
|
|
937
|
+
* DataExtension draft (which carries per-audience permissions) into the column
|
|
938
|
+
* being edited. The `destination`-branch counterpart to `applyFieldEdit`.
|
|
939
|
+
*/
|
|
940
|
+
applyDataExtensionFieldEdit(draft) {
|
|
941
|
+
if (this.editingHeader == null) {
|
|
729
942
|
return;
|
|
730
943
|
}
|
|
731
|
-
this.setNewFieldConfig(this.
|
|
944
|
+
this.setNewFieldConfig(this.editingHeader, (0, _fieldEdit.toDataExtensionFieldEdit)(draft));
|
|
732
945
|
}
|
|
733
946
|
|
|
734
947
|
/**
|
|
@@ -776,15 +989,15 @@ class ImportState {
|
|
|
776
989
|
// The job ran server-side, so a failure arrives on a 200 (the poll
|
|
777
990
|
// succeeded; the job didn't) with no HTTP error to carry the application
|
|
778
991
|
// 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.
|
|
992
|
+
// reads the code from `response.data.details.applicationError`), then
|
|
993
|
+
// resolve it through the consumer's per-code map via `withErrorHandler` —
|
|
994
|
+
// the same way as `fetchData` etc. `job.error` is the consumer's
|
|
995
|
+
// application error (their import business logic), so only they can map
|
|
996
|
+
// its codes; they supply just the code→message map and we do the
|
|
997
|
+
// wrapping. `withErrorHandler` attaches the map to the error and
|
|
998
|
+
// re-rejects it, so the progress step resolves a consumer-specific
|
|
999
|
+
// message via `getResolvedError`. Without a map the same error still
|
|
1000
|
+
// throws, falling back to the generic message.
|
|
788
1001
|
const error = Object.assign(new Error(`ImportJob.${((_job$error = job.error) == null ? void 0 : _job$error.code) ?? 'failed'}`), {
|
|
789
1002
|
response: {
|
|
790
1003
|
data: {
|
|
@@ -794,11 +1007,17 @@ class ImportState {
|
|
|
794
1007
|
}
|
|
795
1008
|
}
|
|
796
1009
|
});
|
|
797
|
-
const
|
|
798
|
-
if (
|
|
799
|
-
//
|
|
800
|
-
// `
|
|
801
|
-
|
|
1010
|
+
const jobApplicationErrorMap = this.jobApplicationErrorMap;
|
|
1011
|
+
if (jobApplicationErrorMap) {
|
|
1012
|
+
// Adapt the consumer's code→message map into the error handler's
|
|
1013
|
+
// `errorCodesMap`: each entry, when its code matches the job's
|
|
1014
|
+
// application error, resolves with that error. `withErrorHandler`
|
|
1015
|
+
// attaches the resolved message to `error` and re-rejects it, so the
|
|
1016
|
+
// progress step surfaces it via `getResolvedError` — the consumer only
|
|
1017
|
+
// supplies the map, not the wrapping.
|
|
1018
|
+
const appError = job.error ?? {};
|
|
1019
|
+
const errorCodesMap = Object.fromEntries(Object.entries(jobApplicationErrorMap).map(([code, resolve]) => [code, () => resolve(appError)]));
|
|
1020
|
+
await this.errorHandler.withErrorHandler(() => Promise.reject(error), errorCodesMap);
|
|
802
1021
|
}
|
|
803
1022
|
throw error;
|
|
804
1023
|
}
|
|
@@ -826,10 +1045,16 @@ class ImportState {
|
|
|
826
1045
|
};
|
|
827
1046
|
}
|
|
828
1047
|
_applyJobProgress(job) {
|
|
1048
|
+
var _progress$itemsSucces, _progress$itemsSucces2;
|
|
829
1049
|
const progress = job.progress;
|
|
830
1050
|
const succeeded = (progress == null ? void 0 : progress.itemsSuccessful) ?? 0;
|
|
831
1051
|
const failed = (progress == null ? void 0 : progress.itemsRejected) ?? 0;
|
|
832
1052
|
const current = (progress == null ? void 0 : progress.current) ?? succeeded + failed;
|
|
1053
|
+
// The server splits successful items into created (new) and updated
|
|
1054
|
+
// (existing) — the summary step reports each. Absent on older jobs, in
|
|
1055
|
+
// which case both stay 0 and only the total `succeeded` is shown.
|
|
1056
|
+
const created = (progress == null || (_progress$itemsSucces = progress.itemsSuccessfulInfo) == null ? void 0 : _progress$itemsSucces.itemsCreated) ?? 0;
|
|
1057
|
+
const updated = (progress == null || (_progress$itemsSucces2 = progress.itemsSuccessfulInfo) == null ? void 0 : _progress$itemsSucces2.itemsUpdated) ?? 0;
|
|
833
1058
|
|
|
834
1059
|
// `totalCount` is fixed up front in `startImport` from the CSV row count
|
|
835
1060
|
// (one uploaded row per CSV row); polling only advances `processedCount`.
|
|
@@ -837,10 +1062,15 @@ class ImportState {
|
|
|
837
1062
|
this.processedCount = Math.min(current, this.totalCount);
|
|
838
1063
|
this.result = {
|
|
839
1064
|
succeeded,
|
|
840
|
-
failed
|
|
1065
|
+
failed,
|
|
1066
|
+
created,
|
|
1067
|
+
updated
|
|
841
1068
|
};
|
|
842
1069
|
});
|
|
843
1070
|
}
|
|
1071
|
+
_lingerOnCompletion() {
|
|
1072
|
+
return new Promise(resolve => setTimeout(resolve, COMPLETION_LINGER_MS));
|
|
1073
|
+
}
|
|
844
1074
|
async _runBackup() {
|
|
845
1075
|
const backup = this.backup;
|
|
846
1076
|
if (!backup) {
|
|
@@ -872,9 +1102,6 @@ class ImportState {
|
|
|
872
1102
|
});
|
|
873
1103
|
return false;
|
|
874
1104
|
}
|
|
875
|
-
_lingerOnCompletion() {
|
|
876
|
-
return new Promise(resolve => setTimeout(resolve, COMPLETION_LINGER_MS));
|
|
877
|
-
}
|
|
878
1105
|
|
|
879
1106
|
/**
|
|
880
1107
|
* Resolves every column to the final destination key used in the upload
|
|
@@ -885,18 +1112,21 @@ class ImportState {
|
|
|
885
1112
|
*/
|
|
886
1113
|
async _resolveFieldMapping() {
|
|
887
1114
|
const resolved = {};
|
|
888
|
-
|
|
1115
|
+
// Pending new columns with their deduped key + display name. Source-neutral;
|
|
1116
|
+
// the per-source field def is built from these in `_createPendingFields`.
|
|
1117
|
+
const pending = [];
|
|
889
1118
|
// Seed with existing field ids/display names so derived keys and names never
|
|
890
1119
|
// collide with them or with each other.
|
|
891
1120
|
const usedKeys = new Set(this.fields.map(field => field.id).filter(id => !!id));
|
|
892
1121
|
const usedNames = new Set(this.fields.map(field => field.header).filter(header => !!header));
|
|
1122
|
+
const canCreate = this.canCreateNewField;
|
|
893
1123
|
for (const header of this.csvHeaders) {
|
|
894
1124
|
const mapping = this.mappingByHeader[header];
|
|
895
1125
|
if (mapping == null) {
|
|
896
1126
|
resolved[header] = null;
|
|
897
1127
|
} else if (mapping.kind === 'existing') {
|
|
898
1128
|
resolved[header] = mapping.fieldId;
|
|
899
|
-
} else if (
|
|
1129
|
+
} else if (canCreate) {
|
|
900
1130
|
var _mapping$displayName;
|
|
901
1131
|
// The user's name override (from the field editor) seeds the unique
|
|
902
1132
|
// name/key derivation; otherwise fall back to the column header.
|
|
@@ -907,34 +1137,79 @@ class ImportState {
|
|
|
907
1137
|
} = uniqueFieldNameAndKey(baseName, usedNames, usedKeys);
|
|
908
1138
|
usedKeys.add(key);
|
|
909
1139
|
usedNames.add(displayName);
|
|
910
|
-
|
|
1140
|
+
pending.push({
|
|
911
1141
|
key,
|
|
912
|
-
type: mapping.fieldType,
|
|
913
1142
|
displayName,
|
|
914
|
-
|
|
915
|
-
config: mapping.config
|
|
916
|
-
})
|
|
1143
|
+
mapping
|
|
917
1144
|
});
|
|
918
1145
|
resolved[header] = key;
|
|
919
1146
|
} else {
|
|
920
|
-
// "new" mapping
|
|
1147
|
+
// "new" mapping with no create callback on this source — nothing to map into.
|
|
921
1148
|
resolved[header] = null;
|
|
922
1149
|
}
|
|
923
1150
|
}
|
|
924
|
-
if (
|
|
1151
|
+
if (pending.length > 0) {
|
|
925
1152
|
// 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;
|
|
1153
|
+
// the observable until then so columns don't update mid-import.
|
|
1154
|
+
this._pendingFieldsMap = await this._createPendingFields(pending);
|
|
931
1155
|
}
|
|
932
1156
|
return resolved;
|
|
933
1157
|
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Creates the pending new fields via the active `dataSource` branch's callback
|
|
1161
|
+
* — `updateSchema` (CMS) or `createFields` (DataExtension) — building the
|
|
1162
|
+
* source-specific field def from the shared, already-deduped pending columns.
|
|
1163
|
+
* The callback may throw; `startImport` surfaces that as the error state.
|
|
1164
|
+
*/
|
|
1165
|
+
async _createPendingFields(pending) {
|
|
1166
|
+
const updateSchema = this.updateSchema;
|
|
1167
|
+
const createDataExtensionFields = this.createDataExtensionFields;
|
|
1168
|
+
let updated;
|
|
1169
|
+
if (updateSchema) {
|
|
1170
|
+
updated = await updateSchema(pending.map(({
|
|
1171
|
+
key,
|
|
1172
|
+
displayName,
|
|
1173
|
+
mapping
|
|
1174
|
+
}) => ({
|
|
1175
|
+
key,
|
|
1176
|
+
type: mapping.fieldType,
|
|
1177
|
+
displayName,
|
|
1178
|
+
...(mapping.config && {
|
|
1179
|
+
config: mapping.config
|
|
1180
|
+
})
|
|
1181
|
+
})));
|
|
1182
|
+
} else if (createDataExtensionFields) {
|
|
1183
|
+
// The DataExtension source builds its own field shape from the neutral
|
|
1184
|
+
// pending columns — no DataExtension-specific def-building in core.
|
|
1185
|
+
updated = await createDataExtensionFields(pending);
|
|
1186
|
+
} else {
|
|
1187
|
+
return undefined;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Guard the empty map: SchemaState.mergeFields is a full replace, so merging
|
|
1191
|
+
// `{}` would wipe every column — treat a missing/empty return as "nothing to
|
|
1192
|
+
// merge".
|
|
1193
|
+
return updated && Object.keys(updated).length > 0 ? updated : undefined;
|
|
1194
|
+
}
|
|
934
1195
|
async startImport() {
|
|
935
1196
|
if (!this.hasMappings) {
|
|
936
1197
|
return;
|
|
937
1198
|
}
|
|
1199
|
+
|
|
1200
|
+
// Block import while any required destination field is unmapped and tell the
|
|
1201
|
+
// user which (CAIRO-4452). The footer already flags this; the Import button
|
|
1202
|
+
// stays enabled, so this is where the click is stopped.
|
|
1203
|
+
if (this.hasMissingRequiredFields) {
|
|
1204
|
+
var _this$showToast3;
|
|
1205
|
+
(_this$showToast3 = this.showToast) == null || _this$showToast3.call(this, {
|
|
1206
|
+
type: 'ERROR',
|
|
1207
|
+
message: this.translate('cairo.import.missingRequiredMessage', {
|
|
1208
|
+
fields: this._missingRequiredFieldNames()
|
|
1209
|
+
})
|
|
1210
|
+
});
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
938
1213
|
this._cancelled = false;
|
|
939
1214
|
this._abortController = new AbortController();
|
|
940
1215
|
|
|
@@ -953,7 +1228,9 @@ class ImportState {
|
|
|
953
1228
|
};
|
|
954
1229
|
this.result = {
|
|
955
1230
|
succeeded: 0,
|
|
956
|
-
failed: 0
|
|
1231
|
+
failed: 0,
|
|
1232
|
+
created: 0,
|
|
1233
|
+
updated: 0
|
|
957
1234
|
};
|
|
958
1235
|
this._jobId = null;
|
|
959
1236
|
// Drop any fields map from a prior attempt so a no-new-fields "Try again"
|
|
@@ -1007,17 +1284,19 @@ class ImportState {
|
|
|
1007
1284
|
..._model.queryStatus.success,
|
|
1008
1285
|
data: this.result
|
|
1009
1286
|
};
|
|
1287
|
+
// The job reports counts only when it finishes, so the bar sat at 0 for
|
|
1288
|
+
// the whole import (CAIRO-4476). Snap it to 100% and hold it (below)
|
|
1289
|
+
// before the summary step replaces the progress step, so the user sees
|
|
1290
|
+
// the import reach completion.
|
|
1291
|
+
this.processedCount = this.totalCount;
|
|
1010
1292
|
});
|
|
1011
1293
|
|
|
1012
|
-
//
|
|
1013
|
-
//
|
|
1014
|
-
// the
|
|
1015
|
-
|
|
1016
|
-
if (this.
|
|
1017
|
-
|
|
1018
|
-
if (this._cancelled) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1294
|
+
// Keep the completed bar on screen briefly before moving on. `importStatus`
|
|
1295
|
+
// is already success, so a close during this window just dismisses the
|
|
1296
|
+
// modal (the `close()` guard doesn't treat it as a cancel).
|
|
1297
|
+
await this._lingerOnCompletion();
|
|
1298
|
+
if (this._cancelled) {
|
|
1299
|
+
return;
|
|
1021
1300
|
}
|
|
1022
1301
|
|
|
1023
1302
|
// Success path only. `_pendingFieldsMap` holds this run's returned map — it's
|
|
@@ -1025,12 +1304,12 @@ class ImportState {
|
|
|
1025
1304
|
// `_resolveFieldMapping`; snapshotting here keeps the merge success-gated.
|
|
1026
1305
|
createdFields = this._pendingFieldsMap;
|
|
1027
1306
|
(0, _mobx.runInAction)(() => {
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
this.
|
|
1307
|
+
// Import finished: show the summary step (created / updated / failed
|
|
1308
|
+
// counts, plus a "Download CSV" of the failed rows when any failed).
|
|
1309
|
+
// The modal stays open; the user dismisses it from the summary via
|
|
1310
|
+
// `done`. Refresh the collection now so the table behind the modal is
|
|
1311
|
+
// up to date by the time it closes.
|
|
1312
|
+
this.currentStep = 'summary';
|
|
1034
1313
|
this.collection.clearResultAndMoveToStart({
|
|
1035
1314
|
force: true
|
|
1036
1315
|
});
|