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