@truedat/qx 7.14.5 → 8.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/qx",
3
- "version": "7.14.5",
3
+ "version": "8.0.0",
4
4
  "description": "Truedat Web Quality Experience package",
5
5
  "sideEffects": false,
6
6
  "module": "src/index.js",
@@ -53,7 +53,7 @@
53
53
  "@testing-library/jest-dom": "^6.6.3",
54
54
  "@testing-library/react": "^16.3.0",
55
55
  "@testing-library/user-event": "^14.6.1",
56
- "@truedat/test": "7.14.5",
56
+ "@truedat/test": "8.0.0",
57
57
  "identity-obj-proxy": "^3.0.0",
58
58
  "jest": "^29.7.0",
59
59
  "redux-saga-test-plan": "^4.0.6"
@@ -86,5 +86,5 @@
86
86
  "semantic-ui-react": "^3.0.0-beta.2",
87
87
  "swr": "^2.3.3"
88
88
  },
89
- "gitHead": "c27ba60d1a158d6c21105f73d6ac4a20d83e5630"
89
+ "gitHead": "5a3af0f7a7833b2e02a876a08e228a5d678927ea"
90
90
  }
@@ -1,6 +1,50 @@
1
1
  import { render, waitForLoad } from "@truedat/test/render";
2
2
  import DataViewSummary from "../DataViewSummary";
3
3
 
