@truedat/dq 8.10.8 → 8.11.2

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.
@@ -1,14 +1,15 @@
1
1
  import _ from "lodash/fp";
2
2
  import { createSelector } from "reselect";
3
3
 
4
- const FIELDS_WITHOUT_VALUE_TYPES = [
5
- "url",
6
- "table",
7
- "system",
8
- "image",
9
- "markdown",
4
+ const FIELDS_WITHOUT_VALUE_TYPES = ["table", "system", "image", "markdown"];
5
+ const ADD_ON_UPDATE_FIELDS = [
6
+ "dataset",
7
+ "populations",
8
+ "validation",
9
+ "segments",
10
+ "description",
11
+ "business_concept",
10
12
  ];
11
-
12
13
  const getEvents = ({ events }) => events;
13
14
 
14
15
  const getRuleTemplate = (state) =>
@@ -21,85 +22,386 @@ const getImplementationTemplate = (state) =>
21
22
  _.getOr([], "allTemplates", state),
22
23
  );
23
24
 
24
- const getFieldsWithoutValue = (template) =>
25
- _.flow(
26
- _.getOr([], "content"),
27
- _.map(_.getOr([], "fields")),
28
- _.flatten,
29
- _.filter((f) => _.includes(_.prop("type")(f))(FIELDS_WITHOUT_VALUE_TYPES)),
30
- _.map(_.prop("name")),
31
- )(template);
25
+ const shouldHideValue = (entry, section) => {
26
+ if (!entry) return false;
27
+ if (
28
+ section === "removed" &&
29
+ entry.type &&
30
+ FIELDS_WITHOUT_VALUE_TYPES.includes(entry.type)
31
+ ) {
32
+ return true;
33
+ }
34
+ return false;
35
+ };
32
36
 
33
- const withoutTargetAction = (action, field, fieldsWithoutValue) =>
34
- _.includes(field)(fieldsWithoutValue) ? `${action}_without_target` : action;
37
+ const contentActionExtra = (section, fieldKey, fieldTypeMap) => {
38
+ const fieldType = _.get(fieldKey, fieldTypeMap);
39
+ return _.includes(fieldType)(FIELDS_WITHOUT_VALUE_TYPES)
40
+ ? `${section}_without_target`
41
+ : section;
42
+ };
43
+
44
+ const getFieldLabel = (field, fields) =>
45
+ _.flow(
46
+ _.find((f) => _.prop("name")(f) == field),
47
+ _.defaultTo({}),
48
+ _.prop("label"),
49
+ )(fields) || field;
35
50
 
36
- const unwrapFieldValue = (fieldValue) =>
37
- _.isObject(fieldValue) && !_.isNull(fieldValue) && _.has("value", fieldValue)
38
- ? fieldValue.value
39
- : fieldValue;
51
+ const isNullOrEmpty = (value) =>
52
+ !_.isNumber(value) &&
53
+ (!value || _.isEmpty(value) || (value.length === 1 && _.isEmpty(value[0])));
40
54
 
41
55
  const transformFieldFormat = (field) =>
42
56
  _.isBoolean(field) ? _.toString(field) : field;
43
57
 
