@truedat/dq 4.36.8 → 4.37.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +4 -4
  3. package/src/components/RuleEventRow.js +47 -0
  4. package/src/components/RuleEvents.js +32 -0
  5. package/src/components/RuleImplementationsDownload.js +2 -1
  6. package/src/components/RuleRoutes.js +24 -0
  7. package/src/components/RuleTabs.js +15 -1
  8. package/src/components/__tests__/RuleEventRow.spec.js +33 -0
  9. package/src/components/__tests__/RuleEvents.spec.js +64 -0
  10. package/src/components/__tests__/RuleImplementationEvents.spec.js +2 -3
  11. package/src/components/__tests__/__snapshots__/RuleEventRow.spec.js.snap +38 -0
  12. package/src/components/__tests__/__snapshots__/RuleEvents.spec.js.snap +78 -0
  13. package/src/components/__tests__/__snapshots__/RuleImplementationEvents.spec.js.snap +2 -2
  14. package/src/components/ruleImplementationForm/DatasetForm.js +9 -26
  15. package/src/components/ruleImplementationForm/FiltersField.js +2 -12
  16. package/src/components/ruleImplementationForm/__tests__/DataSetForm.spec.js +19 -59
  17. package/src/components/ruleImplementationForm/__tests__/FiltersField.spec.js +4 -18
  18. package/src/components/ruleImplementationForm/__tests__/__snapshots__/DataSetForm.spec.js.snap +7 -147
  19. package/src/components/ruleImplementationForm/__tests__/__snapshots__/FiltersField.spec.js.snap +7 -12
  20. package/src/messages/en.js +8 -0
  21. package/src/messages/es.js +11 -0
  22. package/src/reducers/index.js +0 -2
  23. package/src/routines.js +0 -3
  24. package/src/selectors/__tests__/datasetDefaultFiltersSelector.spec.js +17 -0
  25. package/src/selectors/__tests__/getParsedEvents.spec.js +173 -0
  26. package/src/selectors/datasetDefaultFiltersSelector.js +8 -0
  27. package/src/selectors/getParsedEvents.js +55 -0
  28. package/src/selectors/index.js +1 -1
  29. package/src/reducers/__tests__/ruleImplementationFilterValues.spec.js +0 -36
  30. package/src/reducers/ruleImplementationFilterValues.js +0 -20
  31. package/src/selectors/__tests__/getRuleImplementationFormFilters.spec.js +0 -22
  32. package/src/selectors/getRuleImplementationFormFilters.js +0 -19
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.37.2] 2022-02-02
4
+
5
+ ### Added
6
+
7
+ - [TD-4294] Rule audit tab
8
+
9
+ ## [4.37.1] 2022-02-02
10
+
11
+ ### Changed
12
+
13
+ - [TD-4152] Include aditional information on implementations download
14
+
15
+ ## [4.37.0] 2022-01-25
16
+
17
+ ### Changed
18
+
19
+ - [TD-2564] `DatasetForm` now has a configurable `defaultFilters` prop
20
+ - Simplified `DatasetForm` and `FiltersField` components by removing obscure
21
+ code for installation-specific custom filters
22
+
3
23
  ## [4.36.4] 2022-01-19
4
24
 
5
25
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.36.8",
3
+ "version": "4.37.2",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "dependencies": {
84
84
  "@apollo/client": "^3.4.10",
85
- "@truedat/core": "4.36.8",
86
- "@truedat/df": "4.36.8",
85
+ "@truedat/core": "4.37.2",
86
+ "@truedat/df": "4.37.2",
87
87
  "axios": "^0.19.2",
88
88
  "graphql": "^15.5.3",
89
89
  "path-to-regexp": "^1.7.0",
@@ -103,5 +103,5 @@
103
103
  "react-dom": ">= 16.8.6 < 17",
104
104
  "semantic-ui-react": ">= 0.88.2 < 2.1"
105
105
  },
106
- "gitHead": "b9b8ee80998b230697593b5d207d187971bc0724"
106
+ "gitHead": "ca6ba01d31ab5ec415b9b21d6ce02b418fc6e0ff"
107
107
  }
