@truedat/dq 4.43.4 → 4.43.5

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 (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +5 -5
  3. package/src/components/NewRuleImplementation.js +2 -3
  4. package/src/components/RuleImplementation.js +102 -86
  5. package/src/components/RuleImplementationResultTabs.js +3 -13
  6. package/src/components/RuleResult.js +1 -54
  7. package/src/components/RuleResultRemediations.js +0 -7
  8. package/src/components/RuleResultRow.js +10 -0
  9. package/src/components/RuleResultSegmentRow.js +2 -2
  10. package/src/components/RuleResultSegments.js +16 -2
  11. package/src/components/RuleResultsRoutes.js +2 -2
  12. package/src/components/RuleRoutes.js +13 -1
  13. package/src/components/__test_samples__/NewRuleImplementationProps.js +0 -3
  14. package/src/components/__tests__/NewRuleImplementation.spec.js +0 -1
  15. package/src/components/__tests__/RuleImplementationResultTabs.spec.js +30 -0
  16. package/src/components/__tests__/RuleResult.spec.js +34 -0
  17. package/src/components/__tests__/{ExecutionDetails.spec.js → RuleResultDetails.spec.js} +0 -0
  18. package/src/components/__tests__/RuleResultRemediationLoader.spec.js +33 -0
  19. package/src/components/__tests__/RuleResultRemediations.spec.js +56 -0
  20. package/src/components/__tests__/RuleResultRoutes.spec.js +12 -0
  21. package/src/components/__tests__/RuleResultSegmentRow.spec.js +183 -0
  22. package/src/components/__tests__/RuleResultSegments.spec.js +68 -0
  23. package/src/components/__tests__/__snapshots__/NewRuleImplementation.spec.js.snap +0 -34
  24. package/src/components/__tests__/__snapshots__/RuleImplementationResultTabs.spec.js.snap +16 -0
  25. package/src/components/__tests__/__snapshots__/RuleResult.spec.js.snap +3 -0
  26. package/src/components/__tests__/__snapshots__/{ExecutionDetails.spec.js.snap → RuleResultDetails.spec.js.snap} +0 -0
  27. package/src/components/__tests__/__snapshots__/RuleResultRemediations.spec.js.snap +3 -0
  28. package/src/components/__tests__/__snapshots__/RuleResultRoutes.spec.js.snap +3 -0
  29. package/src/components/__tests__/__snapshots__/RuleResultSegments.spec.js.snap +3 -0
  30. package/src/components/ruleImplementationForm/RuleImplementationForm.js +13 -3
  31. package/src/components/ruleImplementationForm/__tests__/__snapshots__/RuleImplementationForm.spec.js.snap +0 -17
  32. package/src/selectors/getSegmentResultsColumns.js +9 -0
  33. package/src/selectors/index.js +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.43.5] 2022-05-05
4
+
5
+ # Added
6
+
7
+ - [TD-4538] Add more coding for segments configuration and segment results for implementations
8
+
3
9
  ## [4.43.3] 2022-05-04
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.43.4",
3
+ "version": "4.43.5",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -31,7 +31,7 @@
31
31
  "@babel/plugin-transform-modules-commonjs": "^7.15.0",
32
32
  "@babel/preset-env": "^7.15.0",
33
33
  "@babel/preset-react": "^7.14.5",
34
- "@truedat/test": "4.43.4",
34
+ "@truedat/test": "4.43.5",
35
35
  "babel-jest": "^27.0.6",
36
36
  "babel-plugin-dynamic-import-node": "^2.3.3",
37
37
  "babel-plugin-lodash": "^3.3.4",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "dependencies": {
84
84
  "@apollo/client": "^3.4.10",
85
- "@truedat/core": "4.43.4",
86
- "@truedat/df": "4.43.4",
85
+ "@truedat/core": "4.43.5",
86
+ "@truedat/df": "4.43.5",
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": "7f0463a65a0b8137f1598487bf26fefa52e5fa59"
106
+ "gitHead": "ab63882af7d0c31643f56b9c7eafca813763dd9e"
107
107
  }
@@ -224,8 +224,8 @@ const updateStructureParentIndex = (structures) => (structure) => {
224
224
  return structure;
225
225
  };
226
226
 