44
- const getContentChanges = (action, fields, fieldsWithoutValue) =>
45
- _.flow(
46
- _.toPairs,
47
- _.map(([field, rawValue]) => ({
48
- field,
49
- action: withoutTargetAction(action, field, fieldsWithoutValue),
50
- value: transformFieldFormat(unwrapFieldValue(rawValue)),
51
- })),
52
- )(fields || {});
53
-
54
- const getImplementationUpdatedEvent = (e) => {
55
- const { payload } = e;
56
- if (!payload) return { ...e, payload: [] };
58
+ const transformRulePayloadValueFormat = (payload, field) => {
59
+ const sectionFields = ["dataset", "populations", "validation", "segments"];
60
+
61
+ if (sectionFields.includes(field)) {
62
+ return isNullOrEmpty(payload[field]) ? null : payload[field];
63
+ }
57
64
 
65
+ switch (field) {
66
+ case "business_concept":
67
+ return payload.business_concept?.name || null;
68
+ default:
69
+ return payload[field];
70
+ }
71
+ };
72
+
73
+ const selectPayloadAction = (baseAction, field, value) => {
74
+ const baseFields = [
75
+ "business_concept",
76
+ "description",
77
+ "df_name",
78
+ "name",
79
+ "goal",
80
+ "minimum",
81
+ "result_type",
82
+ "implementation_key",
83
+ "rule_name",
84
+ "dataset",
85
+ "populations",
86
+ "validation",
87
+ "segments",
88
+ ];
89
+ if (
90
+ _.includes(baseAction)(["changed", "updated"]) &&
91
+ _.includes(field)(baseFields) &&
92
+ isNullOrEmpty(value)
93
+ ) {
94
+ return field + "_removed_without_value";
95
+ } else if (
96
+ _.includes(baseAction)(["changed", "updated"]) &&
97
+ _.includes(field)([...baseFields, "executable", "active"])
98
+ ) {
99
+ return field + "_updated";
100
+ } else if (
101
+ baseAction == "created" &&
102
+ _.includes(field)(baseFields) &&
103
+ isNullOrEmpty(value)
104
+ ) {
105
+ return null;
106
+ } else if (
107
+ baseAction == "created" &&
108
+ _.includes(field)([...baseFields, "executable", "active"])
109
+ ) {
110
+ return field + "_added";
111
+ } else if (baseAction == "created") {
112
+ return "added";
113
+ }
114
+ return "changed";
115
+ };
116
+
117
+ const checkDomainInPayload = (payload, baseAction) => {
58
118
  // eslint-disable-next-line fp/no-let
59
119
  let field_config = [];
120
+ const { new_domain, old_domain, domain_id, event_via } = payload;
60
121
 
61
- const { new_domain } = payload;
62
- const { old_domain } = payload;
63
-
64
- if (new_domain) {
122
+ if (new_domain && !old_domain) {
123
+ const { name: domain_new_name } = new_domain;
124
+ // eslint-disable-next-line fp/no-mutation
125
+ field_config = _.concat({
126
+ action: baseAction,
127
+ action_extra: "domain_added_with_name",
128
+ field: domain_new_name,
129
+ event_via,
130
+ })(field_config);
131
+ } else if (new_domain && old_domain) {
65
132
  const { name: domain_new_name } = new_domain;
66
133
  const { name: domain_old_name } = old_domain;
67
134
  // eslint-disable-next-line fp/no-mutation
68
135
  field_config = _.concat({
69
- action: "domain_updated_with_names",
136
+ action: baseAction,
137
+ action_extra: "domain_updated_with_names",
70
138
  field: domain_old_name,
71
139
  value: domain_new_name,
140
+ event_via,
72
141
  })(field_config);
73
- } else {
142
+ } else if (domain_id && !new_domain) {
74
143
  // eslint-disable-next-line fp/no-mutation
75
144
  field_config = _.concat({
76
- action: "updated",
77
- field: _.path("payload.implementation_key")(e),
145
+ action: baseAction,
146
+ action_extra:
147
+ baseAction == "created"
148
+ ? "domain_added_with_id"
149
+ : "domain_updated_with_id",
150
+ field: domain_id,
151
+ event_via,
78
152
  })(field_config);
79
153
  }
154
+ return field_config;
155
+ };
156
+
157
+ const normalizeUrlArrayValue = (val) => {
158
+ if (_.isArray(val)) {
159
+ if (
160
+ _.every(
161
+ (v) => _.isObject(v) && _.has("url_name")(v) && _.has("url_value")(v),
162
+ )(val)
163
+ ) {
164
+ return _.flow(
165
+ _.map((v) => `[${v.url_name}] (${v.url_value})`),
166
+ _.join(" ; "),
167
+ )(val);
168
+ }
169
+ return `[${val.map((item) => (typeof item === "string" ? item : JSON.stringify(item))).join(", ")}]`;
170
+ }
171
+ return val;
172
+ };
173
+
174
+ const maybeExtractContentValue = _.cond([
175
+ [_.has("value"), _.prop("value")],
176
+ [_.T, _.identity],
177
+ ]);
178
+
179
+ const getTemplateFields = (template) =>
180
+ _.flow(
181
+ _.getOr([], "content"),
182
+ _.map(_.getOr([], "fields")),
183
+ _.flatten,
184
+ )(template);
185
+
186
+ const formatContentValue = (entry, section) => {
187
+ if (entry === "") return "";
188
+ if (!entry) return null;
189
+ if (shouldHideValue(entry, section)) {
190
+ return null;
191
+ }
192
+ return transformFieldFormat(
193
+ normalizeUrlArrayValue(maybeExtractContentValue(entry)),
194
+ );
195
+ };
196
+
197
+ const getContentActionExtra = (section, fieldKey, fieldTypeMap, entry) => {
198
+ const baseExtra = contentActionExtra(section, fieldKey, fieldTypeMap);
199
+ if (
200
+ section === "removed" &&
201
+ entry?.type &&
202
+ FIELDS_WITHOUT_VALUE_TYPES.includes(entry.type)
203
+ ) {
204
+ return "removed_without_target";
205
+ }
206
+ return baseExtra;
207
+ };
208
+
209
+ const buildEventPayload = (
210
+ payload,
211
+ template,
212
+ baseAction,
213
+ baseActionExtra,
214
+ contentSections,
215
+ ) => {
216
+ const { event_via, content, df_content } = payload;
217
+
218
+ const templateFields = getTemplateFields(template);
219
+
220
+ const fieldTypeMap = _.fromPairs(
221
+ _.map((f) => [f.name, f.type])(templateFields),
222
+ );
223
+
224
+ const fieldsToOmit = [
225
+ "updated_by",
226
+ "updated_at",
227
+ "content",
228
+ "df_content",
229
+ "event_via",
230
+ "domain_ids",
231
+ "domain_id",
232
+ "new_domain",
233
+ "old_domain",
234
+ "rule_id",
235
+ ];
236
+
237
+ const content_any = df_content || content;
238
+
239
+ const baseChanges = _.flow(
240
+ _.omit(fieldsToOmit),
241
+ _.keys,
242
+ _.flatMap((field) => {
243
+ const addedFields = ADD_ON_UPDATE_FIELDS.map((f) => f + "_added");
244
+ if (addedFields.includes(field)) {
245
+ const baseField = field.replace(/_added$/, "");
246
+ const value =
247
+ field === "business_concept_added" && _.isObject(payload[field])
248
+ ? _.get("name", payload[field])
249
+ : payload[field];
250
+
251
+ return {
252
+ field: baseField,
253
+ value,
254
+ action: baseAction,
255
+ action_extra: field,
256
+ event_via,
257
+ };
258
+ }
259
+ if (field === "business_concept_removed") {
260
+ const concept = payload[field];
261
+ return {
262
+ field: "business_concept",
263
+ value: concept?.name || null,
264
+ action: baseAction,
265
+ action_extra: "business_concept_removed",
266
+ event_via,
267
+ };
268
+ }
269
+ const value = transformRulePayloadValueFormat(payload, field);
270
+ return {
271
+ field,
272
+ value,
273
+ action: baseAction,
274
+ action_extra: selectPayloadAction(baseAction, field, value),
275
+ event_via,
276
+ };
277
+ }),
278
+ )(payload);
279
+
280
+ const domainChanges = checkDomainInPayload(payload, baseAction);
281
+
282
+ // eslint-disable-next-line fp/no-let
283
+ let field_config = _.concat(domainChanges, baseChanges);
284
+
285
+ if (content_any) {
286
+ if (contentSections?.length) {
287
+ const contentChanges = _.flatMap((section) => {
288
+ const entries = _.getOr({}, section, content_any);
289
+ return _.map((key) => {
290
+ const entry = entries[key];
291
+ return {
292
+ action: baseAction,
293
+ action_extra: getContentActionExtra(
294
+ section,
295
+ key,
296
+ fieldTypeMap,
297
+ entry,
298
+ ),
299
+ field: getFieldLabel(key, templateFields),
300
+ value: formatContentValue(entry, section),
301
+ event_via,
302
+ };
303
+ })(Object.keys(entries));
304
+ })(contentSections);
305
+ // eslint-disable-next-line fp/no-mutation
306
+ field_config = _.concat(field_config, contentChanges);
307
+ } else {
308
+ const contentChanges = _.map((key) => {
309
+ const entry = content_any[key];
310
+ return {
311
+ action: baseAction,
312
+ action_extra: getContentActionExtra(
313
+ baseActionExtra,
314
+ key,
315
+ fieldTypeMap,
316
+ entry,
317
+ ),
318
+ field: getFieldLabel(key, templateFields),
319
+ value: formatContentValue(entry, baseActionExtra),
320
+ event_via,
321
+ };
322
+ })(Object.keys(content_any));
323
+ // eslint-disable-next-line fp/no-mutation
324
+ field_config = _.concat(field_config, contentChanges);
325
+ }
326
+ }
327
+
328
+ return field_config;
329
+ };
330
+
331
+ const getImplementationUpdatedEvent = ({ payload, ...e }, template) => {
332
+ if (!payload) return { ...e, payload: [] };
333
+ const field_config = buildEventPayload(
334
+ payload,
335
+ template,
336
+ "updated",
337
+ "updated",
338
+ ["changed", "added", "removed"],
339
+ );
340
+ return { ...e, payload: field_config };
341
+ };
342
+
343
+ const getImplementationChangedEvent = ({ payload, ...e }, template) => {
344
+ if (!payload) return { ...e, payload: [] };
345
+ const field_config = buildEventPayload(
346
+ payload,
347
+ template,
348
+ "changed",
349
+ "changed",
350
+ ["changed", "added", "removed"],
351
+ );
352
+ return { ...e, payload: field_config };
353
+ };
354
+
355
+ const getImplementationCreatedEvent = ({ payload, ...e }, template) => {
356
+ if (!payload) return { ...e, payload: [] };
357
+ const field_config = buildEventPayload(
358
+ payload,
359
+ template,
360
+ "created",
361
+ "added",
362
+ null,
363
+ );
364
+ return { ...e, payload: field_config };
365
+ };
366
+
367
+ const getRuleUpdatedEvent = ({ payload, ...e }, template) => {
368
+ if (!payload) return { ...e, payload: [] };
369
+ const field_config = buildEventPayload(
370
+ payload,
371
+ template,
372
+ "changed",
373
+ "changed",
374
+ ["changed", "added", "removed"],
375
+ );
376
+ return { ...e, payload: field_config };
377
+ };
378
+
379
+ const getRuleCreatedEvent = ({ payload, ...e }, template) => {
380
+ if (!payload) return { ...e, payload: [] };
381
+ const field_config = buildEventPayload(
382
+ payload,
383
+ template,
384
+ "created",
385
+ "added",
386
+ null,
387
+ );
80
388
  return { ...e, payload: field_config };
81
389
  };
82
390
 
83
391
  const getParsedEvents = createSelector(
84
392
  [getEvents, getRuleTemplate, getImplementationTemplate],
85
393
  (events, ruleTemplate, implementationTemplate) => {
86
- const ruleFieldsWithoutValue = getFieldsWithoutValue(ruleTemplate);
87
- const implementationFieldsWithoutValue = getFieldsWithoutValue(
88
- implementationTemplate,
89
- );
90
-
91
394
  return _.map((d) => {
395
+ const event_via = _.path("payload.event_via")(d);
396
+ const isImplementationEvent = d.event.startsWith("implementation_");
397
+ const template = isImplementationEvent
398
+ ? implementationTemplate
399
+ : ruleTemplate;
400
+
92
401
  switch (d.event) {
93
402
  case "implementation_created":
94
- return {
95
- ...d,
96
- payload: [
97
- {
98
- field: _.path("payload.rule_name")(d),
99
- action: "created",
100
- },
101
- ],
102
- };
403
+ return getImplementationCreatedEvent(d, template);
404
+
103
405
  case "implementation_moved":
104
406
  return {
105
407
  ...d,
@@ -107,9 +409,12 @@ const getParsedEvents = createSelector(
107
409
  {
108
410
  field: _.path("payload.rule_name")(d),
109
411
  action: "moved",
412
+ action_extra: "moved",
413
+ event_via,
110
414
  },
111
415
  ],
112
416
  };
417
+
113
418
  case "implementation_restored":
114
419
  return {
115
420
  ...d,
@@ -117,104 +422,103 @@ const getParsedEvents = createSelector(
117
422
  {
118
423
  field: _.path("payload.implementation_key")(d),
119
424
  action: "restored",
425
+ event_via,
120
426
  },
121
427
  ],
122
428
  };
429
+
123
430
  case "implementation_deleted":
124
- return {};
125
- case "implementation_deprecated":
126
431
  return {
127
432
  ...d,
128
433
  payload: [
129
434
  {
130
435
  field: _.path("payload.implementation_key")(d),
131
- action: "deprecated",
436
+ action: "deleted",
437
+ event_via,
132
438
  },
133
439
  ],
134
440
  };
135
- case "implementation_changed": {
136
- const dfContent = _.getOr({}, "payload.df_content")(d);
441
+
442
+ case "implementation_changed":
443
+ return getImplementationChangedEvent(d, template);
444
+
445
+ case "implementation_status_updated": {
446
+ const new_status = _.path("payload.status")(d);
447
+ const version = _.path("payload.version")(d);
137
448
  return {
138
449
  ...d,
139
450
  payload: [
140
- ...getContentChanges(
141
- "changed",
142
- dfContent.changed,
143
- implementationFieldsWithoutValue,
144
- ),
145
- ...getContentChanges(
146
- "added",
147
- dfContent.added,
148
- implementationFieldsWithoutValue,
149
- ),
150
- ...getContentChanges(
151
- "removed",
152
- dfContent.removed,
153
- implementationFieldsWithoutValue,
154
- ),
451
+ {
452
+ value: version,
453
+ action: new_status,
454
+ event_via,
455
+ ...(version &&
456
+ new_status === "draft" && {
457
+ action_extra: "created_version",
458
+ }),
459
+ },
155
460
  ],
156
461
  };
157
462
  }
158
- case "implementation_status_updated":
463
+
464
+ case "implementation_updated":
465
+ return getImplementationUpdatedEvent(d, template);
466
+
467
+ case "implementation_structure_created":
468
+ case "implementation_structure_deleted": {
469
+ const suffix =
470
+ d.event === "implementation_structure_created"
471
+ ? "created"
472
+ : "deleted";
159
473
  return {
160
474
  ...d,
161
475
  payload: [
162
476
  {
163
- field: _.path("payload.implementation_key")(d),
164
- action: _.path("payload.status")(d),
477
+ field: d.payload.type,
478
+ value: d.payload.data_structure_name,
479
+ action: `relation_${suffix}`,
480
+ action_extra: `relation_${suffix}_data_structure`,
481
+ event_via,
165
482
  },
166
483
  ],
167
484
  };
485
+ }
168
486
 
169
- case "implementation_updated":
170
- return getImplementationUpdatedEvent(d);
171
-
172
- case "rule_created":
487
+ case "relation_created":
488
+ case "relation_deprecated": {
489
+ const suffix =
490
+ d.event === "relation_created" ? "created" : "deprecated";
173
491
  return {
174
492
  ...d,
175
493
  payload: [
176
494
  {
177
- field: _.path("payload.name")(d),
178
- action: "created",
495
+ field: d.payload.target_type,
496
+ value: d.payload.target_name,
497
+ action: `relation_${suffix}`,
498
+ action_extra: `relation_${suffix}_${d.payload.target_type}`,
499
+ event_via,
179
500
  },
180
501
  ],
181
502
  };
182
- case "rule_updated": {
183
- const content = _.getOr({}, "payload.content")(d);
503
+ }
504
+
505
+ case "rule_created":
506
+ return getRuleCreatedEvent(d, template);
507
+
508
+ case "rule_updated":
509
+ return getRuleUpdatedEvent(d, template);
510
+
511
+ case "rule_deprecated":
184
512
  return {
185
513
  ...d,
186
514
  payload: [
187
- ..._.flow(
188
- _.path("payload"),
189
- _.omit(["updated_by", "content"]),
190
- _.keys,
191
- _.map((field) => ({
192
- field,
193
- action:
194
- field === "description"
195
- ? "changed_without_target"
196
- : "changed",
197
- value: d.payload[field],
198
- })),
199
- )(d),
200
- ...getContentChanges(
201
- "changed",
202
- content.changed,
203
- ruleFieldsWithoutValue,
204
- ),
205
- ...getContentChanges(
206
- "added",
207
- content.added,
208
- ruleFieldsWithoutValue,
209
- ),
210
- ...getContentChanges(
211
- "removed",
212
- content.removed,
213
- ruleFieldsWithoutValue,
214
- ),
515
+ {
516
+ field: _.path("payload.resource_id")(d),
517
+ action: "deprecated",
518
+ event_via,
519
+ },
215
520
  ],
216
521
  };
217
- }
218
522
 
219
523
  default:
220
524
  return {
@@ -223,6 +527,7 @@ const getParsedEvents = createSelector(
223
527
  {
224
528
  field: _.path("payload.implementation_key")(d),
225
529
  action: "updated",
530
+ event_via,
226
531
  },
227
532
  ],
228
533
  };
@@ -0,0 +1,7 @@
1
+ .audit-event-via {
2
+ color: #1e70bf;
3
+ display: inline-block;
4
+ font-weight: 700;
5
+ margin-right: 0em;
6
+ vertical-align: baseline;
7
+ }