@@ -0,0 +1,47 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Feed } from "semantic-ui-react";
5
+ import { useIntl } from "react-intl";
6
+ import Moment from "react-moment";
7
+
8
+ export const RuleEventRow = ({ user, user_name, ts, payload }) => {
9
+ const { formatMessage, locale } = useIntl();
10
+ const userName = _.propOr(user_name, "user_name")(user);
11
+
12
+ return _.isEmpty(payload) ? null : (
13
+ <Feed.Event>
14
+ <Feed.Content>
15
+ <Feed.Summary>
16
+ <Feed.User>{userName}</Feed.User>{" "}
17
+ <Feed.Date>
18
+ <Moment locale={locale} date={ts} format="YYYY-MM-DD HH:mm" />
19
+ </Feed.Date>
20
+ </Feed.Summary>
21
+ {payload.map(({ field, action, value }, i) => (
22
+ <Feed.Extra
23
+ key={i}
24
+ text
25
+ content={formatMessage(
26
+ {
27
+ id: `rules.events.action_${action}${
28
+ typeof value === "undefined" || _.isObject(value) ? "" : "_to"
29
+ }`,
30
+ },
31
+ [field, JSON.stringify(value)]
32
+ )}
33
+ />
34
+ ))}
35
+ </Feed.Content>
36
+ </Feed.Event>
37
+ );
38
+ };
39
+
40
+ RuleEventRow.propTypes = {
41
+ user: PropTypes.object,
42
+ user_name: PropTypes.string,
43
+ ts: PropTypes.string,
44
+ payload: PropTypes.array,
45
+ };
46
+
47
+ export default RuleEventRow;
@@ -0,0 +1,32 @@
1
+
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Feed, Segment } from "semantic-ui-react";
5
+ import { connect } from "react-redux";
6
+ import { getParsedEvents } from "../selectors";
7
+ import { RuleEventRow } from "./RuleEventRow";
8
+
9
+ export const RuleEvents = ({ events, eventsLoading }) => {
10
+ return eventsLoading ? null : (
11
+ <Segment attached="bottom">
12
+ <Feed size="small">
13
+ {events.map((e, i) => (
14
+ <RuleEventRow key={i} {...e} />
15
+ ))}
16
+ </Feed>
17
+ </Segment>
18
+ );
19
+ };
20
+ RuleEvents.propTypes = {
21
+ events: PropTypes.array.isRequired,
22
+ eventsLoading: PropTypes.bool,
23
+ };
24
+
25
+ const mapStateToProps = (state) => {
26
+ return {
27
+ events: getParsedEvents(state),
28
+ eventsLoading: state.eventsLoading,
29
+ };
30
+ };
31
+
32
+ export default connect(mapStateToProps)(RuleEvents);
@@ -10,7 +10,8 @@ const staticHeaderLabels = [
10
10
  "implementation_key",
11
11
  "implementation_type",
12
12
  "rule",
13
- "template",
13
+ "rule_template",
14
+ "implementation_template",
14
15
  "goal",
15
16
  "minimum",
16
17
  "business_concept",
@@ -9,6 +9,7 @@ import { useAuthorized } from "@truedat/core/hooks";
9
9
  import {
10
10
  RULE,
11
11
  RULE_EDIT,
12
+ RULE_EVENTS,
12
13
  RULE_IMPLEMENTATION_RESULT_DETAILS,
13
14
  RULE_IMPLEMENTATION_RESULTS_DETAILS,
14
15
  RULE_IMPLEMENTATION,
@@ -32,6 +33,7 @@ import NewRule from "./NewRule";
32
33
  import NewRuleImplementation from "./NewRuleImplementation";
33
34
  import Rule from "./Rule";
34
35
  import RuleCrumbs from "./RuleCrumbs";
36
+ import RuleEvents from "./RuleEvents";
35
37
  import RuleImplementation from "./RuleImplementation";
36
38
  import RuleImplementationEvents from "./RuleImplementationEvents";
37
39
  import RuleImplementationLoader from "./RuleImplementationLoader";
@@ -61,6 +63,12 @@ const ImplementationStructuresLoader = React.lazy(() =>
61
63
  const QualityTemplatesLoader = () => <TemplatesLoader scope="dq" />;
62
64
  const ImplementationTemplatesLoader = () => <TemplatesLoader scope="ri" />;
63
65
 
66
+ const RuleEventsLoader = () => {
67
+ const match = useRouteMatch();
68
+ const id = _.path("params.id")(match);
69
+ return <EventsLoader resource_id={id} resource_type="rule" />;
70
+ };
71
+
64
72
  const ImplementationEventsLoader = () => {
65
73
  const match = useRouteMatch();
66
74
  const id = _.path("params.implementation_id")(match);
@@ -135,6 +143,22 @@ const RuleRoutes = ({
135
143
  </>
136
144
  )}
137
145
  />
146
+
147
+ <Route
148
+ exact
149
+ path={RULE_EVENTS}
150
+ render={() => (
151
+ <>
152
+ <RuleCrumbs />
153
+ <Segment>
154
+ <RuleEventsLoader />
155
+ {ruleLoaded && <Rule />}
156
+ {ruleLoaded && <RuleEvents />}
157
+ </Segment>
158
+ </>
159
+ )}
160
+ />
161
+
138
162
  <Route
139
163
  exact
140
164
  path={RULE_IMPLEMENTATIONS}
@@ -6,7 +6,12 @@ import { Link } from "react-router-dom";
6
6
  import { connect } from "react-redux";
7
7
  import { FormattedMessage } from "react-intl";
8
8
  import { usePath } from "@truedat/core/hooks";
9
- import { RULE, RULE_IMPLEMENTATIONS, linkTo } from "@truedat/core/routes";
9
+ import {
10
+ RULE,
11
+ RULE_IMPLEMENTATIONS,
12
+ RULE_EVENTS,
13
+ linkTo,
14
+ } from "@truedat/core/routes";
10
15
 
11
16
  const RuleTabs = ({ rule }) => {
12
17
  const path = usePath();
@@ -22,6 +27,15 @@ const RuleTabs = ({ rule }) => {
22
27
  >
23
28
  <FormattedMessage id="tabs.dq.ruleImplementations" />
24
29
  </Menu.Item>
30
+ <Menu.Item
31
+ active={path === RULE_EVENTS}
32
+ as={Link}
33
+ to={linkTo.RULE_EVENTS({
34
+ id: rule.id,
35
+ })}
36
+ >
37
+ <FormattedMessage id="tabs.dq.rule.audit" />
38
+ </Menu.Item>
25
39
  </Menu>
26
40
  );
27
41
  };
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { RuleEventRow } from "../RuleEventRow";
4
+
5
+ describe("<RuleEventsRow />", () => {
6
+ const props = {
7
+ payload: [
8
+ {
9
+ field: "field1",
10
+ action: "changed",
11
+ value: "new value",
12
+ },
13
+ ],
14
+ user_name: "some.user@domain.tld",
15
+ user: {
16
+ email: "some.user@domain.tld",
17
+ full_name: "Some User",
18
+ id: 142,
19
+ user_name: "some.user@domain.tld",
20
+ },
21
+ ts: "2021-09-16T07:36:32.848603Z",
22
+ };
23
+ const renderOpts = {
24
+ messages: {
25
+ en: { "rules.events.action_changed_to": "Field {0} changed to: {1}" },
26
+ },
27
+ };
28
+
29
+ it("matches the latest snapshot", () => {
30
+ const { container } = render(<RuleEventRow {...props} />, renderOpts);
31
+ expect(container).toMatchSnapshot();
32
+ });
33
+ });
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { RuleEvents } from "../RuleEvents";
4
+ import { getParsedEvents } from "../../selectors/getParsedEvents";
5
+
6
+ describe("<RuleEvents />", () => {
7
+ const events = [
8
+ {
9
+ id: 100217,
10
+ service: "td_dd",
11
+ resource_id: 777,
12
+ resource_type: "rule",
13
+ event: "rule_updated",
14
+ payload: {
15
+ domain_id: 140,
16
+ },
17
+ user_id: 142,
18
+ user_name: null,
19
+ user: {
20
+ email: "some.user@domain.tld",
21
+ full_name: "Some User",
22
+ id: 142,
23
+ user_name: "some.user@domain.tld"
24
+ },
25
+ ts: "2021-09-16T07:36:32.848603Z"
26
+ },
27
+ {
28
+ id: 100436,
29
+ service: "td_dd",
30
+ resource_id: 777,
31
+ resource_type: "rule",
32
+ event: "rule_updated",
33
+ payload: {
34
+ domain_id: 168
35
+ },
36
+ user_id: 171,
37
+ user_name: null,
38
+ user: {
39
+ email: "some.user@domain.tld",
40
+ full_name: "Some User",
41
+ id: 171,
42
+ user_name: "some.user"
43
+ },
44
+ ts: "2021-09-16T09:31:03.061864Z"
45
+ },
46
+ ];
47
+ const renderOpts = {
48
+ messages: {
49
+ en: {
50
+ "rules.events.action_created": "Rule created",
51
+ "rules.events.action_changed": "Field {0} changed: {1}",
52
+ "rules.events.action_changed_to": "Field {0} changed to: {1}",
53
+ "rules.events.action_updated": "Rule updated",
54
+ },
55
+ },
56
+ };
57
+
58
+ const props = { events: getParsedEvents({ events }), eventsLoading: false };
59
+
60
+ it("matches the latest snapshot", () => {
61
+ const { container } = render(<RuleEvents {...props} />, renderOpts);
62
+ expect(container).toMatchSnapshot();
63
+ });
64
+ });
@@ -24,9 +24,8 @@ describe("<RuleImplementationEvents />", () => {
24
24
  const renderOpts = {
25
25
  messages: {
26
26
  en: {
27
- "ruleImplementations.events.field_restored": "implementation restored",
28
- "ruleImplementations.events.field_deprecated":
29
- "implementation deprecated",
27
+ "ruleImplementations.events.action_deprecated": "deprecated",
28
+ "ruleImplementations.events.action_restored": "restored",
30
29
  },
31
30
  },
32
31
  };
@@ -0,0 +1,38 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleEventsRow /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="event"
7
+ >
8
+ <div
9
+ class="content"
10
+ >
11
+ <div
12
+ class="summary"
13
+ >
14
+ <a
15
+ class="user"
16
+ >
17
+ some.user@domain.tld
18
+ </a>
19
+
20
+ <div
21
+ class="date"
22
+ >
23
+ <time
24
+ datetime="1631777792848"
25
+ >
26
+ 2021-09-16 07:36
27
+ </time>
28
+ </div>
29
+ </div>
30
+ <div
31
+ class="text extra"
32
+ >
33
+ Field field1 changed to: "new value"
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ `;
@@ -0,0 +1,78 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleEvents /> matches the latest snapshot 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
+ some.user@domain.tld
24
+ </a>
25
+
26
+ <div
27
+ class="date"
28
+ >
29
+ <time
30
+ datetime="1631777792848"
31
+ >
32
+ 2021-09-16 07:36
33
+ </time>
34
+ </div>
35
+ </div>
36
+ <div
37
+ class="text extra"
38
+ >
39
+ Field domain_id changed to: 140
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <div
44
+ class="event"
45
+ >
46
+ <div
47
+ class="content"
48
+ >
49
+ <div
50
+ class="summary"
51
+ >
52
+ <a
53
+ class="user"
54
+ >
55
+ some.user
56
+ </a>
57
+
58
+ <div
59
+ class="date"
60
+ >
61
+ <time
62
+ datetime="1631784663061"
63
+ >
64
+ 2021-09-16 09:31
65
+ </time>
66
+ </div>
67
+ </div>
68
+ <div
69
+ class="text extra"
70
+ >
71
+ Field domain_id changed to: 168
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ `;
@@ -34,7 +34,7 @@ exports[`<RuleImplementationEvents /> matches the latest snapshot 1`] = `
34
34
  <div
35
35
  class="text extra"
36
36
  >
37
- ruleImplementations.events.action_restored
37
+ restored
38
38
  </div>
39
39
  </div>
40
40
  </div>
@@ -64,7 +64,7 @@ exports[`<RuleImplementationEvents /> matches the latest snapshot 1`] = `
64
64
  <div
65
65
  class="text extra"
66
66
  >
67
- ruleImplementations.events.action_deprecated
67
+ deprecated
68
68
  </div>
69
69
  </div>
70
70
  </div>
@@ -5,8 +5,7 @@ import { connect } from "react-redux";
5
5
  import { useIntl } from "react-intl";
6
6
  import { dropAt, replaceAt } from "@truedat/core/services/arrays";
7
7
  import { Button } from "semantic-ui-react";
8
- import { setImplementationFilterValues } from "../../routines";
9
- import { getRuleImplementationFormFilters } from "../../selectors";
8
+ import { datasetDefaultFiltersSelector } from "../../selectors";
10
9
  import "../../styles/ruleImplementationForm/DatasetForm.less";
11
10
 
12
11
  const StructureSelectorInputField = React.lazy(() =>
@@ -14,10 +13,9 @@ const StructureSelectorInputField = React.lazy(() =>
14
13
  );
15
14
 
16
15
  export const DatasetForm = ({
17
- customFilters,
16
+ defaultFilters,
18
17
  structures,
19
18
  selector,
20
- setImplementationFilterValues,
21
19
  setStructures,
22
20
  setSelector,
23
21
  }) => {
@@ -66,12 +64,9 @@ export const DatasetForm = ({
66
64
  const setStructure = ({ index, value: param_value }) => {
67
65
  const structureChanged = hasChangedStructure(index, param_value);
68
66
  const value = structureChanged ? setAliasIndex(param_value) : param_value;
69
- if (index === 0 && structureChanged) {
70
- setStructures([value ? { ...value } : {}]);
71
- setImplementationFilterValues({ value: _.get("structure")(value) });
72
- } else if (_.isNil(index))
67
+ if (_.isNil(index)) {
73
68
  setStructures([...structures, value ? { ...value } : {}]);
74
- else if (value) {
69
+ } else if (value) {
75
70
  const mergedValue = { ..._.nth(index)(structures), ...value };
76
71
  setStructures(replaceAt(index, mergedValue)(structures));
77
72
  } else setStructures(deleteStructure(index, structures));
@@ -88,13 +83,6 @@ export const DatasetForm = ({
88
83
  return dropAt(index)(structures);
89
84
  };
90
85
 
91
- const buildDefaultFilters = (i) => {
92
- const defaultFilters = { "class.raw": [""] };
93
- return i == 0
94
- ? { defaultFilters }
95
- : { defaultFilters: { ...defaultFilters, ...customFilters } };
96
- };
97
-
98
86
  return (
99
87
  <>
100
88
  {structures.map((structure, index) => (
@@ -102,7 +90,7 @@ export const DatasetForm = ({
102
90
  active={index === selector}
103
91
  joined={index > 0}
104
92
  key={index}
105
- options={buildDefaultFilters(index)}
93
+ defaultFilters={defaultFilters}
106
94
  onChange={(value) => {
107
95
  setSelector(-1);
108
96
  onChange(index, { structure: value });
@@ -125,27 +113,22 @@ export const DatasetForm = ({
125
113
  setStructure({ value: { join_type: "inner" } });
126
114
  }}
127
115
  >
128
- {formatMessage({
129
- id: "dataset.form.button.add_structure",
130
- })}
116
+ {formatMessage({ id: "dataset.form.button.add_structure" })}
131
117
  </Button>
132
118
  </>
133
119
  );
134
120
  };
135
121
 
136
122
  DatasetForm.propTypes = {
137
- customFilters: PropTypes.object,
123
+ defaultFilters: PropTypes.object,
138
124
  structures: PropTypes.array,
139
125
  selector: PropTypes.number,
140
126
  setStructures: PropTypes.func,
141
127
  setSelector: PropTypes.func,
142
- setImplementationFilterValues: PropTypes.func,
143
128
  };
144
129
 
145
130
  const mapStateToProps = (state) => ({
146
- customFilters: getRuleImplementationFormFilters(state),
131
+ defaultFilters: datasetDefaultFiltersSelector(state),
147
132
  });
148
133
 
149
- export default connect(mapStateToProps, { setImplementationFilterValues })(
150
- DatasetForm
151
- );
134
+ export default connect(mapStateToProps)(DatasetForm);
@@ -1,8 +1,6 @@
1
1
  import _ from "lodash/fp";
2
2
  import React, { useState } from "react";
3
- import { connect } from "react-redux";
4
3
  import PropTypes from "prop-types";
5
- import { getRuleImplementationFormFilters } from "../../selectors";
6
4
  import { getStructureFields } from "../../selectors/getStructureFields";
7
5
  import DateField from "./DateField";
8
6
  import DateTimeField from "./DateTimeField";
@@ -36,7 +34,6 @@ const reducedStructureColumns = [
36
34
  ];
37
35
 
38
36
  export const FiltersField = ({
39
- customFilters,
40
37
  parentStructures,
41
38
  label,
42
39
  operator,
@@ -97,9 +94,7 @@ export const FiltersField = ({
97
94
  <StructureSelectorInputField
98
95
  active={active}
99
96
  joined={false}
100
- options={{
101
- defaultFilters: { "class.raw": ["field"], ...customFilters },
102
- }}
97
+ defaultFilters={{ "class.raw": ["field"] }}
103
98
  onChange={(value) => {
104
99
  onChange(null, {
105
100
  data_structure_id: _.prop("id")(value),
@@ -159,7 +154,6 @@ export const FiltersField = ({
159
154
  };
160
155
 
161
156
  FiltersField.propTypes = {
162
- customFilters: PropTypes.object,
163
157
  label: PropTypes.string,
164
158
  parentStructures: PropTypes.array,
165
159
  operator: PropTypes.object,
@@ -171,8 +165,4 @@ FiltersField.propTypes = {
171
165
  typeCastModifiers: PropTypes.array,
172
166
  };
173
167
 
174
- const mapStateToProps = (state) => ({
175
- customFilters: getRuleImplementationFormFilters(state),
176
- });
177
-
178
- export default connect(mapStateToProps)(FiltersField);
168
+ export default FiltersField;