@truedat/dq 8.5.3 → 8.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "8.5.3",
3
+ "version": "8.5.6",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -56,7 +56,7 @@
56
56
  "@testing-library/jest-dom": "^6.6.3",
57
57
  "@testing-library/react": "^16.3.0",
58
58
  "@testing-library/user-event": "^14.6.1",
59
- "@truedat/test": "8.5.3",
59
+ "@truedat/test": "8.5.6",
60
60
  "identity-obj-proxy": "^3.0.0",
61
61
  "jest": "^29.7.0",
62
62
  "redux-saga-test-plan": "^4.0.6"
@@ -89,5 +89,5 @@
89
89
  "semantic-ui-react": "^3.0.0-beta.2",
90
90
  "swr": "^2.3.3"
91
91
  },
92
- "gitHead": "9d67766fe0910fe519b6f1f2042b4a5dcc28fad5"
92
+ "gitHead": "41e5e6138f5622558bae4151e720c040c4581162"
93
93
  }
@@ -24,7 +24,11 @@ export const RuleEventRow = ({ user, user_name, ts, payload }) => {
24
24
  content={formatMessage(
25
25
  {
26
26
  id: `rules.events.action_${action}${
27
- typeof value === "undefined" || _.isObject(value) ? "" : "_to"
27
+ action === "changed" &&
28
+ typeof value !== "undefined" &&
29
+ !_.isObject(value)
30
+ ? "_to"
31
+ : ""
28
32
  }`,
29
33
  },
30
34
  [field, JSON.stringify(value)]
@@ -100,7 +100,9 @@ export const RuleForm = ({
100
100
  if (ruleData) {
101
101
  setRule({
102
102
  ...ruleData,
103
- description: ruleData.description || "",
103
+ description: _.isString(ruleData?.description)
104
+ ? ruleData.description
105
+ : "",
104
106
  concept: {
105
107
  name: ruleData?.current_business_concept_version?.name,
106
108
  business_concept_id: ruleData.business_concept_id,
@@ -4,7 +4,7 @@ import { EditRule } from "../EditRule";
4
4
 
5
5
  jest.mock("@truedat/bg/concepts/hooks/useConcepts", () => {
6
6
  const originalModule = jest.requireActual(
7
- "@truedat/bg/concepts/hooks/useConcepts"
7
+ "@truedat/bg/concepts/hooks/useConcepts",
8
8
  );
9
9
 
10
10
  return {
@@ -6,7 +6,8 @@ const rule = {
6
6
  business_concept_id: "2D2B3",
7
7
  domain_id: 1,
8
8
  domain: { id: 1, name: "foo", external_id: "bar" },
9
- description: "desc",
9
+ description:
10
+ "# Description\n\n - this is a list\n\n_with bold_ and +italic+ text. ",
10
11
  rule_type: { name: "type1" },
11
12
  };
12
13
 
@@ -23,8 +23,27 @@ exports[`<RuleProperties /> matches the latest snapshot 1`] = `
23
23
  <div
24
24
  class="markdown-reader"
25
25
  >
26
+ <h1>
27
+ Description
28
+ </h1>
29
+
30
+
31
+ <ul>
32
+
33
+
34
+ <li>
35
+ this is a list
36
+ </li>
37
+
38
+
39
+ </ul>
40
+
41
+
26
42
  <p>
27
- desc
43
+ <em>
44
+ with bold
45
+ </em>
46
+ and +italic+ text.
28
47
  </p>
29
48
 
30
49
 
@@ -165,8 +165,8 @@ describe("selectors: getParsedEvents", () => {
165
165
  payload: [
166
166
  {
167
167
  field: "description",
168
- action: "changed",
169
- value: "Añadimos descripción a la regla",
168
+ action: "changed_without_target",
169
+ value: events[2].payload.description,
170
170
  },
171
171
  ],
172
172
  },
@@ -178,6 +178,8 @@ describe("selectors: getParsedEvents", () => {
178
178
  { action: "changed", field: "principle", value: "Validez" },
179
179
  { action: "changed", field: "scheduler", value: "" },
180
180
  { action: "changed", field: "tags", value: "et" },
181
+ { action: "removed", field: "data_owner", value: null },
182
+ { action: "removed", field: "proyecto", value: ["P1"] },
181
183
  ],
182
184
  },
183
185
  {
@@ -201,4 +203,133 @@ describe("selectors: getParsedEvents", () => {
201
203
  expect(res).toHaveLength(0);
202
204
  expect(res).toEqual(expect.arrayContaining([]));
203
205
  });
206
+
207
+ it("appends _without_target to changed action for fields whose template type is url/table/system/image/markdown", () => {
208
+ const ruleTemplate = {
209
+ name: "quality_template",
210
+ content: [
211
+ {
212
+ fields: [
213
+ { name: "description_md", type: "markdown" },
214
+ { name: "doc_url", type: "url" },
215
+ { name: "principle", type: "string" },
216
+ ],
217
+ },
218
+ ],
219
+ };
220
+
221
+ const implementationTemplate = {
222
+ name: "ri_template",
223
+ content: [
224
+ {
225
+ fields: [
226
+ { name: "notes_md", type: "markdown" },
227
+ { name: "evidence", type: "image" },
228
+ { name: "comment", type: "string" },
229
+ ],
230
+ },
231
+ ],
232
+ };
233
+
234
+ const ruleEvent = {
235
+ id: 1,
236
+ service: "td_dd",
237
+ resource_type: "rule",
238
+ event: "rule_updated",
239
+ payload: {
240
+ content: {
241
+ changed: {
242
+ description_md: "# new doc",
243
+ doc_url: "https://example.test",
244
+ principle: "Validez",
245
+ },
246
+ },
247
+ updated_by: 1,
248
+ },
249
+ };
250
+
251
+ const implementationEvent = {
252
+ id: 2,
253
+ service: "td_dd",
254
+ resource_type: "implementation",
255
+ event: "implementation_changed",
256
+ payload: {
257
+ df_content: {
258
+ changed: {
259
+ notes_md: "## hi",
260
+ evidence: { url: "x" },
261
+ comment: "ok",
262
+ },
263
+ },
264
+ },
265
+ };
266
+
267
+ const state = {
268
+ events: [ruleEvent, implementationEvent],
269
+ rule: { df_name: "quality_template" },
270
+ templates: [ruleTemplate],
271
+ ruleImplementation: { df_name: "ri_template" },
272
+ allTemplates: [implementationTemplate],
273
+ };
274
+
275
+ const [ruleParsed, implParsed] = getParsedEvents(state);
276
+
277
+ expect(ruleParsed.payload).toEqual([
278
+ {
279
+ field: "description_md",
280
+ action: "changed_without_target",
281
+ value: "# new doc",
282
+ },
283
+ {
284
+ field: "doc_url",
285
+ action: "changed_without_target",
286
+ value: "https://example.test",
287
+ },
288
+ { field: "principle", action: "changed", value: "Validez" },
289
+ ]);
290
+
291
+ expect(implParsed.payload).toEqual([
292
+ {
293
+ field: "notes_md",
294
+ action: "changed_without_target",
295
+ value: "## hi",
296
+ },
297
+ {
298
+ field: "evidence",
299
+ action: "changed_without_target",
300
+ value: { url: "x" },
301
+ },
302
+ { field: "comment", action: "changed", value: "ok" },
303
+ ]);
304
+ });
305
+
306
+ it("emits added/changed/removed entries from df_content and unwraps {value, origin} fields", () => {
307
+ const event = {
308
+ id: 274884,
309
+ service: "td_dd",
310
+ resource_type: "implementation",
311
+ event: "implementation_changed",
312
+ payload: {
313
+ df_content: {
314
+ added: {
315
+ User: { origin: "user", value: "Inés Owner" },
316
+ },
317
+ changed: {
318
+ comment: { origin: "user", value: "updated" },
319
+ },
320
+ removed: {
321
+ tag: { origin: "default", value: "stale" },
322
+ },
323
+ },
324
+ },
325
+ };
326
+
327
+ const [parsed] = getParsedEvents({ events: [event] });
328
+
329
+ expect(parsed.payload).toEqual([
330
+ { field: "comment", action: "changed", value: "updated" },
331
+ { field: "User", action: "added", value: "Inés Owner" },
332
+ { field: "tag", action: "removed", value: "stale" },
333
+ ]);
334
+ });
204
335
  });
@@ -1,35 +1,61 @@
1
1
  import _ from "lodash/fp";
2
2
  import { createSelector } from "reselect";
3
3
 
4
- const getEvents = ({ events }) => {
5
- return events;
6
- };
4
+ const FIELDS_WITHOUT_VALUE_TYPES = [
5
+ "url",
6
+ "table",
7
+ "system",
8
+ "image",
9
+ "markdown",
10
+ ];
11
+
12
+ const getEvents = ({ events }) => events;
13
+
14
+ const getRuleTemplate = (state) =>
15
+ _.find(_.propEq("name", _.getOr(null, "rule.df_name", state)))(
16
+ _.getOr([], "templates", state),
17
+ );
18
+
19
+ const getImplementationTemplate = (state) =>
20
+ _.find(_.propEq("name", _.getOr(null, "ruleImplementation.df_name", state)))(
21
+ _.getOr([], "allTemplates", state),
22
+ );
23
+
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);
32
+
33
+ const withoutTargetAction = (action, field, fieldsWithoutValue) =>
34
+ _.includes(field)(fieldsWithoutValue) ? `${action}_without_target` : action;
35
+
36
+ const unwrapFieldValue = (fieldValue) =>
37
+ _.isObject(fieldValue) && !_.isNull(fieldValue) && _.has("value", fieldValue)
38
+ ? fieldValue.value
39
+ : fieldValue;
7
40
 
8
41
  const transformFieldFormat = (field) =>
9
42
  _.isBoolean(field) ? _.toString(field) : field;
10
43
 
11
- const findDeep = (obj, key) =>
12
- _.has(key, obj)
13
- ? obj[key]
14
- : _.flatten(
15
- _.map((v) => (typeof v == "object" ? findDeep(v, key) : []), obj)
16
- );
17
-
18
- const transformRulePayloadFormat = (payload, field) => {
19
- switch (field) {
20
- case "content":
21
- return payload.content;
22
- case "description":
23
- return _.first(findDeep(payload.description, "text"));
24
- default:
25
- return payload[field];
26
- }
27
- };
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 || {});
28
53
 
29
54
  const getImplementationUpdatedEvent = (e) => {
30
55
  const { payload } = e;
31
56
  if (!payload) return { ...e, payload: [] };
32
57
 
58
+ // eslint-disable-next-line fp/no-let
33
59
  let field_config = [];
34
60
 
35
61
  const { new_domain } = payload;
@@ -45,6 +71,7 @@ const getImplementationUpdatedEvent = (e) => {
45
71
  value: domain_new_name,
46
72
  })(field_config);
47
73
  } else {
74
+ // eslint-disable-next-line fp/no-mutation
48
75
  field_config = _.concat({
49
76
  action: "updated",
50
77
  field: _.path("payload.implementation_key")(e),
@@ -53,129 +80,155 @@ const getImplementationUpdatedEvent = (e) => {
53
80
  return { ...e, payload: field_config };
54
81
  };
55
82
 
56
- const getParsedEvents = createSelector([getEvents], (events) => {
57
- return _.map((d) => {
58
- switch (d.event) {
59
- case "implementation_created":
60
- return {
61
- ...d,
62
- payload: [
63
- {
64
- field: _.path("payload.rule_name")(d),
65
- action: "created",
66
- },
67
- ],
68
- };
69
- case "implementation_moved":
70
- return {
71
- ...d,
72
- payload: [
73
- {
74
- field: _.path("payload.rule_name")(d),
75
- action: "moved",
76
- },
77
- ],
78
- };
79
- case "implementation_restored":
80
- return {
81
- ...d,
82
- payload: [
83
- {
84
- field: _.path("payload.implementation_key")(d),
85
- action: "restored",
86
- },
87
- ],
88
- };
89
- case "implementation_deleted":
90
- return {};
91
- case "implementation_deprecated":
92
- return {
93
- ...d,
94
- payload: [
95
- {
96
- field: _.path("payload.implementation_key")(d),
97
- action: "deprecated",
98
- },
99
- ],
100
- };
101
- case "implementation_changed":
102
- return {
103
- ...d,
104
- payload: _.flow(
105
- _.path("payload.df_content.changed"),
106
- _.keys,
107
- _.map((e) => ({
108
- field: e,
109
- action: "changed",
110
- value: transformFieldFormat(
111
- _.path(`payload.df_content.changed.${e}`)(d)
83
+ const getParsedEvents = createSelector(
84
+ [getEvents, getRuleTemplate, getImplementationTemplate],
85
+ (events, ruleTemplate, implementationTemplate) => {
86
+ const ruleFieldsWithoutValue = getFieldsWithoutValue(ruleTemplate);
87
+ const implementationFieldsWithoutValue = getFieldsWithoutValue(
88
+ implementationTemplate,
89
+ );
90
+
91
+ return _.map((d) => {
92
+ switch (d.event) {
93
+ case "implementation_created":
94
+ return {
95
+ ...d,
96
+ payload: [
97
+ {
98
+ field: _.path("payload.rule_name")(d),
99
+ action: "created",
100
+ },
101
+ ],
102
+ };
103
+ case "implementation_moved":
104
+ return {
105
+ ...d,
106
+ payload: [
107
+ {
108
+ field: _.path("payload.rule_name")(d),
109
+ action: "moved",
110
+ },
111
+ ],
112
+ };
113
+ case "implementation_restored":
114
+ return {
115
+ ...d,
116
+ payload: [
117
+ {
118
+ field: _.path("payload.implementation_key")(d),
119
+ action: "restored",
120
+ },
121
+ ],
122
+ };
123
+ case "implementation_deleted":
124
+ return {};
125
+ case "implementation_deprecated":
126
+ return {
127
+ ...d,
128
+ payload: [
129
+ {
130
+ field: _.path("payload.implementation_key")(d),
131
+ action: "deprecated",
132
+ },
133
+ ],
134
+ };
135
+ case "implementation_changed": {
136
+ const dfContent = _.getOr({}, "payload.df_content")(d);
137
+ return {
138
+ ...d,
139
+ 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,
112
154
  ),
113
- }))
114
- )(d),
115
- };
116
- case "implementation_status_updated":
117
- return {
118
- ...d,
119
- payload: [
120
- {
121
- field: _.path("payload.implementation_key")(d),
122
- action: _.path("payload.status")(d),
123
- },
124
- ],
125
- };
126
-
127
- case "implementation_updated":
128
- return getImplementationUpdatedEvent(d);
129
-
130
- case "rule_created":
131
- return {
132
- ...d,
133
- payload: [
134
- {
135
- field: _.path("payload.name")(d),
136
- action: "created",
137
- },
138
- ],
139
- };
140
- case "rule_updated":
141
- const content = _.flow(
142
- _.getOr({}, "payload.content.changed"),
143
- _.toPairs,
144
- _.map(([field, value]) => ({
145
- field,
146
- value,
147
- action: "changed",
148
- }))
149
- )(d);
150
- return {
151
- ...d,
152
- payload: [
153
- ..._.flow(
154
- _.path("payload"),
155
- _.omit(["updated_by", "content"]),
156
- _.keys,
157
- _.map((field) => ({
158
- field: field,
159
- action: "changed",
160
- value: transformRulePayloadFormat(d.payload, field),
161
- }))
162
- )(d),
163
- ...content,
164
- ],
165
- };
166
-
167
- default:
168
- return {
169
- ...d,
170
- payload: [
171
- {
172
- field: _.path("payload.implementation_key")(d),
173
- action: "updated",
174
- },
175
- ],
176
- };
177
- }
178
- })(events);
179
- });
155
+ ],
156
+ };
157
+ }
158
+ case "implementation_status_updated":
159
+ return {
160
+ ...d,
161
+ payload: [
162
+ {
163
+ field: _.path("payload.implementation_key")(d),
164
+ action: _.path("payload.status")(d),
165
+ },
166
+ ],
167
+ };
168
+
169
+ case "implementation_updated":
170
+ return getImplementationUpdatedEvent(d);
171
+
172
+ case "rule_created":
173
+ return {
174
+ ...d,
175
+ payload: [
176
+ {
177
+ field: _.path("payload.name")(d),
178
+ action: "created",
179
+ },
180
+ ],
181
+ };
182
+ case "rule_updated": {
183
+ const content = _.getOr({}, "payload.content")(d);
184
+ return {
185
+ ...d,
186
+ 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
+ ),
215
+ ],
216
+ };
217
+ }
218
+
219
+ default:
220
+ return {
221
+ ...d,
222
+ payload: [
223
+ {
224
+ field: _.path("payload.implementation_key")(d),
225
+ action: "updated",
226
+ },
227
+ ],
228
+ };
229
+ }
230
+ })(events);
231
+ },
232
+ );
180
233
 
181
234
  export { getParsedEvents };