@genesislcap/pbc-reporting-ui 14.353.3 → 14.353.4
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/dts/new/components/clone-report-config-modal/clone-report-config-modal.d.ts.map +1 -1
- package/dist/dts/new/components/reporting-configurations/reporting-configurations.d.ts.map +1 -1
- package/dist/dts/new/utils/transformers.d.ts +2 -1
- package/dist/dts/new/utils/transformers.d.ts.map +1 -1
- package/dist/dts/new/utils/transformers.test.d.ts +2 -0
- package/dist/dts/new/utils/transformers.test.d.ts.map +1 -0
- package/dist/esm/new/components/clone-report-config-modal/clone-report-config-modal.js +3 -2
- package/dist/esm/new/components/reporting-configurations/reporting-configurations.js +3 -2
- package/dist/esm/new/utils/transformers.js +63 -22
- package/dist/esm/new/utils/transformers.test.js +419 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -22
- package/src/new/components/clone-report-config-modal/clone-report-config-modal.ts +4 -2
- package/src/new/components/reporting-configurations/reporting-configurations.ts +4 -2
- package/src/new/utils/transformers.test.ts +487 -0
- package/src/new/utils/transformers.ts +71 -11
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { assert, sinon, suite } from '@genesislcap/foundation-testing';
|
|
2
|
+
import type { Genesis } from '../types';
|
|
3
|
+
import { buildDatasourceName, transformFromServerPayload } from './transformers';
|
|
4
|
+
|
|
5
|
+
const TransformFromServerPayload = suite('transformFromServerPayload');
|
|
6
|
+
|
|
7
|
+
TransformFromServerPayload.before.each(() => {
|
|
8
|
+
sinon.restore();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
TransformFromServerPayload('cleans up orphaned fields from INCLUDE_COLUMNS', async () => {
|
|
12
|
+
const mockGetSchema = sinon.stub();
|
|
13
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
14
|
+
properties: {
|
|
15
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
16
|
+
AMOUNT: { genesisType: 'DOUBLE' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
mockGetSchema.resolves(validSchema);
|
|
20
|
+
|
|
21
|
+
const payload: Genesis.ServerReportConfig = {
|
|
22
|
+
ID: 'test-id',
|
|
23
|
+
NAME: 'Test Report',
|
|
24
|
+
SCHEDULES: [],
|
|
25
|
+
DATA_SOURCES: [
|
|
26
|
+
{
|
|
27
|
+
KEY: 'COUNTERPARTY',
|
|
28
|
+
INPUT_TYPE: 'REQ_REP',
|
|
29
|
+
NAME: 'COUNTERPARTY',
|
|
30
|
+
OUTPUT_TYPE: 'TABLE',
|
|
31
|
+
TRANSFORMER_CONFIGURATION: {
|
|
32
|
+
INCLUDE_COLUMNS: ['COUNTERPARTY_NAME', 'AMOUNT', 'NAME'], // NAME is orphaned
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
DESTINATION_IDS: [],
|
|
37
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
41
|
+
|
|
42
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
43
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
44
|
+
|
|
45
|
+
assert.equal(cleanedConfig.INCLUDE_COLUMNS?.length, 2);
|
|
46
|
+
assert.ok(cleanedConfig.INCLUDE_COLUMNS?.includes('COUNTERPARTY_NAME'));
|
|
47
|
+
assert.ok(cleanedConfig.INCLUDE_COLUMNS?.includes('AMOUNT'));
|
|
48
|
+
assert.ok(!cleanedConfig.INCLUDE_COLUMNS?.includes('NAME'));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
TransformFromServerPayload('cleans up orphaned fields from COLUMN_RENAMES', async () => {
|
|
52
|
+
const mockGetSchema = sinon.stub();
|
|
53
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
54
|
+
properties: {
|
|
55
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
56
|
+
AMOUNT: { genesisType: 'DOUBLE' },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
mockGetSchema.resolves(validSchema);
|
|
60
|
+
|
|
61
|
+
const payload: Genesis.ServerReportConfig = {
|
|
62
|
+
ID: 'test-id',
|
|
63
|
+
NAME: 'Test Report',
|
|
64
|
+
SCHEDULES: [],
|
|
65
|
+
DATA_SOURCES: [
|
|
66
|
+
{
|
|
67
|
+
KEY: 'COUNTERPARTY',
|
|
68
|
+
INPUT_TYPE: 'REQ_REP',
|
|
69
|
+
NAME: 'COUNTERPARTY',
|
|
70
|
+
OUTPUT_TYPE: 'TABLE',
|
|
71
|
+
TRANSFORMER_CONFIGURATION: {
|
|
72
|
+
COLUMN_RENAMES: {
|
|
73
|
+
COUNTERPARTY_NAME: 'Counterparty Name',
|
|
74
|
+
AMOUNT: 'Amount',
|
|
75
|
+
NAME: 'Old Name', // orphaned
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
DESTINATION_IDS: [],
|
|
81
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
85
|
+
|
|
86
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
87
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
88
|
+
|
|
89
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_RENAMES || {}).length, 2);
|
|
90
|
+
assert.equal(cleanedConfig.COLUMN_RENAMES?.['COUNTERPARTY_NAME'], 'Counterparty Name');
|
|
91
|
+
assert.equal(cleanedConfig.COLUMN_RENAMES?.['AMOUNT'], 'Amount');
|
|
92
|
+
assert.ok(!cleanedConfig.COLUMN_RENAMES?.['NAME']);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
TransformFromServerPayload('cleans up orphaned fields from COLUMN_FORMATS', async () => {
|
|
96
|
+
const mockGetSchema = sinon.stub();
|
|
97
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
98
|
+
properties: {
|
|
99
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
100
|
+
AMOUNT: { genesisType: 'DOUBLE' },
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
mockGetSchema.resolves(validSchema);
|
|
104
|
+
|
|
105
|
+
const payload: Genesis.ServerReportConfig = {
|
|
106
|
+
ID: 'test-id',
|
|
107
|
+
NAME: 'Test Report',
|
|
108
|
+
SCHEDULES: [],
|
|
109
|
+
DATA_SOURCES: [
|
|
110
|
+
{
|
|
111
|
+
KEY: 'COUNTERPARTY',
|
|
112
|
+
INPUT_TYPE: 'REQ_REP',
|
|
113
|
+
NAME: 'COUNTERPARTY',
|
|
114
|
+
OUTPUT_TYPE: 'TABLE',
|
|
115
|
+
TRANSFORMER_CONFIGURATION: {
|
|
116
|
+
COLUMN_FORMATS: {
|
|
117
|
+
COUNTERPARTY_NAME: 'text',
|
|
118
|
+
AMOUNT: '#,##0.00',
|
|
119
|
+
NAME: 'text', // orphaned
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
DESTINATION_IDS: [],
|
|
125
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
129
|
+
|
|
130
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
131
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
132
|
+
|
|
133
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_FORMATS || {}).length, 2);
|
|
134
|
+
assert.equal(cleanedConfig.COLUMN_FORMATS?.['COUNTERPARTY_NAME'], 'text');
|
|
135
|
+
assert.equal(cleanedConfig.COLUMN_FORMATS?.['AMOUNT'], '#,##0.00');
|
|
136
|
+
assert.ok(!cleanedConfig.COLUMN_FORMATS?.['NAME']);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
TransformFromServerPayload('cleans up orphaned fields from COLUMN_TRANSFORMS', async () => {
|
|
140
|
+
const mockGetSchema = sinon.stub();
|
|
141
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
142
|
+
properties: {
|
|
143
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
144
|
+
AMOUNT: { genesisType: 'DOUBLE' },
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
mockGetSchema.resolves(validSchema);
|
|
148
|
+
|
|
149
|
+
const payload: Genesis.ServerReportConfig = {
|
|
150
|
+
ID: 'test-id',
|
|
151
|
+
NAME: 'Test Report',
|
|
152
|
+
SCHEDULES: [],
|
|
153
|
+
DATA_SOURCES: [
|
|
154
|
+
{
|
|
155
|
+
KEY: 'COUNTERPARTY',
|
|
156
|
+
INPUT_TYPE: 'REQ_REP',
|
|
157
|
+
NAME: 'COUNTERPARTY',
|
|
158
|
+
OUTPUT_TYPE: 'TABLE',
|
|
159
|
+
TRANSFORMER_CONFIGURATION: {
|
|
160
|
+
COLUMN_TRANSFORMS: {
|
|
161
|
+
COUNTERPARTY_NAME: { TYPE: 'BINARY_EXPRESSION', LEFT: {}, OPERATION: 'AND', RIGHT: {} },
|
|
162
|
+
AMOUNT: { TYPE: 'METHOD_EXPRESSION', METHOD: 'TRIM', PARAMETERS: [] },
|
|
163
|
+
NAME: { TYPE: 'BINARY_EXPRESSION', LEFT: {}, OPERATION: 'AND', RIGHT: {} }, // orphaned
|
|
164
|
+
} as any,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
DESTINATION_IDS: [],
|
|
169
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
173
|
+
|
|
174
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
175
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
176
|
+
|
|
177
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_TRANSFORMS || {}).length, 2);
|
|
178
|
+
assert.ok(cleanedConfig.COLUMN_TRANSFORMS?.['COUNTERPARTY_NAME']);
|
|
179
|
+
assert.ok(cleanedConfig.COLUMN_TRANSFORMS?.['AMOUNT']);
|
|
180
|
+
assert.ok(!cleanedConfig.COLUMN_TRANSFORMS?.['NAME']);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
TransformFromServerPayload('handles schema fetch failure gracefully', async () => {
|
|
184
|
+
const mockGetSchema = sinon.stub();
|
|
185
|
+
const consoleWarnStub = sinon.stub(console, 'warn');
|
|
186
|
+
mockGetSchema.rejects(new Error('Schema fetch failed'));
|
|
187
|
+
|
|
188
|
+
const originalConfig = {
|
|
189
|
+
INCLUDE_COLUMNS: ['NAME', 'AMOUNT'],
|
|
190
|
+
COLUMN_RENAMES: { NAME: 'Name' },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const payload: Genesis.ServerReportConfig = {
|
|
194
|
+
ID: 'test-id',
|
|
195
|
+
NAME: 'Test Report',
|
|
196
|
+
SCHEDULES: [],
|
|
197
|
+
DATA_SOURCES: [
|
|
198
|
+
{
|
|
199
|
+
KEY: 'COUNTERPARTY',
|
|
200
|
+
INPUT_TYPE: 'REQ_REP',
|
|
201
|
+
NAME: 'COUNTERPARTY',
|
|
202
|
+
OUTPUT_TYPE: 'TABLE',
|
|
203
|
+
TRANSFORMER_CONFIGURATION: originalConfig,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
DESTINATION_IDS: [],
|
|
207
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
211
|
+
|
|
212
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
213
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
214
|
+
|
|
215
|
+
// Should keep original config when schema fetch fails
|
|
216
|
+
assert.equal(JSON.stringify(cleanedConfig), JSON.stringify(originalConfig));
|
|
217
|
+
assert.ok(consoleWarnStub.calledOnce);
|
|
218
|
+
assert.ok(consoleWarnStub.calledWithMatch(sinon.match.string, sinon.match.instanceOf(Error)));
|
|
219
|
+
|
|
220
|
+
consoleWarnStub.restore();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
TransformFromServerPayload('handles multiple datasources independently', async () => {
|
|
224
|
+
const mockGetSchema = sinon.stub();
|
|
225
|
+
mockGetSchema
|
|
226
|
+
.onFirstCall()
|
|
227
|
+
.resolves({
|
|
228
|
+
properties: {
|
|
229
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
230
|
+
},
|
|
231
|
+
} as Genesis.JSONSchema7)
|
|
232
|
+
.onSecondCall()
|
|
233
|
+
.resolves({
|
|
234
|
+
properties: {
|
|
235
|
+
TRADE_ID: { genesisType: 'STRING' },
|
|
236
|
+
},
|
|
237
|
+
} as Genesis.JSONSchema7);
|
|
238
|
+
|
|
239
|
+
const payload: Genesis.ServerReportConfig = {
|
|
240
|
+
ID: 'test-id',
|
|
241
|
+
NAME: 'Test Report',
|
|
242
|
+
SCHEDULES: [],
|
|
243
|
+
DATA_SOURCES: [
|
|
244
|
+
{
|
|
245
|
+
KEY: 'COUNTERPARTY',
|
|
246
|
+
INPUT_TYPE: 'REQ_REP',
|
|
247
|
+
NAME: 'COUNTERPARTY',
|
|
248
|
+
OUTPUT_TYPE: 'TABLE',
|
|
249
|
+
TRANSFORMER_CONFIGURATION: {
|
|
250
|
+
INCLUDE_COLUMNS: ['COUNTERPARTY_NAME', 'NAME'], // NAME is orphaned
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
KEY: 'TRADE',
|
|
255
|
+
INPUT_TYPE: 'REQ_REP',
|
|
256
|
+
NAME: 'TRADE',
|
|
257
|
+
OUTPUT_TYPE: 'TABLE',
|
|
258
|
+
TRANSFORMER_CONFIGURATION: {
|
|
259
|
+
INCLUDE_COLUMNS: ['TRADE_ID', 'OLD_FIELD'], // OLD_FIELD is orphaned
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
DESTINATION_IDS: [],
|
|
264
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
268
|
+
|
|
269
|
+
const counterpartyKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
270
|
+
const tradeKey = buildDatasourceName('TRADE', 'REQ_REP');
|
|
271
|
+
|
|
272
|
+
assert.equal(
|
|
273
|
+
result.datasourceConfig[counterpartyKey].TRANSFORMER_CONFIGURATION.INCLUDE_COLUMNS?.length,
|
|
274
|
+
1,
|
|
275
|
+
);
|
|
276
|
+
assert.ok(
|
|
277
|
+
result.datasourceConfig[counterpartyKey].TRANSFORMER_CONFIGURATION.INCLUDE_COLUMNS?.includes(
|
|
278
|
+
'COUNTERPARTY_NAME',
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
assert.equal(
|
|
283
|
+
result.datasourceConfig[tradeKey].TRANSFORMER_CONFIGURATION.INCLUDE_COLUMNS?.length,
|
|
284
|
+
1,
|
|
285
|
+
);
|
|
286
|
+
assert.ok(
|
|
287
|
+
result.datasourceConfig[tradeKey].TRANSFORMER_CONFIGURATION.INCLUDE_COLUMNS?.includes(
|
|
288
|
+
'TRADE_ID',
|
|
289
|
+
),
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
TransformFromServerPayload('handles empty transformer configuration', async () => {
|
|
294
|
+
const mockGetSchema = sinon.stub();
|
|
295
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
296
|
+
properties: {
|
|
297
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
mockGetSchema.resolves(validSchema);
|
|
301
|
+
|
|
302
|
+
const payload: Genesis.ServerReportConfig = {
|
|
303
|
+
ID: 'test-id',
|
|
304
|
+
NAME: 'Test Report',
|
|
305
|
+
SCHEDULES: [],
|
|
306
|
+
DATA_SOURCES: [
|
|
307
|
+
{
|
|
308
|
+
KEY: 'COUNTERPARTY',
|
|
309
|
+
INPUT_TYPE: 'REQ_REP',
|
|
310
|
+
NAME: 'COUNTERPARTY',
|
|
311
|
+
OUTPUT_TYPE: 'TABLE',
|
|
312
|
+
TRANSFORMER_CONFIGURATION: {},
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
DESTINATION_IDS: [],
|
|
316
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
320
|
+
|
|
321
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
322
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
323
|
+
|
|
324
|
+
assert.equal(JSON.stringify(cleanedConfig), JSON.stringify({}));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
TransformFromServerPayload('handles missing schema properties', async () => {
|
|
328
|
+
const mockGetSchema = sinon.stub();
|
|
329
|
+
const schemaWithoutProperties: Genesis.JSONSchema7 = {};
|
|
330
|
+
mockGetSchema.resolves(schemaWithoutProperties);
|
|
331
|
+
|
|
332
|
+
const payload: Genesis.ServerReportConfig = {
|
|
333
|
+
ID: 'test-id',
|
|
334
|
+
NAME: 'Test Report',
|
|
335
|
+
SCHEDULES: [],
|
|
336
|
+
DATA_SOURCES: [
|
|
337
|
+
{
|
|
338
|
+
KEY: 'COUNTERPARTY',
|
|
339
|
+
INPUT_TYPE: 'REQ_REP',
|
|
340
|
+
NAME: 'COUNTERPARTY',
|
|
341
|
+
OUTPUT_TYPE: 'TABLE',
|
|
342
|
+
TRANSFORMER_CONFIGURATION: {
|
|
343
|
+
INCLUDE_COLUMNS: ['NAME', 'AMOUNT'],
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
DESTINATION_IDS: [],
|
|
348
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
352
|
+
|
|
353
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
354
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
355
|
+
|
|
356
|
+
// All fields should be removed when schema has no properties
|
|
357
|
+
assert.equal(cleanedConfig.INCLUDE_COLUMNS?.length, 0);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
TransformFromServerPayload('preserves base config correctly', async () => {
|
|
361
|
+
const mockGetSchema = sinon.stub();
|
|
362
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
363
|
+
properties: {
|
|
364
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
mockGetSchema.resolves(validSchema);
|
|
368
|
+
|
|
369
|
+
const payload: Genesis.ServerReportConfig = {
|
|
370
|
+
ID: 'test-id',
|
|
371
|
+
NAME: 'Test Report',
|
|
372
|
+
DESCRIPTION: 'Test Description',
|
|
373
|
+
OUTPUT_FORMAT: 'CSV',
|
|
374
|
+
FILE_NAME: 'test.csv',
|
|
375
|
+
AUTO_DISPATCH: true,
|
|
376
|
+
DESTINATION_IDS: ['dest1', 'dest2'],
|
|
377
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
378
|
+
DOCUMENT_TEMPLATE_ID: 'template-123',
|
|
379
|
+
SCHEDULES: [
|
|
380
|
+
{
|
|
381
|
+
CRON_EXPRESSION: '0 0 0 * * ?',
|
|
382
|
+
TIME_ZONE: 'UTC',
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
DATA_SOURCES: [
|
|
386
|
+
{
|
|
387
|
+
KEY: 'COUNTERPARTY',
|
|
388
|
+
INPUT_TYPE: 'REQ_REP',
|
|
389
|
+
NAME: 'COUNTERPARTY',
|
|
390
|
+
OUTPUT_TYPE: 'TABLE',
|
|
391
|
+
TRANSFORMER_CONFIGURATION: {},
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
397
|
+
|
|
398
|
+
assert.equal(result.baseConfig.ID, 'test-id');
|
|
399
|
+
assert.equal(result.baseConfig.NAME, 'Test Report');
|
|
400
|
+
assert.equal(result.baseConfig.DESCRIPTION, 'Test Description');
|
|
401
|
+
assert.equal(result.baseConfig.OUTPUT_FORMAT, 'CSV');
|
|
402
|
+
assert.equal(result.baseConfig.FILE_NAME, 'test.csv');
|
|
403
|
+
assert.equal(result.baseConfig.AUTO_DISPATCH, true);
|
|
404
|
+
assert.equal(
|
|
405
|
+
JSON.stringify(result.baseConfig.DESTINATION_IDS),
|
|
406
|
+
JSON.stringify(['dest1', 'dest2']),
|
|
407
|
+
);
|
|
408
|
+
assert.equal(result.baseConfig.OUTPUT_DIRECTORY, '/reports');
|
|
409
|
+
assert.equal(result.baseConfig.DOCUMENT_TEMPLATE_ID, 'template-123');
|
|
410
|
+
assert.ok(result.baseConfig.SCHEDULE);
|
|
411
|
+
assert.equal(result.baseConfig.SCHEDULE?.CRON_EXPRESSION, '0 0 0 * * ?');
|
|
412
|
+
assert.equal(result.baseConfig.SCHEDULE?.TIME_ZONE, 'UTC');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
TransformFromServerPayload('handles all transformer config types together', async () => {
|
|
416
|
+
const mockGetSchema = sinon.stub();
|
|
417
|
+
const validSchema: Genesis.JSONSchema7 = {
|
|
418
|
+
properties: {
|
|
419
|
+
COUNTERPARTY_NAME: { genesisType: 'STRING' },
|
|
420
|
+
AMOUNT: { genesisType: 'DOUBLE' },
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
mockGetSchema.resolves(validSchema);
|
|
424
|
+
|
|
425
|
+
const payload: Genesis.ServerReportConfig = {
|
|
426
|
+
ID: 'test-id',
|
|
427
|
+
NAME: 'Test Report',
|
|
428
|
+
SCHEDULES: [],
|
|
429
|
+
DATA_SOURCES: [
|
|
430
|
+
{
|
|
431
|
+
KEY: 'COUNTERPARTY',
|
|
432
|
+
INPUT_TYPE: 'REQ_REP',
|
|
433
|
+
NAME: 'COUNTERPARTY',
|
|
434
|
+
OUTPUT_TYPE: 'TABLE',
|
|
435
|
+
TRANSFORMER_CONFIGURATION: {
|
|
436
|
+
INCLUDE_COLUMNS: ['COUNTERPARTY_NAME', 'AMOUNT', 'NAME'], // NAME orphaned
|
|
437
|
+
COLUMN_RENAMES: {
|
|
438
|
+
COUNTERPARTY_NAME: 'Counterparty',
|
|
439
|
+
NAME: 'Old Name', // orphaned
|
|
440
|
+
},
|
|
441
|
+
COLUMN_FORMATS: {
|
|
442
|
+
AMOUNT: '#,##0.00',
|
|
443
|
+
NAME: 'text', // orphaned
|
|
444
|
+
},
|
|
445
|
+
COLUMN_TRANSFORMS: {
|
|
446
|
+
COUNTERPARTY_NAME: { TYPE: 'BINARY_EXPRESSION', LEFT: {}, OPERATION: 'AND', RIGHT: {} },
|
|
447
|
+
NAME: { TYPE: 'METHOD_EXPRESSION', METHOD: 'TRIM', PARAMETERS: [] }, // orphaned
|
|
448
|
+
} as any,
|
|
449
|
+
ROW_FILTERS: {
|
|
450
|
+
TYPE: 'PREDICATE_EXPRESSION' as const,
|
|
451
|
+
OPERATION: 'AND' as const,
|
|
452
|
+
EXPRESSIONS: [],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
DESTINATION_IDS: [],
|
|
458
|
+
OUTPUT_DIRECTORY: '/reports',
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const result = await transformFromServerPayload(payload, mockGetSchema);
|
|
462
|
+
|
|
463
|
+
const datasourceKey = buildDatasourceName('COUNTERPARTY', 'REQ_REP');
|
|
464
|
+
const cleanedConfig = result.datasourceConfig[datasourceKey].TRANSFORMER_CONFIGURATION;
|
|
465
|
+
|
|
466
|
+
// INCLUDE_COLUMNS should only have valid fields
|
|
467
|
+
assert.equal(cleanedConfig.INCLUDE_COLUMNS?.length, 2);
|
|
468
|
+
assert.ok(cleanedConfig.INCLUDE_COLUMNS?.includes('COUNTERPARTY_NAME'));
|
|
469
|
+
assert.ok(cleanedConfig.INCLUDE_COLUMNS?.includes('AMOUNT'));
|
|
470
|
+
|
|
471
|
+
// COLUMN_RENAMES should only have valid fields
|
|
472
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_RENAMES || {}).length, 1);
|
|
473
|
+
assert.equal(cleanedConfig.COLUMN_RENAMES?.['COUNTERPARTY_NAME'], 'Counterparty');
|
|
474
|
+
|
|
475
|
+
// COLUMN_FORMATS should only have valid fields
|
|
476
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_FORMATS || {}).length, 1);
|
|
477
|
+
assert.equal(cleanedConfig.COLUMN_FORMATS?.['AMOUNT'], '#,##0.00');
|
|
478
|
+
|
|
479
|
+
// COLUMN_TRANSFORMS should only have valid fields
|
|
480
|
+
assert.equal(Object.keys(cleanedConfig.COLUMN_TRANSFORMS || {}).length, 1);
|
|
481
|
+
assert.ok(cleanedConfig.COLUMN_TRANSFORMS?.['COUNTERPARTY_NAME']);
|
|
482
|
+
|
|
483
|
+
// ROW_FILTERS should be preserved
|
|
484
|
+
assert.ok(cleanedConfig.ROW_FILTERS);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
TransformFromServerPayload.run();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
1
2
|
import type { BaseConfig, DatasourceInputTypes, DatasourceName, ReportingConfig } from '../store';
|
|
3
|
+
import type { TransformerConfig } from '../store/slices/types';
|
|
2
4
|
import { Display, Genesis } from '../types';
|
|
3
5
|
|
|
4
6
|
export function transformToServerPayload(state: ReportingConfig): Genesis.ServerReportConfig {
|
|
@@ -20,9 +22,41 @@ export function transformToServerPayload(state: ReportingConfig): Genesis.Server
|
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Cleans up orphaned field references from transformer configuration
|
|
27
|
+
* by validating against current schema
|
|
28
|
+
*/
|
|
29
|
+
function cleanupTransformerConfig(
|
|
30
|
+
config: TransformerConfig,
|
|
31
|
+
validFields: Set<string>,
|
|
32
|
+
): TransformerConfig {
|
|
33
|
+
return {
|
|
34
|
+
INCLUDE_COLUMNS: config.INCLUDE_COLUMNS?.filter((field) => validFields.has(field)),
|
|
35
|
+
COLUMN_RENAMES: config.COLUMN_RENAMES
|
|
36
|
+
? Object.fromEntries(
|
|
37
|
+
Object.entries(config.COLUMN_RENAMES).filter(([key]) => validFields.has(key)),
|
|
38
|
+
)
|
|
39
|
+
: undefined,
|
|
40
|
+
COLUMN_FORMATS: config.COLUMN_FORMATS
|
|
41
|
+
? Object.fromEntries(
|
|
42
|
+
Object.entries(config.COLUMN_FORMATS).filter(([key]) => validFields.has(key)),
|
|
43
|
+
)
|
|
44
|
+
: undefined,
|
|
45
|
+
COLUMN_TRANSFORMS: config.COLUMN_TRANSFORMS
|
|
46
|
+
? Object.fromEntries(
|
|
47
|
+
Object.entries(config.COLUMN_TRANSFORMS).filter(([key]) => validFields.has(key)),
|
|
48
|
+
)
|
|
49
|
+
: undefined,
|
|
50
|
+
ROW_FILTERS: config.ROW_FILTERS,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
// TODO: we'll suppress the bigints on the server so won't need to do this
|
|
24
55
|
// horrible destructing
|
|
25
|
-
export function
|
|
56
|
+
export async function transformFromServerPayload(
|
|
57
|
+
payload: Genesis.ServerReportConfig,
|
|
58
|
+
getSchema: (name: string) => Promise<JSONSchema7>,
|
|
59
|
+
): Promise<ReportingConfig> {
|
|
26
60
|
const {
|
|
27
61
|
DATA_SOURCES,
|
|
28
62
|
ID,
|
|
@@ -48,18 +82,44 @@ export function transformFromServerPaylaod(payload: Genesis.ServerReportConfig):
|
|
|
48
82
|
DOCUMENT_TEMPLATE_ID,
|
|
49
83
|
...(SCHEDULES.length > 0 && { SCHEDULE: SCHEDULES[0] }),
|
|
50
84
|
};
|
|
85
|
+
|
|
86
|
+
const cleanedDatasources = await Promise.all(
|
|
87
|
+
DATA_SOURCES.map(
|
|
88
|
+
async ({ KEY, INPUT_TYPE, NAME: DS_NAME, OUTPUT_TYPE, TRANSFORMER_CONFIGURATION }) => {
|
|
89
|
+
try {
|
|
90
|
+
const schema = await getSchema(KEY);
|
|
91
|
+
const validFields = new Set(Object.keys(schema.properties || {}));
|
|
92
|
+
|
|
93
|
+
const cleanedConfig = cleanupTransformerConfig(
|
|
94
|
+
TRANSFORMER_CONFIGURATION || {},
|
|
95
|
+
validFields,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
KEY,
|
|
100
|
+
INPUT_TYPE,
|
|
101
|
+
NAME: DS_NAME,
|
|
102
|
+
OUTPUT_TYPE,
|
|
103
|
+
TRANSFORMER_CONFIGURATION: cleanedConfig,
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// If schema fetch fails, log warning but keep original config
|
|
107
|
+
console.warn(`Failed to fetch schema for ${KEY}, skipping cleanup:`, error);
|
|
108
|
+
return {
|
|
109
|
+
KEY,
|
|
110
|
+
INPUT_TYPE,
|
|
111
|
+
NAME: DS_NAME,
|
|
112
|
+
OUTPUT_TYPE,
|
|
113
|
+
TRANSFORMER_CONFIGURATION: TRANSFORMER_CONFIGURATION || {},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
|
|
51
120
|
return {
|
|
52
121
|
baseConfig,
|
|
53
|
-
|
|
54
|
-
datasourceConfig: DATA_SOURCES.map(
|
|
55
|
-
({ KEY, INPUT_TYPE, NAME: DS_NAME, OUTPUT_TYPE, TRANSFORMER_CONFIGURATION }) => ({
|
|
56
|
-
KEY,
|
|
57
|
-
INPUT_TYPE,
|
|
58
|
-
NAME: DS_NAME,
|
|
59
|
-
OUTPUT_TYPE,
|
|
60
|
-
TRANSFORMER_CONFIGURATION,
|
|
61
|
-
}),
|
|
62
|
-
).reduce(
|
|
122
|
+
datasourceConfig: cleanedDatasources.reduce(
|
|
63
123
|
(acum, ds) => ({
|
|
64
124
|
...acum,
|
|
65
125
|
[buildDatasourceName(ds.NAME, ds.INPUT_TYPE)]: ds,
|