227
- const withConditionDefaultAlias = (conditions, structures) => {
228
- return _.map((p) => {
227
+ const withConditionDefaultAlias = (conditions, structures) =>
228
+ _.map((p) => {
229
229
  return {
230
230
  ...p,
231
231
  structure: updateStructureParentIndex(structures)(p.structure),
@@ -235,7 +235,6 @@ const withConditionDefaultAlias = (conditions, structures) => {
235
235
  : p.value,
236
236
  };
237
237
  })(conditions);
238
- };
239
238
 
240
239
  export const NewRuleImplementation = ({
241
240
  canManageRaw,
@@ -13,92 +13,102 @@ import RuleImplementationTabs from "./RuleImplementationTabs";
13
13
  import ImplementationResultBar from "./ImplementationResultBar";
14
14
 
15
15
  const getAvailableActions = (props, formatMessage) => {
16
- const contentActions = _.isEmpty(props?.ruleImplementation) ? [] : [
17
- {
18
- key: "delete",
19
- value: "delete",
20
- as: OptionModal,
21
- headerMessage: "ruleImplementation.actions.delete.confirmation.header",
22
- contentMessage: "ruleImplementation.actions.delete.confirmation.content",
23
- iconName: "trash alternate outline",
24
- iconColor: "red",
25
- option: "ruleImplementation.actions.delete",
26
- selected: false,
27
- active: false,
28
- filter: !_.prop("activeMode")(props),
29
- ...props,
30
- },
31
- {
32
- key: "softDelete",
33
- value: "softDelete",
34
- as: OptionModal,
35
- headerMessage: "ruleImplementation.actions.delete.confirmation.header",
36
- contentMessage: "ruleImplementation.actions.delete.confirmation.content",
37
- iconName: "folder open outline",
38
- iconColor: "black",
39
- option: "ruleImplementation.actions.deprecate",
40
- selected: false,
41
- active: false,
42
- filter: _.prop("activeMode")(props),
43
- ...props,
44
- },
45
- {
46
- key: "restore",
47
- iconName: "undo",
48
- option: "ruleImplementation.actions.restore",
49
- value: "restore",
50
- as: OptionModal,
51
- headerMessage: "ruleImplementation.actions.restore.confirmation.header",
52
- contentMessage: "ruleImplementation.actions.restore.confirmation.content",
53
- selected: false,
54
- active: false,
55
- filter: !_.prop("activeMode")(props),
56
- restore: true,
57
- ...props,
58
- },
59
- {
60
- key: "edit",
61
- icon: "edit outline",
62
- text: formatMessage({ id: "ruleImplementation.actions.edit" }),
63
- value: "edit",
64
- as: Link,
65
- to: linkTo.RULE_IMPLEMENTATION_EDIT({
66
- id: _.path("rule.id")(props),
67
- implementation_id: _.path("ruleImplementation.id")(props),
68
- }),
69
- filter: true,
70
- selected: false,
71
- active: false,
72
- },
73
- {
74
- key: "move",
75
- icon: "share square",
76
- text: formatMessage({ id: "ruleImplementation.actions.move" }),
77
- value: "move",
78
- as: Link,
79
- to: linkTo.RULE_IMPLEMENTATION_MOVE({
80
- id: _.path("rule.id")(props),
81
- implementation_id: _.path("ruleImplementation.id")(props),
82
- }),
83
- filter: props?.authorized,
84
- selected: false,
85
- active: false,
86
- },
87
- {
88
- key: "clone",
89
- icon: "copy outline",
90
- text: formatMessage({ id: "ruleImplementation.actions.clone" }),
91
- value: "clone",
92
- as: Link,
93
- to: linkTo.RULE_IMPLEMENTATION_CLONE({
94
- id: _.path("rule.id")(props),
95
- implementation_id: _.path("ruleImplementation.id")(props),
96
- }),
97
- filter: true,
98
- selected: false,
99
- active: false,
100
- },
101
- ];
16
+ const contentActions = _.isEmpty(props?.ruleImplementation)
17
+ ? []
18
+ : [
19
+ {
20
+ key: "delete",
21
+ value: "delete",
22
+ as: OptionModal,
23
+ headerMessage:
24
+ "ruleImplementation.actions.delete.confirmation.header",
25
+ contentMessage:
26
+ "ruleImplementation.actions.delete.confirmation.content",
27
+ iconName: "trash alternate outline",
28
+ iconColor: "red",
29
+ option: "ruleImplementation.actions.delete",
30
+ selected: false,
31
+ active: false,
32
+ filter: !_.prop("activeMode")(props),
33
+ ...props,
34
+ },
35
+ {
36
+ key: "softDelete",
37
+ value: "softDelete",
38
+ as: OptionModal,
39
+ headerMessage:
40
+ "ruleImplementation.actions.delete.confirmation.header",
41
+ contentMessage:
42
+ "ruleImplementation.actions.delete.confirmation.content",
43
+ iconName: "folder open outline",
44
+ iconColor: "black",
45
+ option: "ruleImplementation.actions.deprecate",
46
+ selected: false,
47
+ active: false,
48
+ filter: _.prop("activeMode")(props),
49
+ ...props,
50
+ },
51
+ {
52
+ key: "restore",
53
+ iconName: "undo",
54
+ option: "ruleImplementation.actions.restore",
55
+ value: "restore",
56
+ as: OptionModal,
57
+ headerMessage:
58
+ "ruleImplementation.actions.restore.confirmation.header",
59
+ contentMessage:
60
+ "ruleImplementation.actions.restore.confirmation.content",
61
+ selected: false,
62
+ active: false,
63
+ filter: !_.prop("activeMode")(props),
64
+ restore: true,
65
+ ...props,
66
+ },
67
+ {
68
+ key: "edit",
69
+ icon: "edit outline",
70
+ text: formatMessage({ id: "ruleImplementation.actions.edit" }),
71
+ value: "edit",
72
+ as: Link,
73
+ to: linkTo.RULE_IMPLEMENTATION_EDIT({
74
+ id: _.path("rule.id")(props),
75
+ implementation_id: _.path("ruleImplementation.id")(props),
76
+ }),
77
+ filter:
78
+ props.authManageSegments ||
79
+ _.isEmpty(props.ruleImplementation.segments),
80
+ selected: false,
81
+ active: false,
82
+ },
83
+ {
84
+ key: "move",
85
+ icon: "share square",
86
+ text: formatMessage({ id: "ruleImplementation.actions.move" }),
87
+ value: "move",
88
+ as: Link,
89
+ to: linkTo.RULE_IMPLEMENTATION_MOVE({
90
+ id: _.path("rule.id")(props),
91
+ implementation_id: _.path("ruleImplementation.id")(props),
92
+ }),
93
+ filter: props?.authorized,
94
+ selected: false,
95
+ active: false,
96
+ },
97
+ {
98
+ key: "clone",
99
+ icon: "copy outline",
100
+ text: formatMessage({ id: "ruleImplementation.actions.clone" }),
101
+ value: "clone",
102
+ as: Link,
103
+ to: linkTo.RULE_IMPLEMENTATION_CLONE({
104
+ id: _.path("rule.id")(props),
105
+ implementation_id: _.path("ruleImplementation.id")(props),
106
+ }),
107
+ filter: true,
108
+ selected: false,
109
+ active: false,
110
+ },
111
+ ];
102
112
 
103
113
  return _.flow(_.filter("filter"), _.map(_.omit(["filter"])))(contentActions);
104
114
  };
@@ -159,6 +169,7 @@ export const RuleImplementation = ({
159
169
  rule,
160
170
  ruleImplementation,
161
171
  userRulePermissions,
172
+ authManageSegments,
162
173
  }) => {
163
174
  const authorized = useAuthorized();
164
175
  const { formatMessage } = useIntl();
@@ -171,6 +182,7 @@ export const RuleImplementation = ({
171
182
  rule,
172
183
  ruleImplementation,
173
184
  setRuleImplementationStatus,
185
+ authManageSegments,
174
186
  },
175
187
  formatMessage
176
188
  );
@@ -218,6 +230,7 @@ RuleImplementation.propTypes = {
218
230
  rule: PropTypes.object,
219
231
  ruleImplementation: PropTypes.object,
220
232
  userRulePermissions: PropTypes.object,
233
+ authManageSegments: PropTypes.bool,
221
234
  children: PropTypes.node,
222
235
  };
223
236
 
@@ -225,10 +238,13 @@ const mapStateToProps = ({
225
238
  rule,
226
239
  ruleImplementation,
227
240
  userRulePermissions,
241
+ implementationActions,
228
242
  }) => ({
229
243
  rule,
230
244
  ruleImplementation,
231
245
  userRulePermissions,
246
+ authManageSegments:
247
+ _.pathOr(false, "manage_segments.method")(implementationActions) === "POST",
232
248
  });
233
249
 
234
250
  export default connect(mapStateToProps, { setRuleImplementationStatus })(
@@ -9,16 +9,10 @@ import { FormattedMessage } from "react-intl";
9
9
  import {
10
10
  RULE_IMPLEMENTATION_RESULT_DETAILS,
11
11
  RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
12
- //TODO: change name route
13
- RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN,
12
+ RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN,
14
13
  linkTo,
15
14
  } from "@truedat/core/routes";
16
15
 
17
- import RuleResultRemediationLoader from "./RuleResultRemediationLoader";
18
- const TemplatesLoader = React.lazy(() =>
19
- import("@truedat/df/templates/components/TemplatesLoader")
20
- );
21
-
22
16
  export const RuleImplementationResultTabs = ({
23
17
  rule,
24
18
  ruleImplementation,
@@ -49,11 +43,9 @@ export const RuleImplementationResultTabs = ({
49
43
 
50
44
  return renderCondition ? (
51
45
  <Menu.Item
52
- active={
53
- match.path === RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN
54
- }
46
+ active={match.path === RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN}
55
47
  as={Link}
56
- to={linkTo.RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN({
48
+ to={linkTo.RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN({
57
49
  id: rule.id,
58
50
  implementation_id: ruleImplementation.id,
59
51
  rule_result_id: ruleResult.id,
@@ -66,8 +58,6 @@ export const RuleImplementationResultTabs = ({
66
58
 
67
59
  return _.isEmpty(ruleImplementation) || _.isEmpty(rule) ? null : (
68
60
  <>
69
- <RuleResultRemediationLoader propRuleResultId={ruleResult.id} />
70
- <TemplatesLoader scope="remediation" />
71
61
  <Menu attached="top" pointing secondary tabular>
72
62
  <Menu.Item
73
63
  active={match.path === RULE_IMPLEMENTATION_RESULT_DETAILS}
@@ -1,67 +1,14 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
- import { useIntl } from "react-intl";
5
4
  import { connect } from "react-redux";
6
5
  import { useParams } from "react-router-dom";
7
- import { Header, Table, Icon } from "semantic-ui-react";
8
- import { DateTime } from "@truedat/core/components";
6
+ import { Header, Icon } from "semantic-ui-react";
9
7
  import { FormattedMessage } from "react-intl";
10
- import { selectColor } from "../functions/selectors";
11
8
  import RuleImplementationResultTabs from "./RuleImplementationResultTabs";
12
9
 
13
- const GeneralInformation = ({ ruleImplementation, ruleResult }) => {
14
- const { formatMessage, formatNumber: _formatNumber } = useIntl();
15
- const formatNumber = (num) => (_.isNil(num) ? num : _formatNumber(num));
16
- return (
17
- <>
18
- <Table.Row>
19
- <Table.Cell>
20
- {formatMessage({ id: "ruleResult.props.quality" })}
21
- </Table.Cell>
22
- <Table.Cell>
23
- <Icon
24
- name="circle"
25
- color={selectColor({ ...ruleImplementation, ...ruleResult })}
26
- />
27
- {`${parseFloat(ruleResult.result)} %`}
28
- </Table.Cell>
29
- </Table.Row>
30
-
31
- <Table.Row>
32
- <Table.Cell>
33
- {formatMessage({ id: "ruleResult.props.date" })}
34
- </Table.Cell>
35
- <Table.Cell>
36
- <DateTime value={ruleResult.date} />
37
- </Table.Cell>
38
- </Table.Row>
39
-
40
- <Table.Row>
41
- <Table.Cell>
42
- {formatMessage({ id: "ruleResult.props.records" })}
43
- </Table.Cell>
44
- <Table.Cell>{formatNumber(ruleResult.records)}</Table.Cell>
45
- </Table.Row>
46
-
47
- <Table.Row>
48
- <Table.Cell>
49
- {formatMessage({ id: "ruleResult.props.errors" })}
50
- </Table.Cell>
51
- <Table.Cell>{formatNumber(ruleResult.errors)}</Table.Cell>
52
- </Table.Row>
53
- </>
54
- );
55
- };
56
-
57
- GeneralInformation.propTypes = {
58
- ruleImplementation: PropTypes.object,
59
- ruleResult: PropTypes.object,
60
- };
61
-
62
10
  export const RuleResult = ({ ruleImplementation, ruleResultId, children }) => {
63
11
  const { rule_result_id: paramsId } = useParams();
64
-
65
12
  const resultId = _.defaultTo(_.toNumber(paramsId))(ruleResultId);
66
13
 
67
14
  const ruleResult = _.find(_.propEq("id", resultId))(
@@ -6,11 +6,6 @@ import { useParams } from "react-router-dom";
6
6
  import RemediationPlan from "./RemediationPlan";
7
7
  import "../styles/executionDetails.less";
8
8
 
9
- import RuleResultRemediationLoader from "./RuleResultRemediationLoader";
10
- const TemplatesLoader = React.lazy(() =>
11
- import("@truedat/df/templates/components/TemplatesLoader")
12
- );
13
-
14
9
  export const RuleResultRemediations = ({
15
10
  ruleImplementation,
16
11
  ruleResultId,
@@ -25,8 +20,6 @@ export const RuleResultRemediations = ({
25
20
 
26
21
  return _.isEmpty(ruleResult) ? null : (
27
22
  <>
28
- <RuleResultRemediationLoader propRuleResultId={ruleResult.id} />
29
- <TemplatesLoader scope="remediation" />
30
23
  <RemediationPlan
31
24
  className="execution-details-remediation"
32
25
  latestResultId={resultId}
@@ -37,6 +37,16 @@ export const RuleResultRow = ({
37
37
  })}
38
38
  >
39
39
  <DateTime className="rule-result-date" value={ruleResult.date} />
40
+ {ruleResult.has_segments ? (
41
+ <Icon
42
+ circular
43
+ inverted
44
+ name="grid layout"
45
+ color="blue"
46
+ size="small"
47
+ />
48
+ ) : null}
49
+
40
50
  {ruleResult.has_remediation ? (
41
51
  <Icon circular inverted name="rain" color="blue" size="small" />
42
52
  ) : null}
@@ -24,7 +24,7 @@ export const RuleResultSegmentRow = ({
24
24
 
25
25
  return (
26
26
  <Table.Row>
27
- <Table.Cell>{_.prop("name")(segmentResult.params)}</Table.Cell>
27
+ <Table.Cell>{_.prop("segment_name")(segmentResult.params)}</Table.Cell>
28
28
  <Table.Cell>
29
29
  <Icon
30
30
  name="circle"
@@ -75,7 +75,7 @@ RuleResultSegmentRow.propTypes = {
75
75
  optionalColumns: PropTypes.array,
76
76
  rule: PropTypes.object,
77
77
  ruleImplementation: PropTypes.object,
78
- ruleResult: PropTypes.object,
78
+ segmentResult: PropTypes.object,
79
79
  };
80
80
 
81
81
  const mapDispatchToProps = { deleteRuleResult };
@@ -5,8 +5,16 @@ import { useIntl } from "react-intl";
5
5
  import { connect } from "react-redux";
6
6
  import { Table, Message, Divider } from "semantic-ui-react";
7
7
  import { columnDecorator } from "@truedat/core/services";
8
+ import { getSegmentResultsColumns } from "../selectors";
8
9
  import RuleResultSegmentRow from "./RuleResultSegmentRow";
9
10
 
11
+ const optionalColumns = ["errors", "records", "details"];
12
+
13
+ const getOptionalColumnsWithData = (segmentResults) =>
14
+ _.filter((column) =>
15
+ _.any(_.flow(_.prop(column), _.negate(_.isNil)))(segmentResults)
16
+ )(optionalColumns);
17
+
10
18
  export const getCustomColumnsWithData = (segmentResults, columns) =>
11
19
  _.filter((column) =>
12
20
  _.any(_.flow(columnDecorator(column), _.negate(_.isEmpty)))(segmentResults)
@@ -16,11 +24,12 @@ export const RuleResultSegments = ({
16
24
  rule,
17
25
  isAdmin,
18
26
  ruleImplementation,
27
+ customColumns,
19
28
  segmentResults,
20
29
  }) => {
21
30
  const { formatMessage } = useIntl();
22
- const customColumns = [];
23
- const optionalColumns = ["errors", "records", "details"];
31
+
32
+ const optionalColumns = getOptionalColumnsWithData(segmentResults);
24
33
 
25
34
  if (_.isEmpty(rule) || _.isEmpty(segmentResults)) return null;
26
35
 
@@ -89,6 +98,7 @@ RuleResultSegments.propTypes = {
89
98
  ruleImplementation: PropTypes.object,
90
99
  rule: PropTypes.object,
91
100
  segmentResults: PropTypes.object,
101
+ customColumns: PropTypes.array,
92
102
  isAdmin: PropTypes.bool,
93
103
  };
94
104
 
@@ -96,6 +106,10 @@ const mapStateToProps = (state) => ({
96
106
  rule: state.rule,
97
107
  ruleImplementation: state.ruleImplementation,
98
108
  segmentResults: state.segmentResults,
109
+ customColumns: getCustomColumnsWithData(
110
+ state.segmentResults,
111
+ getSegmentResultsColumns(state)
112
+ ),
99
113
  isAdmin: state.authentication.role === "admin",
100
114
  });
101
115
 
@@ -4,7 +4,7 @@ import { Segment } from "semantic-ui-react";
4
4
  import {
5
5
  RULE_IMPLEMENTATION_RESULT_DETAILS,
6
6
  RULE_IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
7
- RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN,
7
+ RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN,
8
8
  } from "@truedat/core/routes";
9
9
 
10
10
  import RuleResult from "./RuleResult";
@@ -52,7 +52,7 @@ export const RuleResultsRoutes = ({}) => {
52
52
  />
53
53
  <Route
54
54
  exact
55
- path={RULE_IMPLEMENTATION_RESULT_DETAILS_REMEDIATION_PLAN}
55
+ path={RULE_IMPLEMENTATION_RESULT_REMEDIATION_PLAN}
56
56
  render={() => (
57
57
  <>
58
58
  <Segment>
@@ -1,5 +1,6 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
+ import { useParams } from "react-router-dom";
3
4
  import PropTypes from "prop-types";
4
5
  import { Route, useRouteMatch } from "react-router-dom";
5
6
  import { connect } from "react-redux";
@@ -51,6 +52,7 @@ import RuleImplementationsFromRuleLoader from "./RuleImplementationsFromRuleLoad
51
52
  import RuleLoader from "./RuleLoader";
52
53
  import RuleProperties from "./RuleProperties";
53
54
  import RuleSubscriptionLoader from "./RuleSubscriptionLoader";
55
+ import RuleResultRemediationLoader from "./RuleResultRemediationLoader";
54
56
 
55
57
  const DomainsLoader = React.lazy(() =>
56
58
  import("@truedat/bg/taxonomy/components/DomainsLoader")
@@ -227,6 +229,8 @@ const ImplementationRoutes = ({
227
229
  }) => {
228
230
  const latest = _.head(ruleImplementation.results);
229
231
  const authorized = useAuthorized();
232
+ const { rule_result_id: ruleResultId } = useParams();
233
+
230
234
  return (
231
235
  <>
232
236
  <Route
@@ -425,7 +429,15 @@ const ImplementationRoutes = ({
425
429
  />
426
430
  <Route
427
431
  path={RULE_IMPLEMENTATION_RESULT_DETAILS}
428
- render={() => <RuleResultsRoutes />}
432
+ render={() => (
433
+ <>
434
+ <RuleResultRemediationLoader
435
+ propRuleResultId={ruleResultId}
436
+ />
437
+ <TemplatesLoader scope="remediation" />
438
+ <RuleResultsRoutes />
439
+ </>
440
+ )}
429
441
  />
430
442
  </>
431
443
  )}
@@ -245,9 +245,6 @@ export const newRuleImplementationProps = {
245
245
  external_id:
246
246
  "/home/alberto/projects/connectors/td-connector-txt-files/data/########_D_PELAYO_POLIZAS_20201202033016_I.txt[Agencia]",
247
247
  id: 4814766,
248
- metadata: {
249
- order: "6",
250
- },
251
248
  name: "Agencia",
252
249
  path: ["Area_Vida", "########_D_PELAYO_POLIZAS_20201202033016_I.txt"],
253
250
  system: {
@@ -1,5 +1,4 @@
1
1
  import React, { Suspense } from "react";
2
- // import { shallow } from "enzyme";
3
2
  import { render } from "@truedat/test/render";
4
3
  import { NewRuleImplementation } from "../NewRuleImplementation";
5
4
  import { newRuleImplementationProps } from "../__test_samples__/NewRuleImplementationProps";
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import messages from "@truedat/dq/messages";
4
+ import { RuleImplementationResultTabs } from "../RuleImplementationResultTabs";
5
+
6
+ describe("<RuleImplementationResultTabs />", () => {
7
+ const renderOpts = {
8
+ messages: {
9
+ en: {
10
+ ...messages.en,
11
+ },
12
+ },
13
+ };
14
+
15
+ const props = {
16
+ rule: { id: 8 },
17
+ ruleImplementation: { id: 1 },
18
+ match: { path: "" },
19
+ canCreateLink: true,
20
+ ruleResult: { id: 2 },
21
+ authCreateRemediation: true,
22
+ };
23
+ it("matches the latest snapshot", () => {
24
+ const { container } = render(
25
+ <RuleImplementationResultTabs {...props} />,
26
+ renderOpts
27
+ );
28
+ expect(container).toMatchSnapshot();
29
+ });
30
+ });
@@ -0,0 +1,34 @@
1
+ import React, { Suspense } from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import messages from "@truedat/dq/messages";
4
+ import { RuleResult } from "../RuleResult";
5
+
6
+ jest.mock("react-router-dom", () => ({
7
+ ...jest.requireActual("react-router-dom"),
8
+ }));
9
+
10
+ const renderOpts = {
11
+ messages: {
12
+ en: {
13
+ ...messages.en,
14
+ },
15
+ },
16
+ };
17
+
18
+ describe("<RuleResult>", () => {
19
+ it("matches the latest snapshot", () => {
20
+ const props = {
21
+ ruleImplementation: {
22
+ results: [{ foo: "bar", id: 100, details: { Query: "ImZvbyI=" } }],
23
+ },
24
+ ruleResultId: 123,
25
+ };
26
+ const { container } = render(
27
+ <Suspense fallback={null}>
28
+ <RuleResult {...props} />
29
+ </Suspense>,
30
+ renderOpts
31
+ );
32
+ expect(container).toMatchSnapshot();
33
+ });
34
+ });
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { mount } from "enzyme";
3
+ import { RuleResultRemediationLoader } from "../RuleResultRemediationLoader";
4
+
5
+ jest.mock("react-router-dom", () => ({
6
+ ...jest.requireActual("react-router-dom"),
7
+ useParams: () => ({ propRuleResultId: 123 }),
8
+ }));
9
+
10
+ describe("<RuleResultRemediationLoader />", () => {
11
+ it("calls fetchRemediation when component mounts but not when it unmounts", () => {
12
+ const props = {
13
+ clearRemediation: jest.fn(),
14
+ fetchRemediation: jest.fn(),
15
+ propRuleResultId: 123,
16
+ };
17
+ const wrapper = mount(<RuleResultRemediationLoader {...props} />);
18
+ expect(props.fetchRemediation.mock.calls.length).toBe(1);
19
+ wrapper.unmount();
20
+ expect(props.fetchRemediation.mock.calls.length).toBe(1);
21
+ });
22
+
23
+ it("calls clearRemediation when component unmounts but not when it mounts", () => {
24
+ const props = {
25
+ clearRemediation: jest.fn(),
26
+ fetchRemediation: jest.fn(),
27
+ };
28
+ const wrapper = mount(<RuleResultRemediationLoader {...props} />);
29
+ expect(props.clearRemediation.mock.calls.length).toBe(0);
30
+ wrapper.unmount();
31
+ expect(props.clearRemediation.mock.calls.length).toBe(1);
32
+ });
33
+ });
@@ -0,0 +1,56 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import messages from "@truedat/dq/messages";
4
+ import { RuleResultRemediations } from "../RuleResultRemediations";
5
+
6
+ jest.mock("react-router-dom", () => ({
7
+ ...jest.requireActual("react-router-dom"),
8
+ useParams: () => ({ rule_result_id: 1 }),
9
+ }));
10
+
11
+ const renderOpts = {
12
+ messages: {
13
+ en: {
14
+ ...messages.en,
15
+ },
16
+ },
17
+ };
18
+
19
+ describe("<RuleResultRemediations />", () => {
20
+ it("renders remediation plan if has rule result", () => {
21
+ const props = {
22
+ ruleImplementation: {
23
+ id: 23,
24
+ minimum: 1,
25
+ goal: 10,
26
+ result_type: "percentage",
27
+ results: [
28
+ {
29
+ date: "2021-06-03T05:44:00Z",
30
+ details: {},
31
+ id: 65599,
32
+ implementation_id: 852,
33
+ result: "80.00",
34
+ result_type: "percentage",
35
+ },
36
+ {
37
+ date: "2021-06-03T05:44:00Z",
38
+ details: {},
39
+ id: 65600,
40
+ implementation_id: 852,
41
+ result: "80.00",
42
+ result_type: "percentage",
43
+ },
44
+ ],
45
+ },
46
+ ruleResultId: 65599,
47
+ };
48
+
49
+ const { container } = render(
50
+ <RuleResultRemediations {...props} />,
51
+ renderOpts
52
+ );
53
+
54
+ expect(container).toMatchSnapshot();
55
+ });
56
+ });
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { render } from "@truedat/test/render";
3
+ import { RuleResultsRoutes } from "../RuleResultsRoutes";
4
+
5
+ describe("<RuleResultsRoutes>", () => {
6
+ const props = {};
7
+
8
+ it("matches the latest snapshot", () => {
9
+ const { container } = render(<RuleResultsRoutes {...props} />, {});
10
+ expect(container).toMatchSnapshot();
11
+ });
12
+ });
@@ -0,0 +1,183 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { shallow } from "enzyme";
4
+ import { intl } from "@truedat/test/intl-stub";
5
+ import { RuleResultSegmentRow } from "../RuleResultSegmentRow";
6
+
7
+ // workaround for enzyme issue with React.useContext
8
+ // see https://github.com/airbnb/enzyme/issues/2176#issuecomment-532361526
9
+ jest.spyOn(React, "useContext").mockImplementation(() => intl);
10
+
11
+ describe("<RuleResultSegmentRow />", () => {
12
+ it("renders customColumns values when passed", () => {
13
+ const RuleResultSegmentRowProps = {
14
+ rule: { id: 10 },
15
+ segmentResult: {
16
+ id: 54,
17
+ result: 80,
18
+ params: { p1: "1", p2: "2", p3: "no", segment_name: "foo" },
19
+ },
20
+ customColumns: [
21
+ {
22
+ name: "m_parm1",
23
+ fieldSelector: _.path("params.p1"),
24
+ },
25
+ {
26
+ name: "m_parm2",
27
+ fieldSelector: _.path("params.p2"),
28
+ },
29
+ ],
30
+ date: "2019-08-12T02:00:00Z",
31
+ ruleImplementation: {
32
+ id: 23,
33
+ minimum: 1,
34
+ goal: 10,
35
+ result_type: "percentage",
36
+ },
37
+ };
38
+
39
+ const wrapper = shallow(
40
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
41
+ );
42
+
43
+ const customColumn = wrapper
44
+ .find("TableRow")
45
+ .find("TableCell")
46
+ .at(2)
47
+ .props().children;
48
+
49
+ expect(customColumn).toBe("1");
50
+
51
+ const customColumn2 = wrapper
52
+ .find("TableRow")
53
+ .find("TableCell")
54
+ .at(3)
55
+ .props().children;
56
+
57
+ expect(customColumn2).toBe("2");
58
+
59
+ expect(wrapper.find("TableRow").find("TableCell")).toHaveLength(4);
60
+ });
61
+
62
+ it("green icon when result is over goal", () => {
63
+ const RuleResultSegmentRowProps = {
64
+ rule: { id: 10 },
65
+ segmentResult: { id: 54, result: 80, params: { segment_name: "foo" } },
66
+
67
+ date: "2019-08-12T02:00:00Z",
68
+ ruleImplementation: {
69
+ id: 23,
70
+ minimum: 1,
71
+ goal: 10,
72
+ result_type: "percentage",
73
+ },
74
+ };
75
+
76
+ const wrapper = shallow(
77
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
78
+ );
79
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
80
+
81
+ expect(cell.find("td").find("Icon")).toHaveLength(1);
82
+
83
+ const icon = cell.find("td").find("Icon").at(0);
84
+
85
+ expect(icon.props().color).toBe("green");
86
+ });
87
+
88
+ it("red icon when result is under minimum", () => {
89
+ const RuleResultSegmentRowProps = {
90
+ rule: { id: 10 },
91
+ segmentResult: { id: 54, result: 80, params: { segment_name: "foo" } },
92
+ date: "2019-08-12T02:00:00Z",
93
+ ruleImplementation: {
94
+ id: 23,
95
+ minimum: 81,
96
+ goal: 90,
97
+ result_type: "percentage",
98
+ },
99
+ };
100
+
101
+ const wrapper = shallow(
102
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
103
+ );
104
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
105
+
106
+ expect(cell.find("td").find("Icon")).toHaveLength(1);
107
+
108
+ const icon = cell.find("td").find("Icon").at(0);
109
+
110
+ expect(icon.props().color).toBe("red");
111
+ });
112
+
113
+ it("yellow icon when result is between minimum and goal", () => {
114
+ const RuleResultSegmentRowProps = {
115
+ rule: { id: 10 },
116
+ ruleImplementation: {
117
+ id: 23,
118
+ minimum: 81,
119
+ goal: 90,
120
+ result_type: "percentage",
121
+ },
122
+ segmentResult: { id: 54, result: 82, params: { segment_name: "foo" } },
123
+ date: "2019-08-12T02:00:00Z",
124
+ };
125
+
126
+ const wrapper = shallow(
127
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
128
+ );
129
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
130
+
131
+ expect(cell.find("td").find("Icon")).toHaveLength(1);
132
+
133
+ const icon = cell.find("td").find("Icon").at(0);
134
+
135
+ expect(icon.props().color).toBe("yellow");
136
+ });
137
+
138
+ it("renders delete cell in RuleResultSegmentRow when user isAdmin is true", () => {
139
+ const RuleResultSegmentRowProps = {
140
+ rule: { id: 10 },
141
+ ruleImplementation: {
142
+ id: 23,
143
+ minimum: 81,
144
+ goal: 90,
145
+ result_type: "percentage",
146
+ },
147
+ segmentResult: { id: 54, result: 82, params: { segment_name: "foo" } },
148
+ date: "2019-08-12T02:00:00Z",
149
+ isAdmin: true,
150
+ deleteRuleResult: jest.fn(),
151
+ };
152
+ const wrapper = shallow(
153
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
154
+ );
155
+ const modal = wrapper
156
+ .find("TableRow")
157
+ .find("TableCell")
158
+ .at(2)
159
+ .find("ConfirmModal");
160
+ expect(modal).toHaveLength(1);
161
+ });
162
+
163
+ it("does not render delete cell in RuleResultSegmentRow when user isAdmin is false", () => {
164
+ const RuleResultSegmentRowProps = {
165
+ rule: { id: 10 },
166
+ ruleImplementation: {
167
+ id: 23,
168
+ minimum: 81,
169
+ goal: 90,
170
+ result_type: "percentage",
171
+ },
172
+ segmentResult: { id: 54, result: 82, params: { segment_name: "foo" } },
173
+ date: "2019-08-12T02:00:00Z",
174
+ isAdmin: false,
175
+ deleteRuleResult: jest.fn(),
176
+ };
177
+ const wrapper = shallow(
178
+ <RuleResultSegmentRow {...RuleResultSegmentRowProps} />
179
+ );
180
+ const modal = wrapper.find("ConfirmModal");
181
+ expect(modal).toHaveLength(0);
182
+ });
183
+ });
@@ -0,0 +1,68 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { shallow } from "enzyme";
4
+ import { intl } from "@truedat/test/intl-stub";
5
+ import {
6
+ RuleResultSegments,
7
+ getCustomColumnsWithData,
8
+ } from "../RuleResultSegments";
9
+
10
+ // workaround for enzyme issue with React.useContext
11
+ // see https://github.com/airbnb/enzyme/issues/2176#issuecomment-532361526
12
+ jest.spyOn(React, "useContext").mockImplementation(() => intl);
13
+
14
+ describe("<RuleResultSegments />", () => {
15
+ const rule = {
16
+ result_type: "percentage",
17
+ minimum: 50,
18
+ goal: 100,
19
+ };
20
+ const ruleImplementation = {
21
+ results: [{ foo: "bar" }],
22
+ };
23
+ const customColumns = [{ foo: "bar", name: "foo" }];
24
+ const props = {
25
+ rule,
26
+ ruleImplementation,
27
+ customColumns,
28
+ };
29
+
30
+ it("matches the latest snapshot", () => {
31
+ const wrapper = shallow(<RuleResultSegments {...props} />);
32
+ expect(wrapper).toMatchSnapshot();
33
+ });
34
+ });
35
+
36
+ describe("getCustomColumnsWithData", () => {
37
+ it("filters custom columns discarding the ones not present in rule results params", () => {
38
+ const segmentResults = [
39
+ { result: 80, params: { p1: "1", p2: "2" } },
40
+ { result: 45 },
41
+ { result: 20, params: { p1: "2" } },
42
+ ];
43
+ const customColumns = [
44
+ {
45
+ name: "p1",
46
+ fieldSelector: _.path("params.p1"),
47
+ },
48
+ {
49
+ name: "p2",
50
+ fieldSelector: _.path("params.p2"),
51
+ },
52
+ {
53
+ name: "p3",
54
+ fieldSelector: _.path("params.p3"),
55
+ },
56
+ ];
57
+
58
+ const filteredColumns = getCustomColumnsWithData(
59
+ segmentResults,
60
+ customColumns
61
+ );
62
+ const columnNames = _.map("name")(filteredColumns);
63
+
64
+ expect(_.includes("p1")(columnNames)).toBe(true);
65
+ expect(_.includes("p2")(columnNames)).toBe(true);
66
+ expect(_.includes("p3")(columnNames)).toBe(false);
67
+ });
68
+ });
@@ -104,23 +104,6 @@ exports[`<NewRuleImplementation /> calculate aliases when not informed 1`] = `
104
104
  </div>
105
105
  </div>
106
106
  </div>
107
- <div
108
- class="step"
109
- >
110
- <i
111
- aria-hidden="true"
112
- class="grid layout icon"
113
- />
114
- <div
115
- class="content"
116
- >
117
- <div
118
- class="title"
119
- >
120
- Segments
121
- </div>
122
- </div>
123
- </div>
124
107
  </div>
125
108
  <div
126
109
  class="ui fitted hidden divider"
@@ -886,23 +869,6 @@ exports[`<NewRuleImplementation /> matches the latest snapshot 1`] = `
886
869
  </div>
887
870
  </div>
888
871
  </div>
889
- <div
890
- class="step"
891
- >
892
- <i
893
- aria-hidden="true"
894
- class="grid layout icon"
895
- />
896
- <div
897
- class="content"
898
- >
899
- <div
900
- class="title"
901
- >
902
- Segments
903
- </div>
904
- </div>
905
- </div>
906
872
  </div>
907
873
  <div
908
874
  class="ui fitted hidden divider"
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleImplementationResultTabs /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="ui pointing secondary top attached tabular menu"
7
+ >
8
+ <a
9
+ class="item"
10
+ href="/rules/8/implementations/1/results/2"
11
+ >
12
+ Information
13
+ </a>
14
+ </div>
15
+ </div>
16
+ `;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleResult> matches the latest snapshot 1`] = `<div />`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleResultRemediations /> renders remediation plan if has rule result 1`] = `<div />`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleResultsRoutes> matches the latest snapshot 1`] = `<div />`;
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleResultSegments /> matches the latest snapshot 1`] = `""`;
@@ -46,6 +46,7 @@ export const RuleImplementationForm = ({
46
46
  setImplementationKey,
47
47
  setValidations,
48
48
  addSegments,
49
+ authManageSegments,
49
50
  setSegments,
50
51
  isSubmitting,
51
52
  handleSubmit,
@@ -60,12 +61,14 @@ export const RuleImplementationForm = ({
60
61
  { name: "dataset", icon: "database", isValid: () => validDataSet() },
61
62
  { name: "populations", icon: "user", isValid: () => validPopulations() },
62
63
  { name: "validations", icon: "setting", isValid: () => validValidations() },
63
- {
64
+ ];
65
+
66
+ if (authManageSegments)
67
+ steps.push({
64
68
  name: "segments",
65
69
  icon: "grid layout",
66
70
  isValid: () => validSegments(),
67
- },
68
- ];
71
+ });
69
72
 
70
73
  const [activeStep, setActiveStep] = useState("information");
71
74
  const [selector, setSelector] = useState();
@@ -336,6 +339,8 @@ export const RuleImplementationForm = ({
336
339
  };
337
340
 
338
341
  RuleImplementationForm.propTypes = {
342
+ isAdmin: PropTypes.bool,
343
+ userRulesPermissions: PropTypes.object,
339
344
  ruleImplementation: PropTypes.object,
340
345
  handleSubmit: PropTypes.func.isRequired,
341
346
  isSubmitting: PropTypes.bool.isRequired,
@@ -346,6 +351,7 @@ RuleImplementationForm.propTypes = {
346
351
  setValidations: PropTypes.func,
347
352
  setSegments: PropTypes.func,
348
353
  addSegments: PropTypes.func,
354
+ authManageSegments: PropTypes.bool,
349
355
  operators: PropTypes.object,
350
356
  onChange: PropTypes.func,
351
357
  template: PropTypes.object,
@@ -354,6 +360,10 @@ RuleImplementationForm.propTypes = {
354
360
  const mapStateToProps = (state) => ({
355
361
  isSubmitting: state.ruleImplementationCreating,
356
362
  template: state.template,
363
+ authManageSegments: _.pathOr(
364
+ false,
365
+ "manage_segments"
366
+ )(state.userRulesPermissions),
357
367
  });
358
368
 
359
369
  export default connect(mapStateToProps)(RuleImplementationForm);
@@ -79,23 +79,6 @@ exports[`<RuleImplementationForm /> matches the latest snapshot 1`] = `
79
79
  </div>
80
80
  </div>
81
81
  </div>
82
- <div
83
- class="step"
84
- >
85
- <i
86
- aria-hidden="true"
87
- class="grid layout icon"
88
- />
89
- <div
90
- class="content"
91
- >
92
- <div
93
- class="title"
94
- >
95
- ruleImplementationForm.step.segments
96
- </div>
97
- </div>
98
- </div>
99
82
  </div>
100
83
  <div
101
84
  class="ui fitted hidden divider"
@@ -0,0 +1,9 @@
1
+ import _ from "lodash/fp";
2
+ import { createSelector } from "reselect";
3
+
4
+ export const defaultSegmentResultsColumns = [];
5
+
6
+ export const getSegmentResultsColumns = createSelector(
7
+ _.prop("segmentResultsColumns"),
8
+ _.defaultTo(defaultSegmentResultsColumns)
9
+ );
@@ -27,5 +27,6 @@ export { getRuleImplementationAvailableFilters } from "./getRuleImplementationAv
27
27
  export { getRuleImplementationSelectedFilters } from "./getRuleImplementationSelectedFilters";
28
28
  export { getRuleImplementationSelectedFilterValues } from "./getRuleImplementationSelectedFilterValues";
29
29
  export { getRuleImplementationSelectedFilterActiveValues } from "./getRuleImplementationSelectedFilterActiveValues";
30
+ export { getSegmentResultsColumns } from "./getSegmentResultsColumns";
30
31
  export * from "./getImplementationStructures";
31
32
  export { getParsedEvents } from "./getParsedEvents";