@truedat/dq 4.43.3 → 4.43.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/CHANGELOG.md +6 -0
- package/package.json +5 -5
- package/src/components/NewRuleImplementation.js +2 -3
- package/src/components/RuleImplementation.js +102 -86
- package/src/components/RuleImplementationResultTabs.js +3 -13
- package/src/components/RuleResult.js +1 -54
- package/src/components/RuleResultRemediations.js +0 -7
- package/src/components/RuleResultRow.js +10 -0
- package/src/components/RuleResultSegmentRow.js +2 -2
- package/src/components/RuleResultSegments.js +16 -2
- package/src/components/RuleResultsRoutes.js +2 -2
- package/src/components/RuleRoutes.js +13 -1
- package/src/components/__test_samples__/NewRuleImplementationProps.js +0 -3
- package/src/components/__tests__/NewRuleImplementation.spec.js +0 -1
- package/src/components/__tests__/RuleImplementationResultTabs.spec.js +30 -0
- package/src/components/__tests__/RuleResult.spec.js +34 -0
- package/src/components/__tests__/{ExecutionDetails.spec.js → RuleResultDetails.spec.js} +0 -0
- package/src/components/__tests__/RuleResultRemediationLoader.spec.js +33 -0
- package/src/components/__tests__/RuleResultRemediations.spec.js +56 -0
- package/src/components/__tests__/RuleResultRoutes.spec.js +12 -0
- package/src/components/__tests__/RuleResultSegmentRow.spec.js +183 -0
- package/src/components/__tests__/RuleResultSegments.spec.js +68 -0
- package/src/components/__tests__/__snapshots__/NewRuleImplementation.spec.js.snap +0 -34
- package/src/components/__tests__/__snapshots__/RuleImplementationResultTabs.spec.js.snap +16 -0
- package/src/components/__tests__/__snapshots__/RuleResult.spec.js.snap +3 -0
- package/src/components/__tests__/__snapshots__/{ExecutionDetails.spec.js.snap → RuleResultDetails.spec.js.snap} +0 -0
- package/src/components/__tests__/__snapshots__/RuleResultRemediations.spec.js.snap +3 -0
- package/src/components/__tests__/__snapshots__/RuleResultRoutes.spec.js.snap +3 -0
- package/src/components/__tests__/__snapshots__/RuleResultSegments.spec.js.snap +3 -0
- package/src/components/ruleImplementationForm/RuleImplementationForm.js +13 -3
- package/src/components/ruleImplementationForm/__tests__/__snapshots__/RuleImplementationForm.spec.js.snap +0 -17
- package/src/selectors/getSegmentResultsColumns.js +9 -0
- package/src/selectors/index.js +1 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dq",
|
|
3
|
-
"version": "4.43.
|
|
3
|
+
"version": "4.43.6",
|
|
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.
|
|
34
|
+
"@truedat/test": "4.43.6",
|
|
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.
|
|
86
|
-
"@truedat/df": "4.43.
|
|
85
|
+
"@truedat/core": "4.43.6",
|
|
86
|
+
"@truedat/df": "4.43.6",
|
|
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": "
|
|
106
|
+
"gitHead": "848143d79486499dd9deadea6a545929b5cdb39c"
|
|
107
107
|
}
|
|
@@ -224,8 +224,8 @@ const updateStructureParentIndex = (structures) => (structure) => {
|
|
|
224
224
|
return structure;
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
-
const withConditionDefaultAlias = (conditions, structures) =>
|
|
228
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
const optionalColumns =
|
|
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
|
-
|
|
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={
|
|
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={() =>
|
|
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
|
+
});
|
|
File without changes
|
|
@@ -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
|
+
`;
|
|
File without changes
|
|
@@ -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
|
+
);
|
package/src/selectors/index.js
CHANGED
|
@@ -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";
|