4
+ jest.mock("@truedat/qx/hooks/useFunctions", () => {
5
+ const originalModule = jest.requireActual("@truedat/qx/hooks/useFunctions");
6
+
7
+ return {
8
+ __esModule: true,
9
+ ...originalModule,
10
+ useFunctions: jest.fn(() => ({
11
+ data: {
12
+ data: [
13
+ {
14
+ name: "eq",
15
+ type: "boolean",
16
+ params: [
17
+ { name: "arg1", type: "any" },
18
+ { name: "arg2", type: "any" },
19
+ ],
20
+ },
21
+ {
22
+ name: "gt",
23
+ type: "boolean",
24
+ params: [
25
+ { name: "arg1", type: "any" },
26
+ { name: "arg2", type: "any" },
27
+ ],
28
+ },
29
+ {
30
+ name: "sum",
31
+ type: "number",
32
+ params: [{ name: "arg1", type: "any" }],
33
+ },
34
+ {
35
+ name: "count",
36
+ type: "number",
37
+ params: [],
38
+ },
39
+ ],
40
+ },
41
+ loading: false,
42
+ error: null,
43
+ mutate: jest.fn(),
44
+ })),
45
+ };
46
+ });
47
+
4
48
  const messages = {
5
49
  "dataViews.form.queryable.from": "FROM",
6
50
  "dataViews.form.queryable.join": "JOIN",
@@ -304,40 +304,23 @@ exports[`<DataViewSummary /> matches snapshot for FROM with JOIN 1`] = `
304
304
  class="ui segment no-margin text-break-word"
305
305
  >
306
306
  <div
307
- class="text-align-left"
307
+ class="condition-function-viewer"
308
308
  >
309
309
  <div
310
- class="ui tiny horizontal label text-break-word"
311
- title="eq"
310
+ class="ui blue label text-break-word"
311
+ title="users.user_id"
312
312
  >
313
- eq
313
+ users.user_id
314
314
  </div>
315
- (
315
+ <b
316
+ class="font-big"
317
+ />
316
318
  <div
317
- class="clause-viewer-function"
319
+ class="ui orange label text-break-word"
320
+ title="orders.user_id"
318
321
  >
319
- <div
320
- class="display-flex"
321
- >
322
- <div
323
- class="ui blue label text-break-word"
324
- title="users.user_id"
325
- >
326
- users.user_id
327
- </div>
328
- </div>
329
- <div
330
- class="display-flex"
331
- >
332
- <div
333
- class="ui orange label text-break-word"
334
- title="orders.user_id"
335
- >
336
- orders.user_id
337
- </div>
338
- </div>
322
+ orders.user_id
339
323
  </div>
340
- )
341
324
  </div>
342
325
  </div>
343
326
  </div>
@@ -427,47 +410,7 @@ exports[`<DataViewSummary /> matches snapshot for FROM with WHERE clause 1`] = `
427
410
  >
428
411
  <div
429
412
  class="ui basic compact segment text-break-word"
430
- >
431
- <div
432
- class="ui segment no-margin text-break-word"
433
- >
434
- <div
435
- class="text-align-left"
436
- >
437
- <div
438
- class="ui tiny horizontal label text-break-word"
439
- title="gt"
440
- >
441
- gt
442
- </div>
443
- (
444
- <div
445
- class="clause-viewer-function"
446
- >
447
- <div
448
- class="display-flex"
449
- >
450
- <div
451
- class="ui blue label text-break-word"
452
- title="users.age"
453
- >
454
- users.age
455
- </div>
456
- </div>
457
- <div
458
- class="display-flex"
459
- >
460
- <div
461
- class="ui basic label text-break-word"
462
- >
463
- 18
464
- </div>
465
- </div>
466
- </div>
467
- )
468
- </div>
469
- </div>
470
- </div>
413
+ />
471
414
  </div>
472
415
  </div>
473
416
  </div>
@@ -603,40 +546,23 @@ exports[`<DataViewSummary /> matches snapshot for complete data view with all qu
603
546
  class="ui segment no-margin text-break-word"
604
547
  >
605
548
  <div
606
- class="text-align-left"
549
+ class="condition-function-viewer"
607
550
  >
608
551
  <div
609
- class="ui tiny horizontal label text-break-word"
610
- title="eq"
552
+ class="ui blue label text-break-word"
553
+ title="users.user_id"
611
554
  >
612
- eq
555
+ users.user_id
613
556
  </div>
614
- (
557
+ <b
558
+ class="font-big"
559
+ />
615
560
  <div
616
- class="clause-viewer-function"
561
+ class="ui orange label text-break-word"
562
+ title="orders.user_id"
617
563
  >
618
- <div
619
- class="display-flex"
620
- >
621
- <div
622
- class="ui blue label text-break-word"
623
- title="users.user_id"
624
- >
625
- users.user_id
626
- </div>
627
- </div>
628
- <div
629
- class="display-flex"
630
- >
631
- <div
632
- class="ui orange label text-break-word"
633
- title="orders.user_id"
634
- >
635
- orders.user_id
636
- </div>
637
- </div>
564
+ orders.user_id
638
565
  </div>
639
- )
640
566
  </div>
641
567
  </div>
642
568
  </div>
@@ -669,47 +595,7 @@ exports[`<DataViewSummary /> matches snapshot for complete data view with all qu
669
595
  >
670
596
  <div
671
597
  class="ui basic compact segment text-break-word"
672
- >
673
- <div
674
- class="ui segment no-margin text-break-word"
675
- >
676
- <div
677
- class="text-align-left"
678
- >
679
- <div
680
- class="ui tiny horizontal label text-break-word"
681
- title="gt"
682
- >
683
- gt
684
- </div>
685
- (
686
- <div
687
- class="clause-viewer-function"
688
- >
689
- <div
690
- class="display-flex"
691
- >
692
- <div
693
- class="ui orange label text-break-word"
694
- title="orders.total"
695
- >
696
- orders.total
697
- </div>
698
- </div>
699
- <div
700
- class="display-flex"
701
- >
702
- <div
703
- class="ui basic label text-break-word"
704
- >
705
- 100
706
- </div>
707
- </div>
708
- </div>
709
- )
710
- </div>
711
- </div>
712
- </div>
598
+ />
713
599
  </div>
714
600
  </div>
715
601
  </div>
@@ -1304,47 +1190,7 @@ exports[`<DataViewSummary /> matches snapshot with HAVING clause (where after gr
1304
1190
  >
1305
1191
  <div
1306
1192
  class="ui basic compact segment text-break-word"
1307
- >
1308
- <div
1309
- class="ui segment no-margin text-break-word"
1310
- >
1311
- <div
1312
- class="text-align-left"
1313
- >
1314
- <div
1315
- class="ui tiny horizontal label text-break-word"
1316
- title="gt"
1317
- >
1318
- gt
1319
- </div>
1320
- (
1321
- <div
1322
- class="clause-viewer-function"
1323
- >
1324
- <div
1325
- class="display-flex"
1326
- >
1327
- <div
1328
- class="ui orange label text-break-word"
1329
- title="Group By.total"
1330
- >
1331
- Group By.total
1332
- </div>
1333
- </div>
1334
- <div
1335
- class="display-flex"
1336
- >
1337
- <div
1338
- class="ui basic label text-break-word"
1339
- >
1340
- 1000
1341
- </div>
1342
- </div>
1343
- </div>
1344
- )
1345
- </div>
1346
- </div>
1347
- </div>
1193
+ />
1348
1194
  </div>
1349
1195
  </div>
1350
1196
  </div>
@@ -0,0 +1,74 @@
1
+ import { Segment, Feed } from "semantic-ui-react";
2
+ import { useEvents } from "@truedat/audit/hooks/useEvents";
3
+ import PropTypes from "prop-types";
4
+ import { use, useMemo } from "react";
5
+ import QxContext from "../QxContext";
6
+ import { getParsedEvents } from "./events/getParsedEvents";
7
+ import { useIntl } from "react-intl";
8
+ import Moment from "react-moment";
9
+
10
+ const MESSAGE_ID = 0;
11
+ const MESSAGE_PARAMS = 1;
12
+
13
+ const EventRow = ({ user, ts, payload }) => {
14
+ const { formatMessage, locale } = useIntl();
15
+ const translateParams = (params) => {
16
+ if (!params || typeof params !== "object") return params;
17
+ if (params.value && typeof params.value === "object" && params.value.id) {
18
+ return { ...params, value: formatMessage(params.value) };
19
+ }
20
+ return params;
21
+ };
22
+ return (
23
+ <Feed.Event>
24
+ <Feed.Content>
25
+ <Feed.Summary>
26
+ <Feed.User>{user?.user_name}</Feed.User>{" "}
27
+ <Feed.Date>
28
+ <Moment locale={locale} date={ts} format="YYYY-MM-DD HH:mm" />
29
+ </Feed.Date>
30
+ </Feed.Summary>
31
+ {payload.map((content, i) => (
32
+ <Feed.Extra
33
+ key={i}
34
+ text
35
+ content={formatMessage(
36
+ content[MESSAGE_ID],
37
+ translateParams(content[MESSAGE_PARAMS])
38
+ )}
39
+ />
40
+ ))}
41
+ </Feed.Content>
42
+ </Feed.Event>
43
+ );
44
+ };
45
+
46
+ EventRow.propTypes = {
47
+ user: PropTypes.object.isRequired,
48
+ ts: PropTypes.string.isRequired,
49
+ payload: PropTypes.array.isRequired,
50
+ };
51
+
52
+ export default function QualityControlEvents() {
53
+ const { qualityControl, loading: qualityControlLoading } = use(QxContext);
54
+ const { data, error, loading } = useEvents(
55
+ qualityControl.id,
56
+ "quality_control"
57
+ );
58
+ const events = data?.data || [];
59
+ const parsedEvents = useMemo(() => getParsedEvents(events), [events]);
60
+
61
+ return (
62
+ <Segment
63
+ attached="bottom"
64
+ loading={loading || qualityControlLoading}
65
+ error={error}
66
+ >
67
+ <Feed size="small">
68
+ {parsedEvents.map((event, index) => (
69
+ <EventRow key={index} {...event} />
70
+ ))}
71
+ </Feed>
72
+ </Segment>
73
+ );
74
+ }
@@ -26,6 +26,7 @@ import NewQualityControl from "./NewQualityControl";
26
26
  import EditQualityControl from "./EditQualityControl";
27
27
  import NewDraftQualityControl from "./NewDraftQualityControl";
28
28
  import { ScoreContextProvider } from "../scores/ScoreContext";
29
+ import QualityControlEvents from "./QualityControlEvents";
29
30
 
30
31
  const LoaderQualityControlScores = () => {
31
32
  const { id } = useParams();
@@ -75,7 +76,7 @@ export default function QualityControlRoutes() {
75
76
  element={
76
77
  <SearchContextProvider
77
78
  {...searchProps}
78
- defaultFilters={{ status: "draft" }}
79
+ defaultFilters={{ status: ["draft", "rejected"] }}
79
80
  >
80
81
  <QualityControls />
81
82
  </SearchContextProvider>
@@ -95,6 +96,7 @@ export default function QualityControlRoutes() {
95
96
  <Route index element={<QualityControl />} />
96
97
  <Route path="history" element={<QualityControlHistory />} />
97
98
  <Route path="scores" element={<LoaderQualityControlScores />} />
99
+ <Route path="events" element={<QualityControlEvents />} />
98
100
  </Route>
99
101
  </Routes>
100
102
  );
@@ -21,6 +21,10 @@ export default function QualityControlTabs() {
21
21
  url: linkTo.QUALITY_CONTROL_SCORES(urlParams),
22
22
  text: "quality_control.tabs.scores",
23
23
  },
24
+ {
25
+ url: linkTo.QUALITY_CONTROL_EVENTS(urlParams),
26
+ text: "quality_control.tabs.events",
27
+ },
24
28
  ];
25
29
 
26
30
  return (
@@ -0,0 +1,115 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { messages as baseMessages } from "@truedat/qx/components/__tests__/__fixtures__/helper";
4
+ import { useEvents } from "@truedat/audit/hooks/useEvents";
5
+ import QxContext from "../../QxContext";
6
+ import QualityControlEvents from "../QualityControlEvents";
7
+
8
+ jest.mock("@truedat/audit/hooks/useEvents", () => ({
9
+ useEvents: jest.fn(),
10
+ }));
11
+
12
+ const messages = {
13
+ ...baseMessages,
14
+ en: {
15
+ ...baseMessages.en,
16
+ "quality_controls.events.action_created": "<b>{field}</b> created",
17
+ "quality_controls.events.action_field_changed":
18
+ "<b>{field}</b> changed to {value}",
19
+ "quality_controls.events.action_dynamic_field_changed":
20
+ "<b>{field}</b> updated",
21
+ "quality_controls.events.action_enabled": "Quality control enabled",
22
+ "quality_controls.events.action_disabled": "Quality control disabled",
23
+ "quality_controls.events.action_draft_version_created":
24
+ "Draft version <b>{value}</b> created",
25
+ },
26
+ };
27
+
28
+ const renderOpts = { messages };
29
+
30
+ afterEach(() => {
31
+ jest.clearAllMocks();
32
+ });
33
+
34
+ describe("<QualityControlEvents />", () => {
35
+ it("renders parsed events with highlighted fields", () => {
36
+ useEvents.mockReturnValue({
37
+ data: {
38
+ data: [
39
+ {
40
+ event: "quality_control_created",
41
+ payload: { name: "Sample control" },
42
+ ts: "2024-01-01T00:00:00Z",
43
+ user: { user_name: "Jane Doe" },
44
+ },
45
+ ],
46
+ },
47
+ error: null,
48
+ loading: false,
49
+ });
50
+
51
+ const contextValue = {
52
+ qualityControl: { id: "qc-1" },
53
+ loading: false,
54
+ };
55
+
56
+ const rendered = render(
57
+ <QxContext.Provider value={contextValue}>
58
+ <QualityControlEvents />
59
+ </QxContext.Provider>,
60
+ renderOpts
61
+ );
62
+
63
+ expect(rendered.container).toMatchSnapshot();
64
+ expect(useEvents).toHaveBeenCalledWith("qc-1", "quality_control");
65
+ expect(rendered.getByText(/jane doe/i)).toBeInTheDocument();
66
+
67
+ const boldField = rendered.getByText(/sample control/i);
68
+ expect(boldField.tagName).toBe("B");
69
+ expect(rendered.getByText(/created/i)).toBeInTheDocument();
70
+ });
71
+
72
+ it("shows loading state when events are loading", () => {
73
+ useEvents.mockReturnValue({
74
+ data: null,
75
+ error: null,
76
+ loading: true,
77
+ });
78
+
79
+ const contextValue = {
80
+ qualityControl: { id: "qc-1" },
81
+ loading: false,
82
+ };
83
+
84
+ const rendered = render(
85
+ <QxContext.Provider value={contextValue}>
86
+ <QualityControlEvents />
87
+ </QxContext.Provider>,
88
+ renderOpts
89
+ );
90
+
91
+ expect(rendered.container.querySelector(".segment.loading")).toBeTruthy();
92
+ });
93
+
94
+ it("shows loading state when quality control is loading", () => {
95
+ useEvents.mockReturnValue({
96
+ data: { data: [] },
97
+ error: null,
98
+ loading: false,
99
+ });
100
+
101
+ const contextValue = {
102
+ qualityControl: { id: "qc-1" },
103
+ loading: true,
104
+ };
105
+
106
+ const rendered = render(
107
+ <QxContext.Provider value={contextValue}>
108
+ <QualityControlEvents />
109
+ </QxContext.Provider>,
110
+ renderOpts
111
+ );
112
+
113
+ expect(rendered.container.querySelector(".segment.loading")).toBeTruthy();
114
+ });
115
+ });
@@ -0,0 +1,49 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<QualityControlEvents /> renders parsed events with highlighted fields 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui bottom attached segment"
7
+ >
8
+ <div
9
+ class="ui small feed"
10
+ >
11
+ <div
12
+ class="event"
13
+ >
14
+ <div
15
+ class="content"
16
+ >
17
+ <div
18
+ class="summary"
19
+ >
20
+ <a
21
+ class="user"
22
+ >
23
+ Jane Doe
24
+ </a>
25
+
26
+ <div
27
+ class="date"
28
+ >
29
+ <time
30
+ datetime="1704067200000"
31
+ >
32
+ 2024-01-01 00:00
33
+ </time>
34
+ </div>
35
+ </div>
36
+ <div
37
+ class="text extra"
38
+ >
39
+ <b>
40
+ Sample control
41
+ </b>
42
+ created
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ `;
@@ -180,6 +180,13 @@ exports[`<QualityControlHeader /> matches the latest snapshot with control quali
180
180
  >
181
181
  quality_control.tabs.scores
182
182
  </a>
183
+ <a
184
+ class="item"
185
+ data-discover="true"
186
+ href="/qualityControls/8/version/1/events"
187
+ >
188
+ quality_control.tabs.events
189
+ </a>
183
190
  </div>
184
191
  </div>
185
192
  </div>
@@ -26,6 +26,13 @@ exports[`<QualityControlTabs /> matches the latest snapshot 1`] = `
26
26
  >
27
27
  Scores
28
28
  </a>
29
+ <a
30
+ class="item"
31
+ data-discover="true"
32
+ href="/qualityControls/8/version/1/events"
33
+ >
34
+ quality_control.tabs.events
35
+ </a>
29
36
  </div>
30
37
  </div>
31
38
  `;
@@ -0,0 +1,106 @@
1
+ import { getParsedEvents, bold } from "../getParsedEvents";
2
+ const user = { user_name: "Jane" };
3
+ const timestamp = "2024-01-01T00:00:00Z";
4
+
5
+ describe("getParsedEvents", () => {
6
+ it("returns payload for quality_control_created", () => {
7
+ const events = [
8
+ {
9
+ event: "quality_control_created",
10
+ payload: { name: "Control A" },
11
+ ts: timestamp,
12
+ user,
13
+ },
14
+ ];
15
+
16
+ expect(getParsedEvents(events)).toEqual([
17
+ {
18
+ ts: timestamp,
19
+ user,
20
+ payload: [
21
+ [
22
+ { id: "quality_controls.events.action_created" },
23
+ { field: "Control A", b: bold },
24
+ ],
25
+ ],
26
+ },
27
+ ]);
28
+ });
29
+
30
+ it("combines payload sections for quality_control_updated", () => {
31
+ const events = [
32
+ {
33
+ event: "quality_control_updated",
34
+ ts: timestamp,
35
+ user,
36
+ payload: {
37
+ changes: {
38
+ active: false,
39
+ name: "New name",
40
+ dynamic_content: {
41
+ threshold: { value: 10 },
42
+ },
43
+ },
44
+ },
45
+ },
46
+ ];
47
+ expect(getParsedEvents(events)).toEqual([
48
+ {
49
+ ts: timestamp,
50
+ user,
51
+ payload: [
52
+ [{ id: "quality_controls.events.action_disabled" }, { b: bold }],
53
+ [
54
+ { id: "quality_controls.events.action_field_changed" },
55
+ { field: "name", value: "New name", b: bold },
56
+ ],
57
+ [
58
+ { id: "quality_controls.events.action_dynamic_field_changed" },
59
+ { field: "threshold", value: 10, b: bold },
60
+ ],
61
+ ],
62
+ },
63
+ ]);
64
+ });
65
+
66
+ it("returns payload for version status update", () => {
67
+ const events = [
68
+ {
69
+ event: "quality_control_version_status_updated",
70
+ payload: { action: "approved", version: "v2", status: "published" },
71
+ ts: timestamp,
72
+ user,
73
+ },
74
+ ];
75
+
76
+ expect(getParsedEvents(events)).toEqual([
77
+ {
78
+ ts: timestamp,
79
+ user,
80
+ payload: [
81
+ [
82
+ { id: "quality_controls.events.action_approved" },
83
+ {
84
+ field: "v2",
85
+ value: { id: "quality_control.status.published" },
86
+ b: bold,
87
+ },
88
+ ],
89
+ ],
90
+ },
91
+ ]);
92
+ });
93
+
94
+ it("filters out unsupported events", () => {
95
+ const events = [
96
+ {
97
+ event: "quality_control_deleted",
98
+ ts: timestamp,
99
+ user,
100
+ payload: {},
101
+ },
102
+ ];
103
+
104
+ expect(getParsedEvents(events)).toEqual([]);
105
+ });
106
+ });
@@ -0,0 +1,134 @@
1
+ import _ from "lodash/fp";
2
+
3
+ export const bold = (text) => <b>{text}</b>;
4
+
5
+ const normalizeValue = (value) => {
6
+ if (_.isObject(value)) return JSON.stringify(value);
7
+ if (_.isBoolean(value)) return String(value);
8
+ return value;
9
+ };
10
+
11
+ const getPayloadFromActiveChange = (event) => {
12
+ if (_.has("payload.changes.active")(event)) {
13
+ return _.prop("payload.changes.active")(event)
14
+ ? [[{ id: "quality_controls.events.action_enabled" }, { b: bold }]]
15
+ : [[{ id: "quality_controls.events.action_disabled" }, { b: bold }]];
16
+ }
17
+ return [];
18
+ };
19
+
20
+ const getDomainNames = (path) => (event) =>
21
+ _.flow(_.prop(path), _.defaultTo([]), _.map(_.prop("name")))(event);
22
+
23
+ const getPayloadFromDomainChange = (event) => {
24
+ if (_.has("payload.changes.domains")(event)) {
25
+ const domainNames = _.join(", ")(
26
+ getDomainNames("payload.changes.domains")(event)
27
+ );
28
+ const currentDomainNames = _.join(", ")(
29
+ getDomainNames("payload.current_domains")(event)
30
+ );
31
+ return [
32
+ [
33
+ { id: "quality_controls.events.action_domain_changed" },
34
+ { domainNames, currentDomainNames, b: bold },
35
+ ],
36
+ ];
37
+ }
38
+ return [];
39
+ };
40
+
41
+ const getPayloadFromDynamicContentChange = (event) => {
42
+ const content = _.propOr({}, "payload.changes.dynamic_content")(event);
43
+ if (!_.isEmpty(content)) {
44
+ return _.flow(
45
+ _.toPairs,
46
+ _.map(([field, value]) => [
47
+ { id: "quality_controls.events.action_dynamic_field_changed" },
48
+ { field, value: normalizeValue(value?.value), b: bold },
49
+ ])
50
+ )(content);
51
+ }
52
+ return [];
53
+ };
54
+
55
+ const getPayloadFromRegularFields = (event) => {
56
+ if (_.has("payload.changes")(event)) {
57
+ return _.flow(
58
+ _.omit(["dynamic_content"]),
59
+ _.omit(["domains"]),
60
+ _.omit(["active"]),
61
+ _.toPairs,
62
+ _.map(([field, value]) => [
63
+ { id: "quality_controls.events.action_field_changed" },
64
+ { field, value: normalizeValue(value), b: bold },
65
+ ])
66
+ )(event?.payload?.changes);
67
+ }
68
+ return [];
69
+ };
70
+
71
+ const getPayloadFromUpdateEvent = (event) => {
72
+ const payloadFromActiveChange = getPayloadFromActiveChange(event);
73
+ const payloadFromRegularFields = getPayloadFromRegularFields(event);
74
+ const payloadFromDomainChange = getPayloadFromDomainChange(event);
75
+ const payloadFromDynamicContentChange =
76
+ getPayloadFromDynamicContentChange(event);
77
+
78
+ return [
79
+ ...payloadFromActiveChange,
80
+ ...payloadFromRegularFields,
81
+ ...payloadFromDomainChange,
82
+ ...payloadFromDynamicContentChange,
83
+ ];
84
+ };
85
+
86
+ const payloadFromEvent = (event) => {
87
+ const eventBase = _.pick(["ts", "user"])(event);
88
+
89
+ switch (event.event) {
90
+ case "quality_control_created":
91
+ return {
92
+ ...eventBase,
93
+ payload: [
94
+ [
95
+ { id: "quality_controls.events.action_created" },
96
+ { field: event?.payload?.name, b: bold },
97
+ ],
98
+ ],
99
+ };
100
+ case "quality_control_version_status_updated":
101
+ return {
102
+ ...eventBase,
103
+ payload: [
104
+ [
105
+ { id: `quality_controls.events.action_${event?.payload?.action}` },
106
+ {
107
+ field: event?.payload?.version,
108
+ value: { id: `quality_control.status.${event?.payload?.status}` },
109
+ b: bold,
110
+ },
111
+ ],
112
+ ],
113
+ };
114
+ case "quality_control_version_draft_created":
115
+ return {
116
+ ...eventBase,
117
+ payload: [
118
+ [
119
+ { id: "quality_controls.events.action_draft_version_created" },
120
+ { value: event?.payload?.version, b: bold },
121
+ ],
122
+ ],
123
+ };
124
+ case "quality_control_updated":
125
+ return { ...eventBase, payload: getPayloadFromUpdateEvent(event) };
126
+ case "quality_control_version_draft_updated":
127
+ return { ...eventBase, payload: getPayloadFromUpdateEvent(event) };
128
+ default:
129
+ return null;
130
+ }
131
+ };
132
+ export const getParsedEvents = (events) => {
133
+ return events.map(payloadFromEvent).filter(Boolean);
134
+ };