@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
|
@@ -19,13 +19,17 @@ const COMPLETED_JOB = {
|
|
|
19
19
|
itemsSuccessful: 2,
|
|
20
20
|
itemsRejected: 0,
|
|
21
21
|
total: 2,
|
|
22
|
-
current: 2
|
|
22
|
+
current: 2,
|
|
23
|
+
itemsSuccessfulInfo: {
|
|
24
|
+
itemsCreated: 1,
|
|
25
|
+
itemsUpdated: 1
|
|
26
|
+
}
|
|
23
27
|
}
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
//
|
|
27
|
-
// (for schema-backed configs).
|
|
28
|
-
// is
|
|
30
|
+
// Convenience flat overrides, assembled into the `dataSource` union below;
|
|
31
|
+
// `config` fully replaces the assembled config (for schema-backed configs).
|
|
32
|
+
// `detectColumnType` is an infra prop.
|
|
29
33
|
|
|
30
34
|
function setup(clientOverrides = {}, extra = {}) {
|
|
31
35
|
const client = {
|
|
@@ -49,15 +53,49 @@ function setup(clientOverrides = {}, extra = {}) {
|
|
|
49
53
|
const {
|
|
50
54
|
detectColumnType,
|
|
51
55
|
config: configOverride,
|
|
52
|
-
|
|
56
|
+
collectionId,
|
|
57
|
+
destination,
|
|
58
|
+
updateSchema,
|
|
59
|
+
createFields,
|
|
60
|
+
fieldManagement,
|
|
61
|
+
mergeFields,
|
|
62
|
+
fields,
|
|
63
|
+
idFieldKey,
|
|
64
|
+
backup,
|
|
65
|
+
jobApplicationErrorMap
|
|
53
66
|
} = extra;
|
|
67
|
+
const dataSource = destination ? {
|
|
68
|
+
destination,
|
|
69
|
+
...(createFields && {
|
|
70
|
+
createFields
|
|
71
|
+
}),
|
|
72
|
+
...(fieldManagement && {
|
|
73
|
+
fieldManagement
|
|
74
|
+
})
|
|
75
|
+
} : {
|
|
76
|
+
collectionId: collectionId ?? 'Movies',
|
|
77
|
+
...(updateSchema && {
|
|
78
|
+
updateSchema
|
|
79
|
+
})
|
|
80
|
+
};
|
|
54
81
|
const config = configOverride ?? {
|
|
55
|
-
fields: [{
|
|
82
|
+
fields: fields ?? [{
|
|
56
83
|
id: 'name',
|
|
57
84
|
header: 'Name'
|
|
58
85
|
}],
|
|
59
|
-
|
|
60
|
-
...
|
|
86
|
+
dataSource,
|
|
87
|
+
...(mergeFields && {
|
|
88
|
+
mergeFields
|
|
89
|
+
}),
|
|
90
|
+
...(idFieldKey && {
|
|
91
|
+
idFieldKey
|
|
92
|
+
}),
|
|
93
|
+
...(backup && {
|
|
94
|
+
backup
|
|
95
|
+
}),
|
|
96
|
+
...(jobApplicationErrorMap && {
|
|
97
|
+
jobApplicationErrorMap
|
|
98
|
+
})
|
|
61
99
|
};
|
|
62
100
|
const props = {
|
|
63
101
|
config,
|
|
@@ -99,8 +137,8 @@ function setup(clientOverrides = {}, extra = {}) {
|
|
|
99
137
|
}
|
|
100
138
|
describe('ImportState engine (data-hub job)', () => {
|
|
101
139
|
beforeEach(() => {
|
|
102
|
-
// The
|
|
103
|
-
//
|
|
140
|
+
// The poll loop awaits real setTimeouts between polls; make sure timers are
|
|
141
|
+
// real even if the preset defaults to fake ones.
|
|
104
142
|
jest.useRealTimers();
|
|
105
143
|
});
|
|
106
144
|
it('uploads the aligned file, submits a job, and reports success', async () => {
|
|
@@ -122,17 +160,63 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
122
160
|
}
|
|
123
161
|
}
|
|
124
162
|
});
|
|
163
|
+
|
|
164
|
+
// created/updated come from the job's itemsSuccessfulInfo.
|
|
125
165
|
expect(state.result).toEqual({
|
|
126
166
|
succeeded: 2,
|
|
127
|
-
failed: 0
|
|
167
|
+
failed: 0,
|
|
168
|
+
created: 1,
|
|
169
|
+
updated: 1
|
|
128
170
|
});
|
|
129
171
|
expect(state.importStatus.status).toBe('success');
|
|
130
|
-
|
|
172
|
+
// The modal stays open on the summary step; the collection is refreshed now
|
|
173
|
+
// so the table is current once the user closes it via `done`.
|
|
174
|
+
expect(state.currentStep).toBe('summary');
|
|
175
|
+
expect(importModalState.close).not.toHaveBeenCalled();
|
|
131
176
|
expect(collection.clearResultAndMoveToStart).toHaveBeenCalledWith({
|
|
132
177
|
force: true
|
|
133
178
|
});
|
|
134
179
|
});
|
|
135
|
-
it('
|
|
180
|
+
it('holds the completed (100%) progress bar on the progress step before the summary (CAIRO-4476)', async () => {
|
|
181
|
+
const {
|
|
182
|
+
state
|
|
183
|
+
} = setup();
|
|
184
|
+
|
|
185
|
+
// The job reports counts only when it finishes, so the bar can only reach
|
|
186
|
+
// 100% at the very end. Capture the step + counts at the moment the linger
|
|
187
|
+
// starts to prove the user sees the completed bar before the summary
|
|
188
|
+
// replaces it.
|
|
189
|
+
let atLinger = null;
|
|
190
|
+
const spy = jest
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
|
+
.spyOn(state, '_lingerOnCompletion').mockImplementation(() => {
|
|
193
|
+
atLinger = {
|
|
194
|
+
step: state.currentStep,
|
|
195
|
+
processed: state.processedCount,
|
|
196
|
+
total: state.totalCount
|
|
197
|
+
};
|
|
198
|
+
return Promise.resolve();
|
|
199
|
+
});
|
|
200
|
+
await state.startImport();
|
|
201
|
+
expect(atLinger).toEqual({
|
|
202
|
+
step: 'progress',
|
|
203
|
+
processed: 2,
|
|
204
|
+
total: 2
|
|
205
|
+
});
|
|
206
|
+
expect(state.currentStep).toBe('summary');
|
|
207
|
+
spy.mockRestore();
|
|
208
|
+
});
|
|
209
|
+
it('closes the modal only when the user dismisses the summary via done', async () => {
|
|
210
|
+
const {
|
|
211
|
+
state,
|
|
212
|
+
importModalState
|
|
213
|
+
} = setup();
|
|
214
|
+
await state.startImport();
|
|
215
|
+
expect(importModalState.close).not.toHaveBeenCalled();
|
|
216
|
+
state.done();
|
|
217
|
+
expect(importModalState.close).toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
it('shows the summary step (no toast) when some rows are rejected', async () => {
|
|
136
220
|
const {
|
|
137
221
|
state,
|
|
138
222
|
showToast,
|
|
@@ -145,25 +229,30 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
145
229
|
itemsSuccessful: 1,
|
|
146
230
|
itemsRejected: 1,
|
|
147
231
|
total: 2,
|
|
148
|
-
current: 2
|
|
232
|
+
current: 2,
|
|
233
|
+
itemsSuccessfulInfo: {
|
|
234
|
+
itemsCreated: 1,
|
|
235
|
+
itemsUpdated: 0
|
|
236
|
+
}
|
|
149
237
|
}
|
|
150
238
|
}))
|
|
151
239
|
});
|
|
152
240
|
await state.startImport();
|
|
153
241
|
expect(state.result).toEqual({
|
|
154
242
|
succeeded: 1,
|
|
155
|
-
failed: 1
|
|
243
|
+
failed: 1,
|
|
244
|
+
created: 1,
|
|
245
|
+
updated: 0
|
|
156
246
|
});
|
|
157
|
-
expect(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
expect(importModalState.close).toHaveBeenCalled();
|
|
247
|
+
expect(state.currentStep).toBe('summary');
|
|
248
|
+
// The completion toast is gone — the summary step reports the outcome.
|
|
249
|
+
expect(showToast).not.toHaveBeenCalled();
|
|
250
|
+
expect(importModalState.close).not.toHaveBeenCalled();
|
|
161
251
|
});
|
|
162
252
|
it('maps the failed "row N" by record (unaffected by an embedded-newline cell)', async () => {
|
|
163
253
|
const {
|
|
164
254
|
state,
|
|
165
|
-
downloadCsv
|
|
166
|
-
showToast
|
|
255
|
+
downloadCsv
|
|
167
256
|
} = setup({
|
|
168
257
|
getJob: jest.fn(async () => ({
|
|
169
258
|
id: 'job-1',
|
|
@@ -206,19 +295,17 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
206
295
|
};
|
|
207
296
|
});
|
|
208
297
|
await state.startImport();
|
|
209
|
-
|
|
210
|
-
await toast.action.onClick();
|
|
298
|
+
await state.downloadFailedRows();
|
|
211
299
|
|
|
212
300
|
// "row 4" → csvRows[2] (Mango), NOT shifted by the embedded newline in row 0.
|
|
213
301
|
const [content] = downloadCsv.mock.calls[0];
|
|
214
302
|
expect(content).toContain('Mango');
|
|
215
303
|
expect(content).not.toContain('Apple');
|
|
216
304
|
});
|
|
217
|
-
it('downloads a re-importable CSV of the failed rows
|
|
305
|
+
it('downloads a re-importable CSV of the failed rows, each prefixed with its failure reason', async () => {
|
|
218
306
|
const {
|
|
219
307
|
state,
|
|
220
308
|
client,
|
|
221
|
-
showToast,
|
|
222
309
|
downloadCsv
|
|
223
310
|
} = setup({
|
|
224
311
|
getJob: jest.fn(async () => ({
|
|
@@ -235,7 +322,7 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
235
322
|
sourceItemId: 'row 3',
|
|
236
323
|
failure: {
|
|
237
324
|
code: 'bad',
|
|
238
|
-
description: '
|
|
325
|
+
description: 'Invalid value'
|
|
239
326
|
}
|
|
240
327
|
}],
|
|
241
328
|
pagingMetadata: {
|
|
@@ -244,13 +331,12 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
244
331
|
}))
|
|
245
332
|
});
|
|
246
333
|
await state.startImport();
|
|
247
|
-
|
|
248
|
-
// Trigger the toast's download action.
|
|
249
|
-
const toast = showToast.mock.calls[0][0];
|
|
250
|
-
await toast.action.onClick();
|
|
334
|
+
await state.downloadFailedRows();
|
|
251
335
|
expect(client.listMovementLogs).toHaveBeenCalledWith('job-1', undefined);
|
|
252
|
-
//
|
|
253
|
-
|
|
336
|
+
// A leading reason column (the translated header) + original headers, then
|
|
337
|
+
// the failed row prefixed with the server's failure reason — ready to fix
|
|
338
|
+
// and re-upload. `translate` here echoes the key for the column header.
|
|
339
|
+
expect(downloadCsv).toHaveBeenCalledWith('cairo.import.failureReasonColumn,Name\r\nInvalid value,Pear', 'failed-data.csv');
|
|
254
340
|
});
|
|
255
341
|
it('surfaces an error state with an application-error-shaped error when the job fails', async () => {
|
|
256
342
|
var _error$response;
|
|
@@ -278,17 +364,18 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
278
364
|
expect(importModalState.close).not.toHaveBeenCalled();
|
|
279
365
|
expect(collection.clearResultAndMoveToStart).not.toHaveBeenCalled();
|
|
280
366
|
});
|
|
281
|
-
it('
|
|
367
|
+
it('resolves a failed job through the consumer jobApplicationErrorMap', async () => {
|
|
282
368
|
var _error$response2;
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
// against the real error handler is
|
|
287
|
-
// Table.importButton spec
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
369
|
+
// ImportState wraps the failure with `errorHandler.withErrorHandler(run,
|
|
370
|
+
// jobApplicationErrorMap)` itself; the consumer only supplies the per-code
|
|
371
|
+
// map. The surfaced error keeps the application-error-shaped code intact.
|
|
372
|
+
// End-to-end resolution against the real error handler (getResolvedError) is
|
|
373
|
+
// covered in the cairo Table.importButton spec.
|
|
374
|
+
const jobApplicationErrorMap = {
|
|
375
|
+
duplicate_key: () => ({
|
|
376
|
+
message: 'Bad rows'
|
|
377
|
+
})
|
|
378
|
+
};
|
|
292
379
|
const {
|
|
293
380
|
state
|
|
294
381
|
} = setup({
|
|
@@ -301,11 +388,9 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
301
388
|
}
|
|
302
389
|
}))
|
|
303
390
|
}, {
|
|
304
|
-
|
|
391
|
+
jobApplicationErrorMap
|
|
305
392
|
});
|
|
306
393
|
await state.startImport();
|
|
307
|
-
expect(onJobError).toHaveBeenCalledTimes(1);
|
|
308
|
-
expect(typeof onJobError.mock.calls[0][0]).toBe('function');
|
|
309
394
|
expect(state.importStatus.status).toBe('error');
|
|
310
395
|
const error = state.importStatus.error;
|
|
311
396
|
expect((_error$response2 = error.response) == null || (_error$response2 = _error$response2.data) == null || (_error$response2 = _error$response2.details) == null || (_error$response2 = _error$response2.applicationError) == null ? void 0 : _error$response2.code).toBe('duplicate_key');
|
|
@@ -365,7 +450,7 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
365
450
|
// forever; it must stop after the first fetch.
|
|
366
451
|
expect(client.getJob).toHaveBeenCalledTimes(1);
|
|
367
452
|
});
|
|
368
|
-
it('does not restore
|
|
453
|
+
it('does not restore a completed import when the summary is closed', async () => {
|
|
369
454
|
const restoreFn = jest.fn().mockResolvedValue(undefined);
|
|
370
455
|
const {
|
|
371
456
|
state,
|
|
@@ -376,30 +461,19 @@ describe('ImportState engine (data-hub job)', () => {
|
|
|
376
461
|
restore: restoreFn
|
|
377
462
|
})
|
|
378
463
|
});
|
|
464
|
+
await state.startImport();
|
|
379
465
|
|
|
380
|
-
//
|
|
381
|
-
// linger timer.
|
|
382
|
-
const flush = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
383
|
-
const promise = state.startImport();
|
|
384
|
-
// Run backup → upload → submit → poll (terminal) → success, parking on the
|
|
385
|
-
// 600ms completion linger (still on the 'progress' step) without firing it.
|
|
386
|
-
await flush();
|
|
387
|
-
await flush();
|
|
388
|
-
await flush();
|
|
466
|
+
// The import finished on the summary step and the collection was refreshed.
|
|
389
467
|
expect(state.importStatus.isSuccess).toBe(true);
|
|
390
|
-
expect(state.currentStep).toBe('
|
|
468
|
+
expect(state.currentStep).toBe('summary');
|
|
469
|
+
expect(collection.clearResultAndMoveToStart).toHaveBeenCalledWith({
|
|
470
|
+
force: true
|
|
471
|
+
});
|
|
391
472
|
|
|
392
|
-
//
|
|
473
|
+
// Closing the summary must not revert a successful import.
|
|
393
474
|
await state.close();
|
|
394
|
-
await promise;
|
|
395
|
-
|
|
396
|
-
// A successful import must not be reverted; the modal closes and the
|
|
397
|
-
// collection refreshes.
|
|
398
475
|
expect(restoreFn).not.toHaveBeenCalled();
|
|
399
476
|
expect(importModalState.close).toHaveBeenCalled();
|
|
400
|
-
expect(collection.clearResultAndMoveToStart).toHaveBeenCalledWith({
|
|
401
|
-
force: true
|
|
402
|
-
});
|
|
403
477
|
});
|
|
404
478
|
});
|
|
405
479
|
describe('ImportState writePolicy (id-collision handling)', () => {
|
|
@@ -685,6 +759,115 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
685
759
|
const wixData = setup().state;
|
|
686
760
|
expect(wixData.canConfigureWritePolicy).toBe(true);
|
|
687
761
|
});
|
|
762
|
+
describe('DataExtension create fields (destination branch)', () => {
|
|
763
|
+
it('enables create-new-field when the destination branch supplies createFields', () => {
|
|
764
|
+
const withCreate = setup({}, {
|
|
765
|
+
destination: {
|
|
766
|
+
storesCatalogProducts: {}
|
|
767
|
+
},
|
|
768
|
+
createFields: jest.fn(async () => undefined)
|
|
769
|
+
}).state;
|
|
770
|
+
expect(withCreate.canCreateNewField).toBe(true);
|
|
771
|
+
const withoutCreate = setup({}, {
|
|
772
|
+
destination: {
|
|
773
|
+
storesCatalogProducts: {}
|
|
774
|
+
}
|
|
775
|
+
}).state;
|
|
776
|
+
expect(withoutCreate.canCreateNewField).toBe(false);
|
|
777
|
+
});
|
|
778
|
+
it('hands createFields the deduped pending columns (source builds its own def)', async () => {
|
|
779
|
+
const createFields = jest.fn(async () => undefined);
|
|
780
|
+
const {
|
|
781
|
+
state,
|
|
782
|
+
client
|
|
783
|
+
} = setup({}, {
|
|
784
|
+
destination: {
|
|
785
|
+
storesCatalogProducts: {}
|
|
786
|
+
},
|
|
787
|
+
createFields
|
|
788
|
+
});
|
|
789
|
+
(0, _mobx.runInAction)(() => {
|
|
790
|
+
state.csvHeaders = ['Name', 'Color'];
|
|
791
|
+
state.csvRows = [['Apple', 'Red'], ['Pear', 'Green']];
|
|
792
|
+
state.mappingByHeader = {
|
|
793
|
+
Name: {
|
|
794
|
+
kind: 'existing',
|
|
795
|
+
fieldId: 'name'
|
|
796
|
+
},
|
|
797
|
+
Color: {
|
|
798
|
+
kind: 'new',
|
|
799
|
+
fieldType: 'text'
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
});
|
|
803
|
+
await state.startImport();
|
|
804
|
+
expect(createFields).toHaveBeenCalledTimes(1);
|
|
805
|
+
// Core passes the neutral pending column (deduped key + name + the mapping);
|
|
806
|
+
// the DataExtension source turns it into its own field shape.
|
|
807
|
+
expect(createFields).toHaveBeenCalledWith([{
|
|
808
|
+
key: 'color',
|
|
809
|
+
displayName: 'Color',
|
|
810
|
+
mapping: {
|
|
811
|
+
kind: 'new',
|
|
812
|
+
fieldType: 'text'
|
|
813
|
+
}
|
|
814
|
+
}]);
|
|
815
|
+
// The consumer's destination is still sent verbatim.
|
|
816
|
+
expect(client.submitImportJob).toHaveBeenCalledWith(expect.objectContaining({
|
|
817
|
+
destination: {
|
|
818
|
+
storesCatalogProducts: {}
|
|
819
|
+
}
|
|
820
|
+
}));
|
|
821
|
+
});
|
|
822
|
+
it('carries the edited mapping config (precise type, permissions, PII) in the pending column', async () => {
|
|
823
|
+
const createFields = jest.fn(async () => undefined);
|
|
824
|
+
const {
|
|
825
|
+
state
|
|
826
|
+
} = setup({}, {
|
|
827
|
+
destination: {
|
|
828
|
+
storesCatalogProducts: {}
|
|
829
|
+
},
|
|
830
|
+
createFields
|
|
831
|
+
});
|
|
832
|
+
const config = {
|
|
833
|
+
deType: 'decimal',
|
|
834
|
+
isPii: true,
|
|
835
|
+
permissions: {
|
|
836
|
+
installedApps: {
|
|
837
|
+
defaultRead: true,
|
|
838
|
+
defaultWrite: false
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
(0, _mobx.runInAction)(() => {
|
|
843
|
+
state.csvHeaders = ['Name', 'Count'];
|
|
844
|
+
state.csvRows = [['Apple', '3']];
|
|
845
|
+
state.mappingByHeader = {
|
|
846
|
+
Name: {
|
|
847
|
+
kind: 'existing',
|
|
848
|
+
fieldId: 'name'
|
|
849
|
+
},
|
|
850
|
+
Count: {
|
|
851
|
+
kind: 'new',
|
|
852
|
+
fieldType: 'number',
|
|
853
|
+
displayName: 'Count',
|
|
854
|
+
config
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
});
|
|
858
|
+
await state.startImport();
|
|
859
|
+
expect(createFields).toHaveBeenCalledWith([{
|
|
860
|
+
key: 'count',
|
|
861
|
+
displayName: 'Count',
|
|
862
|
+
mapping: {
|
|
863
|
+
kind: 'new',
|
|
864
|
+
fieldType: 'number',
|
|
865
|
+
displayName: 'Count',
|
|
866
|
+
config
|
|
867
|
+
}
|
|
868
|
+
}]);
|
|
869
|
+
});
|
|
870
|
+
});
|
|
688
871
|
describe('created fields (CAIRO-4400)', () => {
|
|
689
872
|
const RETURNED_FIELDS = {
|
|
690
873
|
name: {
|
|
@@ -807,7 +990,6 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
807
990
|
const updateSchema = jest.fn(async () => RETURNED_FIELDS);
|
|
808
991
|
const {
|
|
809
992
|
state,
|
|
810
|
-
importModalState,
|
|
811
993
|
collection
|
|
812
994
|
} = setup({}, {
|
|
813
995
|
updateSchema,
|
|
@@ -818,22 +1000,32 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
818
1000
|
expect(mergeFields).toHaveBeenCalledTimes(1);
|
|
819
1001
|
// A throwing merge is swallowed — the completed import is untouched.
|
|
820
1002
|
expect(state.importStatus.status).toBe('success');
|
|
821
|
-
expect(
|
|
1003
|
+
expect(state.currentStep).toBe('summary');
|
|
822
1004
|
expect(collection.clearResultAndMoveToStart).toHaveBeenCalled();
|
|
823
1005
|
consoleError.mockRestore();
|
|
824
1006
|
});
|
|
825
1007
|
});
|
|
826
1008
|
describe('schema-backed config (live getters + create-field permission)', () => {
|
|
827
|
-
// A schema-backed config
|
|
828
|
-
// updateSchema
|
|
1009
|
+
// A schema-backed config: the CMS `dataSource` branch a SchemaImportRef
|
|
1010
|
+
// satisfies. It supplies `updateSchema` only when create is permitted — its
|
|
1011
|
+
// presence is the create-new-field opt-in (no separate flag).
|
|
829
1012
|
const schemaConfig = (over = {}) => ({
|
|
830
|
-
fields: [{
|
|
1013
|
+
fields: over.fields ?? [{
|
|
831
1014
|
id: 'name',
|
|
832
1015
|
header: 'Name'
|
|
833
1016
|
}],
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1017
|
+
dataSource: {
|
|
1018
|
+
collectionId: over.collectionId ?? 'RefCollection',
|
|
1019
|
+
...(over.updateSchema && {
|
|
1020
|
+
updateSchema: over.updateSchema
|
|
1021
|
+
})
|
|
1022
|
+
},
|
|
1023
|
+
...(over.idFieldKey && {
|
|
1024
|
+
idFieldKey: over.idFieldKey
|
|
1025
|
+
}),
|
|
1026
|
+
...(over.mergeFields && {
|
|
1027
|
+
mergeFields: over.mergeFields
|
|
1028
|
+
})
|
|
837
1029
|
});
|
|
838
1030
|
it('reads fields live from the config rather than snapshotting them', () => {
|
|
839
1031
|
const config = schemaConfig();
|
|
@@ -856,17 +1048,17 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
856
1048
|
config.fields = updated;
|
|
857
1049
|
expect(state.fields).toBe(updated);
|
|
858
1050
|
});
|
|
859
|
-
it('gates create-new-field on
|
|
1051
|
+
it('gates create-new-field on the presence of a create callback (updateSchema)', () => {
|
|
1052
|
+
// No create callback ⇒ create-new-field off (the source didn't permit it).
|
|
860
1053
|
const off = setup({}, {
|
|
861
|
-
config: schemaConfig(
|
|
862
|
-
canAddFields: false,
|
|
863
|
-
updateSchema: jest.fn()
|
|
864
|
-
})
|
|
1054
|
+
config: schemaConfig()
|
|
865
1055
|
});
|
|
866
1056
|
expect(off.state.canCreateNewField).toBe(false);
|
|
1057
|
+
|
|
1058
|
+
// A create callback present ⇒ on.
|
|
867
1059
|
const on = setup({}, {
|
|
868
1060
|
config: schemaConfig({
|
|
869
|
-
|
|
1061
|
+
updateSchema: jest.fn()
|
|
870
1062
|
})
|
|
871
1063
|
});
|
|
872
1064
|
expect(on.state.canCreateNewField).toBe(true);
|
|
@@ -906,7 +1098,6 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
906
1098
|
state
|
|
907
1099
|
} = setup({}, {
|
|
908
1100
|
config: schemaConfig({
|
|
909
|
-
canAddFields: true,
|
|
910
1101
|
updateSchema
|
|
911
1102
|
})
|
|
912
1103
|
});
|
|
@@ -947,4 +1138,147 @@ describe('ImportState platformized destination (non–Wix-Data)', () => {
|
|
|
947
1138
|
});
|
|
948
1139
|
});
|
|
949
1140
|
});
|
|
1141
|
+
describe('ImportState required fields (CAIRO-4452)', () => {
|
|
1142
|
+
beforeEach(() => {
|
|
1143
|
+
jest.useRealTimers();
|
|
1144
|
+
});
|
|
1145
|
+
it('is satisfied when a required field has a column mapped to it', () => {
|
|
1146
|
+
const {
|
|
1147
|
+
state
|
|
1148
|
+
} = setup({}, {
|
|
1149
|
+
config: {
|
|
1150
|
+
fields: [{
|
|
1151
|
+
id: 'name',
|
|
1152
|
+
header: 'Name',
|
|
1153
|
+
required: true
|
|
1154
|
+
}],
|
|
1155
|
+
dataSource: {
|
|
1156
|
+
collectionId: 'Movies'
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// The seeded mapping maps the "Name" column to the `name` field.
|
|
1162
|
+
expect(state.hasMissingRequiredFields).toBe(false);
|
|
1163
|
+
expect(state.missingRequiredFields).toEqual([]);
|
|
1164
|
+
});
|
|
1165
|
+
it('flags a required field the user set to "don\'t import"', () => {
|
|
1166
|
+
const {
|
|
1167
|
+
state
|
|
1168
|
+
} = setup({}, {
|
|
1169
|
+
config: {
|
|
1170
|
+
fields: [{
|
|
1171
|
+
id: 'name',
|
|
1172
|
+
header: 'Name',
|
|
1173
|
+
required: true
|
|
1174
|
+
}],
|
|
1175
|
+
dataSource: {
|
|
1176
|
+
collectionId: 'Movies'
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
state.setMapping('Name', null);
|
|
1181
|
+
expect(state.hasMissingRequiredFields).toBe(true);
|
|
1182
|
+
expect(state.missingRequiredFields.map(f => f.id)).toEqual(['name']);
|
|
1183
|
+
});
|
|
1184
|
+
it('ignores unmapped fields that are not required', () => {
|
|
1185
|
+
const {
|
|
1186
|
+
state
|
|
1187
|
+
} = setup({}, {
|
|
1188
|
+
config: {
|
|
1189
|
+
fields: [{
|
|
1190
|
+
id: 'name',
|
|
1191
|
+
header: 'Name'
|
|
1192
|
+
}, {
|
|
1193
|
+
id: 'year',
|
|
1194
|
+
header: 'Year',
|
|
1195
|
+
required: false
|
|
1196
|
+
}],
|
|
1197
|
+
dataSource: {
|
|
1198
|
+
collectionId: 'Movies'
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
state.setMapping('Name', null);
|
|
1203
|
+
expect(state.hasMissingRequiredFields).toBe(false);
|
|
1204
|
+
});
|
|
1205
|
+
it('is not satisfied by a create-new-field mapping', () => {
|
|
1206
|
+
const {
|
|
1207
|
+
state
|
|
1208
|
+
} = setup({}, {
|
|
1209
|
+
config: {
|
|
1210
|
+
fields: [{
|
|
1211
|
+
id: 'name',
|
|
1212
|
+
header: 'Name',
|
|
1213
|
+
required: true
|
|
1214
|
+
}],
|
|
1215
|
+
dataSource: {
|
|
1216
|
+
collectionId: 'Movies',
|
|
1217
|
+
updateSchema: async () => undefined
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
// Mapping the column to a brand-new field can't fill the existing required
|
|
1223
|
+
// field, so it stays missing.
|
|
1224
|
+
state.setNewFieldMapping('Name');
|
|
1225
|
+
expect(state.hasMissingRequiredFields).toBe(true);
|
|
1226
|
+
});
|
|
1227
|
+
it('blocks startImport and shows an error toast when a required field is missing', async () => {
|
|
1228
|
+
const {
|
|
1229
|
+
state,
|
|
1230
|
+
client,
|
|
1231
|
+
showToast
|
|
1232
|
+
} = setup({}, {
|
|
1233
|
+
config: {
|
|
1234
|
+
fields: [{
|
|
1235
|
+
id: 'name',
|
|
1236
|
+
header: 'Name',
|
|
1237
|
+
required: true
|
|
1238
|
+
}, {
|
|
1239
|
+
id: 'year',
|
|
1240
|
+
header: 'Year'
|
|
1241
|
+
}],
|
|
1242
|
+
dataSource: {
|
|
1243
|
+
collectionId: 'Movies'
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
// Keep a mapping (so the import isn't blocked by "no mappings") but leave the
|
|
1249
|
+
// required `name` field unmapped.
|
|
1250
|
+
(0, _mobx.runInAction)(() => {
|
|
1251
|
+
state.csvHeaders = ['Name', 'Year'];
|
|
1252
|
+
state.csvRows = [['Apple', '2020']];
|
|
1253
|
+
});
|
|
1254
|
+
state.setMapping('Name', null);
|
|
1255
|
+
state.setMapping('Year', 'year');
|
|
1256
|
+
await state.startImport();
|
|
1257
|
+
expect(client.uploadFile).not.toHaveBeenCalled();
|
|
1258
|
+
expect(client.submitImportJob).not.toHaveBeenCalled();
|
|
1259
|
+
expect(state.currentStep).not.toBe('progress');
|
|
1260
|
+
expect(showToast).toHaveBeenCalledWith(expect.objectContaining({
|
|
1261
|
+
type: 'ERROR'
|
|
1262
|
+
}));
|
|
1263
|
+
});
|
|
1264
|
+
it('proceeds with import when all required fields are mapped', async () => {
|
|
1265
|
+
const {
|
|
1266
|
+
state,
|
|
1267
|
+
client
|
|
1268
|
+
} = setup({}, {
|
|
1269
|
+
config: {
|
|
1270
|
+
fields: [{
|
|
1271
|
+
id: 'name',
|
|
1272
|
+
header: 'Name',
|
|
1273
|
+
required: true
|
|
1274
|
+
}],
|
|
1275
|
+
dataSource: {
|
|
1276
|
+
collectionId: 'Movies'
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
await state.startImport();
|
|
1281
|
+
expect(client.submitImportJob).toHaveBeenCalledTimes(1);
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
950
1284
|
//# sourceMappingURL=ImportState.engine.spec.js.map
|