@truedat/dq 4.38.8 → 4.40.1

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 (24) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +5 -5
  3. package/src/components/ExecutionDetails.js +18 -22
  4. package/src/components/ImplementationsRoutes.js +0 -4
  5. package/src/components/RuleImplementationResults.js +10 -10
  6. package/src/components/RuleImplementationsActions.js +16 -8
  7. package/src/components/RuleImplementationsOptions.js +3 -2
  8. package/src/components/RuleResultRow.js +25 -33
  9. package/src/components/RuleResultsUpload.js +10 -28
  10. package/src/components/__tests__/RuleImplementationsActions.spec.js +2 -0
  11. package/src/components/__tests__/RuleImplementationsOptions.spec.js +16 -12
  12. package/src/components/__tests__/RuleResultRow.spec.js +30 -16
  13. package/src/components/__tests__/RuleResultsUpload.spec.js +8 -66
  14. package/src/components/__tests__/__snapshots__/ExecutionDetails.spec.js.snap +73 -11
  15. package/src/components/__tests__/__snapshots__/RuleImplementationResults.spec.js.snap +2 -3
  16. package/src/components/__tests__/__snapshots__/RuleImplementationsActions.spec.js.snap +1 -0
  17. package/src/components/__tests__/__snapshots__/RuleImplementationsOptions.spec.js.snap +63 -54
  18. package/src/components/__tests__/__snapshots__/RuleResultsUpload.spec.js.snap +16 -8
  19. package/src/reducers/__tests__/implementationsActions.spec.js +50 -0
  20. package/src/reducers/__tests__/{uploadingResults.spec.js → resultsUploading.spec.js} +5 -5
  21. package/src/reducers/implementationsActions.js +24 -0
  22. package/src/reducers/index.js +12 -10
  23. package/src/reducers/{uploadingResults.js → resultsUploading.js} +2 -2
  24. package/src/styles/ruleResultRow.less +3 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.40.1] 2022-03-08
4
+
5
+ ### Changed
6
+
7
+ - [TD-4604] Use actions from `POST /api/implementations/search` instead of
8
+ fetching permissions
9
+
10
+ ## [4.39.0] 2022-03-01
11
+
12
+ ### Changed
13
+
14
+ - [TD-4528] Restyle quality results table
15
+
3
16
  ## [4.38.7] 2022-02-22
4
17
 
5
18
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.38.8",
3
+ "version": "4.40.1",
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.38.8",
34
+ "@truedat/test": "4.39.0",
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.38.8",
86
- "@truedat/df": "4.38.8",
85
+ "@truedat/core": "4.40.1",
86
+ "@truedat/df": "4.40.1",
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": "66f56f3fe3e1b5eb0b18379f2d141b1433bf82c1"
106
+ "gitHead": "58b35b48833fbed1602d1070853e6c805bcc48f1"
107
107
  }
@@ -91,14 +91,7 @@ export const ExecutionDetails = ({ ruleImplementation, ruleResultId }) => {
91
91
  ruleImplementation.results
92
92
  );
93
93
 
94
- return _.isEmpty(ruleResult.details) ? (
95
- <Message
96
- style={{ marginTop: "14px" }}
97
- header={formatMessage({
98
- id: "rule.ruleImplementation.results.details.empty",
99
- })}
100
- />
101
- ) : (
94
+ return (
102
95
  <Table className="implementation-results medium">
103
96
  <Table.Header>
104
97
  <Table.Row>
@@ -116,20 +109,23 @@ export const ExecutionDetails = ({ ruleImplementation, ruleResultId }) => {
116
109
  ruleResult={ruleResult}
117
110
  />
118
111
  </Table.Body>
119
-
120
- <Table.Header>
121
- <Table.Row>
122
- <Table.HeaderCell
123
- colSpan={2}
124
- content={formatMessage({
125
- id: "ruleResult.props.header.details",
126
- })}
127
- />
128
- </Table.Row>
129
- </Table.Header>
130
- <Table.Body>
131
- <DetailRow details={ruleResult.details} />
132
- </Table.Body>
112
+ {!_.isEmpty(ruleResult.details) ? (
113
+ <>
114
+ <Table.Header>
115
+ <Table.Row>
116
+ <Table.HeaderCell
117
+ colSpan={2}
118
+ content={formatMessage({
119
+ id: "ruleResult.props.header.details",
120
+ })}
121
+ />
122
+ </Table.Row>
123
+ </Table.Header>
124
+ <Table.Body>
125
+ <DetailRow details={ruleResult?.details} />
126
+ </Table.Body>
127
+ </>
128
+ ) : null}
133
129
  </Table>
134
130
  );
135
131
  };
@@ -8,9 +8,6 @@ import RuleImplementationsLoader from "./RuleImplementationsLoader";
8
8
  const TemplatesLoader = React.lazy(() =>
9
9
  import("@truedat/df/templates/components/TemplatesLoader")
10
10
  );
11
- const UserDomainsLoader = React.lazy(() =>
12
- import("@truedat/auth/users/components/UserDomainsLoader")
13
- );
14
11
  const UserSearchFiltersLoader = React.lazy(() =>
15
12
  import("@truedat/dd/components/UserSearchFiltersLoader")
16
13
  );
@@ -24,7 +21,6 @@ const ImplementationsRoutes = () => (
24
21
  <ImplementationFiltersLoader />
25
22
  <TemplatesLoader scope="qe" />
26
23
  <UserSearchFiltersLoader scope="rule_implementation" />
27
- <UserDomainsLoader permissions={["manage_rule_results"]} />
28
24
  <RuleImplementations exact />
29
25
  </>
30
26
  )}
@@ -35,7 +35,7 @@ export const RuleImplementationResults = ({
35
35
 
36
36
  return (
37
37
  <>
38
- {ruleImplementation.event_type === "FAILED" && (
38
+ {ruleImplementation.event_type === "FAILED" ? (
39
39
  <Message negative style={{ marginTop: "14px" }}>
40
40
  <Message.Header>
41
41
  {formatMessage({
@@ -61,7 +61,7 @@ export const RuleImplementationResults = ({
61
61
  {ruleImplementation.event_message}
62
62
  </p>
63
63
  </Message>
64
- )}
64
+ ) : null}
65
65
  {_.isEmpty(ruleResults) ? (
66
66
  <Message
67
67
  style={{ marginTop: "14px" }}
@@ -70,25 +70,25 @@ export const RuleImplementationResults = ({
70
70
  })}
71
71
  />
72
72
  ) : (
73
- <Table className="implementation-results small" selectable>
73
+ <Table className="implementation-results small">
74
74
  <Table.Header>
75
75
  <Table.Row>
76
76
  <Table.HeaderCell
77
- content={formatMessage({ id: "ruleResult.props.quality" })}
77
+ content={formatMessage({ id: "ruleResult.props.date" })}
78
78
  />
79
79
  <Table.HeaderCell
80
- content={formatMessage({ id: "ruleResult.props.date" })}
80
+ content={formatMessage({ id: "ruleResult.props.quality" })}
81
81
  />
82
- {_.includes("records")(optionalColumns) && (
82
+ {_.includes("records")(optionalColumns) ? (
83
83
  <Table.HeaderCell
84
84
  content={formatMessage({ id: "ruleResult.props.records" })}
85
85
  />
86
- )}
87
- {_.includes("errors")(optionalColumns) && (
86
+ ) : null}
87
+ {_.includes("errors")(optionalColumns) ? (
88
88
  <Table.HeaderCell
89
89
  content={formatMessage({ id: "ruleResult.props.errors" })}
90
90
  />
91
- )}
91
+ ) : null}
92
92
  {customColumns.map((column, index) => (
93
93
  <Table.HeaderCell
94
94
  key={index}
@@ -99,7 +99,7 @@ export const RuleImplementationResults = ({
99
99
  />
100
100
  ))}
101
101
  {<Table.HeaderCell />}
102
- {isAdmin && <Table.HeaderCell />}
102
+ {isAdmin ? <Table.HeaderCell /> : null}
103
103
  </Table.Row>
104
104
  </Table.Header>
105
105
  <Table.Body>
@@ -14,18 +14,19 @@ import ExecutionPopup from "./ExecutionPopup";
14
14
  import RuleImplementationsOptions from "./RuleImplementationsOptions";
15
15
 
16
16
  export const RuleImplementationsActions = ({
17
+ actions,
17
18
  addImplementationFilter,
18
19
  canExecute,
19
- executeImplementationsOn,
20
- toggleImplementationFilterValue,
21
- removeImplementationFilter,
22
20
  createExecutionGroup,
23
- selectedImplementations,
21
+ executeImplementationsOn,
24
22
  implementationQuery,
25
- setMode,
26
23
  implementationsExecution,
24
+ removeImplementationFilter,
27
25
  ruleImplementationCount,
28
26
  ruleImplementationsLoading,
27
+ selectedImplementations,
28
+ setMode,
29
+ toggleImplementationFilterValue,
29
30
  }) => {
30
31
  const showExecutableInfo = () => {
31
32
  addImplementationFilter({ filter: "executable" });
@@ -50,6 +51,8 @@ export const RuleImplementationsActions = ({
50
51
  : { filters: { id: selectedImplementations } };
51
52
  createExecutionGroup({ ...query, df_content });
52
53
  };
54
+
55
+ const canUploadResults = !!actions?.uploadResults;
53
56
  return (
54
57
  <div style={{ float: "right" }}>
55
58
  {implementationsExecution && canExecute && (
@@ -73,24 +76,28 @@ export const RuleImplementationsActions = ({
73
76
  />
74
77
  </>
75
78
  )}
76
- <RuleImplementationsOptions loading={ruleImplementationsLoading} />
79
+ <RuleImplementationsOptions
80
+ loading={ruleImplementationsLoading}
81
+ canUploadResults={canUploadResults}
82
+ />
77
83
  </div>
78
84
  );
79
85
  };
80
86
 
81
87
  RuleImplementationsActions.propTypes = {
88
+ actions: PropTypes.object,
82
89
  addImplementationFilter: PropTypes.func,
83
90
  canExecute: PropTypes.bool,
84
91
  createExecutionGroup: PropTypes.func,
85
92
  executeImplementationsOn: PropTypes.bool,
86
- implementationsExecution: PropTypes.bool,
87
93
  implementationQuery: PropTypes.object,
94
+ implementationsExecution: PropTypes.bool,
88
95
  removeImplementationFilter: PropTypes.func,
89
96
  ruleImplementationCount: PropTypes.number,
97
+ ruleImplementationsLoading: PropTypes.bool,
90
98
  selectedImplementations: PropTypes.array,
91
99
  setMode: PropTypes.func,
92
100
  toggleImplementationFilterValue: PropTypes.func,
93
- ruleImplementationsLoading: PropTypes.bool,
94
101
  };
95
102
 
96
103
  const mapStateToProps = (state) => ({
@@ -98,6 +105,7 @@ const mapStateToProps = (state) => ({
98
105
  ruleImplementationCount: state.ruleImplementationCount,
99
106
  implementationsExecution: getImplementationsExecution(state),
100
107
  ruleImplementationsLoading: state.ruleImplementationsLoading,
108
+ actions: state.implementationsActions,
101
109
  });
102
110
 
103
111
  export default connect(mapStateToProps, {
@@ -5,7 +5,7 @@ import RuleImplementationsDownload from "./RuleImplementationsDownload";
5
5
  import RuleResultsUpload from "./RuleResultsUpload";
6
6
  import ImplementationsUploadButton from "./ImplementationsUploadButton";
7
7
 
8
- export const RuleImplementationsOptions = ({ loading }) => (
8
+ export const RuleImplementationsOptions = ({ loading, canUploadResults }) => (
9
9
  <Dropdown
10
10
  icon="ellipsis vertical"
11
11
  className="button icon group-actions button-update"
@@ -16,13 +16,14 @@ export const RuleImplementationsOptions = ({ loading }) => (
16
16
  <Dropdown.Menu>
17
17
  <RuleImplementationsDownload />
18
18
  <ImplementationsUploadButton />
19
- <RuleResultsUpload />
19
+ {canUploadResults ? <RuleResultsUpload /> : null}
20
20
  </Dropdown.Menu>
21
21
  </Dropdown>
22
22
  );
23
23
 
24
24
  RuleImplementationsOptions.propTypes = {
25
25
  loading: PropTypes.bool,
26
+ canUploadResults: PropTypes.bool,
26
27
  };
27
28
 
28
29
  export default RuleImplementationsOptions;
@@ -3,13 +3,14 @@ import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { useIntl } from "react-intl";
6
- import { useHistory } from "react-router-dom";
6
+ import { Link } from "react-router-dom";
7
7
  import { Table, Icon } from "semantic-ui-react";
8
8
  import { ConfirmModal, DateTime } from "@truedat/core/components";
9
9
  import { columnDecorator } from "@truedat/core/services";
10
10
  import { linkTo } from "@truedat/core/routes";
11
11
  import { selectColor } from "../functions/selectors";
12
12
  import { deleteRuleResult } from "../routines";
13
+ import "../styles/ruleResultRow.less";
13
14
 
14
15
  export const RuleResultRow = ({
15
16
  deleteRuleResult,
@@ -22,24 +23,25 @@ export const RuleResultRow = ({
22
23
  }) => {
23
24
  const { formatMessage, formatNumber: _formatNumber } = useIntl();
24
25
  const formatNumber = (num) => (_.isNil(num) ? num : _formatNumber(num));
25
- const history = useHistory();
26
26
 
27
27
  return (
28
- <Table.Row
29
- onClick={() =>
30
- !_.isEmpty(ruleResult.details)
31
- ? history.push(
32
- linkTo.RULE_IMPLEMENTATION_RESULT_DETAILS({
33
- id: rule.id,
34
- implementation_id: ruleImplementation.id,
35
- rule,
36
- ruleImplementation,
37
- rule_result_id: ruleResult.id,
38
- })
39
- )
40
- : null
41
- }
42
- >
28
+ <Table.Row>
29
+ <Table.Cell>
30
+ <Link
31
+ to={linkTo.RULE_IMPLEMENTATION_RESULT_DETAILS({
32
+ id: rule.id,
33
+ implementation_id: ruleImplementation.id,
34
+ rule,
35
+ ruleImplementation,
36
+ rule_result_id: ruleResult.id,
37
+ })}
38
+ >
39
+ <DateTime className="rule-result-date" value={ruleResult.date} />
40
+ {!_.isEmpty(ruleResult.details) ? (
41
+ <Icon name="info circle" color="blue" />
42
+ ) : null}
43
+ </Link>
44
+ </Table.Cell>
43
45
  <Table.Cell>
44
46
  <Icon
45
47
  name="circle"
@@ -47,28 +49,18 @@ export const RuleResultRow = ({
47
49
  />
48
50
  {`${parseFloat(ruleResult.result)} %`}
49
51
  </Table.Cell>
50
- <Table.Cell>
51
- <DateTime value={ruleResult.date} />
52
- </Table.Cell>
53
- {_.includes("records")(optionalColumns) && (
52
+ {_.includes("records")(optionalColumns) ? (
54
53
  <Table.Cell>{formatNumber(ruleResult.records)}</Table.Cell>
55
- )}
56
- {_.includes("errors")(optionalColumns) && (
54
+ ) : null}
55
+ {_.includes("errors")(optionalColumns) ? (
57
56
  <Table.Cell>{formatNumber(ruleResult.errors)}</Table.Cell>
58
- )}
59
- {_.includes("details")(optionalColumns) && (
60
- <Table.Cell>
61
- {!_.isEmpty(ruleResult.details) && (
62
- <Icon name="info circle" color="blue" />
63
- )}
64
- </Table.Cell>
65
- )}
57
+ ) : null}
66
58
  {customColumns?.map((column, index) => (
67
59
  <Table.Cell key={index}>
68
60
  {columnDecorator(column)(ruleResult)}
69
61
  </Table.Cell>
70
62
  ))}
71
- {isAdmin && (
63
+ {isAdmin ? (
72
64
  <Table.Cell onClick={(e) => e.stopPropagation()}>
73
65
  <ConfirmModal
74
66
  icon="trash"
@@ -88,7 +80,7 @@ export const RuleResultRow = ({
88
80
  }
89
81
  />
90
82
  </Table.Cell>
91
- )}
83
+ ) : null}
92
84
  </Table.Row>
93
85
  );
94
86
  };
@@ -1,5 +1,4 @@
1
- import _ from "lodash/fp";
2
- import React, { useEffect, useState } from "react";
1
+ import React from "react";
3
2
  import PropTypes from "prop-types";
4
3
  import { connect } from "react-redux";
5
4
  import { Dropdown } from "semantic-ui-react";
@@ -7,31 +6,18 @@ import { useIntl } from "react-intl";
7
6
  import { UploadModal } from "@truedat/core/components";
8
7
  import { uploadResults } from "../routines";
9
8
 
10
- export const RuleResultsUpload = ({ uploadResults, loading, userDomains }) => {
9
+ export const RuleResultsUpload = ({ uploadResults, loading }) => {
11
10
  const { formatMessage } = useIntl();
12
- const [permissionFound, setPermissionFound] = useState(false);
13
-
14
- useEffect(() => {
15
- setPermissionFound(
16
- !_.flow(
17
- _.find({ permission: "manage_rule_results" }),
18
- _.path("domains"),
19
- _.isEmpty
20
- )(userDomains)
21
- );
22
- }, [userDomains]);
23
11
 
24
12
  return (
25
13
  <UploadModal
26
14
  icon="upload"
27
15
  trigger={
28
- permissionFound ? (
29
- <Dropdown.Item
30
- icon="upload"
31
- text={formatMessage({ id: "ruleResults.actions.upload.tooltip" })}
32
- disabled={loading}
33
- />
34
- ) : null
16
+ <Dropdown.Item
17
+ icon="upload"
18
+ text={formatMessage({ id: "ruleResults.actions.upload.tooltip" })}
19
+ disabled={loading}
20
+ />
35
21
  }
36
22
  header={formatMessage({
37
23
  id: "ruleResults.actions.upload.confirmation.header",
@@ -52,14 +38,10 @@ export const RuleResultsUpload = ({ uploadResults, loading, userDomains }) => {
52
38
  RuleResultsUpload.propTypes = {
53
39
  uploadResults: PropTypes.func,
54
40
  loading: PropTypes.bool,
55
- userDomains: PropTypes.array,
56
41
  };
57
42
 
58
- const mapStateToProps = (state) => {
59
- return {
60
- loading: state.uploadingResults,
61
- userDomains: state.userDomains,
62
- };
63
- };
43
+ const mapStateToProps = ({ resultsUploading: loading }) => ({
44
+ loading,
45
+ });
64
46
 
65
47
  export default connect(mapStateToProps, { uploadResults })(RuleResultsUpload);
@@ -12,6 +12,7 @@ describe("<RuleImplementationsActions />", () => {
12
12
  const setMode = jest.fn();
13
13
  const removeImplementationFilter = jest.fn();
14
14
  const props = {
15
+ actions: { uploadResults: {} },
15
16
  canExecute: true,
16
17
  executionGroupLoading: false,
17
18
  ruleImplementationCount: 12,
@@ -23,6 +24,7 @@ describe("<RuleImplementationsActions />", () => {
23
24
  setMode,
24
25
  removeImplementationFilter,
25
26
  };
27
+
26
28
  it("matches the latest snapshot", () => {
27
29
  const wrapper = shallow(<RuleImplementationsActions {...props} />);
28
30
  expect(wrapper).toMatchSnapshot();
@@ -1,18 +1,22 @@
1
- import _ from "lodash/fp";
2
1
  import React from "react";
3
- import { shallow } from "enzyme";
4
- import { intl } from "@truedat/test/intl-stub";
5
- import { RuleImplementationsOptions } from "../RuleImplementationsOptions";
2
+ import { waitFor } from "@testing-library/react";
3
+ import { render } from "@truedat/test/render";
4
+ import coreEn from "@truedat/core/messages/en";
5
+ import RuleImplementationsOptions from "../RuleImplementationsOptions";
6
+ import en from "../../messages/en";
6
7
 
7
- jest.spyOn(React, "useContext").mockImplementation(() => intl);
8
+ const renderOpts = {
9
+ messages: { en: { ...coreEn, ...en } },
10
+ };
11
+
12
+ const props = { canUploadResults: true };
8
13
 
9
14
  describe("<RuleImplementationsOptions />", () => {
10
- it("matches the latest snapshot", () => {
11
- const wrapper = shallow(<RuleImplementationsOptions />);
12
- expect(wrapper).toMatchSnapshot();
13
- });
14
- it("matches the latest loading snapshot", () => {
15
- const wrapper = shallow(<RuleImplementationsOptions loading />);
16
- expect(wrapper).toMatchSnapshot();
15
+ it("matches the latest snapshot", async () => {
16
+ const { container } = render(
17
+ <RuleImplementationsOptions {...props} />,
18
+ renderOpts
19
+ );
20
+ await waitFor(() => expect(container).toMatchSnapshot());
17
21
  });
18
22
  });
@@ -11,8 +11,12 @@ jest.spyOn(React, "useContext").mockImplementation(() => intl);
11
11
  describe("<RuleResultRow />", () => {
12
12
  it("renders customColumns values when passed", () => {
13
13
  const ruleResultRowProps = {
14
- id: 10,
15
- ruleResult: { result: 80, params: { p1: "1", p2: "2", p3: "no" } },
14
+ rule: { id: 10 },
15
+ ruleResult: {
16
+ id: 54,
17
+ result: 80,
18
+ params: { p1: "1", p2: "2", p3: "no" },
19
+ },
16
20
  customColumns: [
17
21
  {
18
22
  name: "m_parm1",
@@ -24,7 +28,12 @@ describe("<RuleResultRow />", () => {
24
28
  },
25
29
  ],
26
30
  date: "2019-08-12T02:00:00Z",
27
- ruleImplementation: { minimum: 1, goal: 10, result_type: "percentage" },
31
+ ruleImplementation: {
32
+ id: 23,
33
+ minimum: 1,
34
+ goal: 10,
35
+ result_type: "percentage",
36
+ },
28
37
  };
29
38
 
30
39
  const wrapper = shallow(<RuleResultRow {...ruleResultRowProps} />);
@@ -50,10 +59,11 @@ describe("<RuleResultRow />", () => {
50
59
 
51
60
  it("green icon when result is over goal", () => {
52
61
  const ruleResultRowProps = {
53
- id: 10,
54
- ruleResult: { result: 80 },
62
+ rule: { id: 10 },
63
+ ruleResult: { id: 54, result: 80 },
55
64
  date: "2019-08-12T02:00:00Z",
56
65
  ruleImplementation: {
66
+ id: 23,
57
67
  minimum: 1,
58
68
  goal: 10,
59
69
  result_type: "percentage",
@@ -61,7 +71,7 @@ describe("<RuleResultRow />", () => {
61
71
  };
62
72
 
63
73
  const wrapper = shallow(<RuleResultRow {...ruleResultRowProps} />);
64
- const cell = wrapper.find("TableRow").find("TableCell").at(0).dive();
74
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
65
75
 
66
76
  expect(cell.find("td").find("Icon")).toHaveLength(1);
67
77
 
@@ -72,10 +82,11 @@ describe("<RuleResultRow />", () => {
72
82
 
73
83
  it("red icon when result is under minimum", () => {
74
84
  const ruleResultRowProps = {
75
- id: 10,
76
- ruleResult: { result: 80 },
85
+ rule: { id: 10 },
86
+ ruleResult: { id: 54, result: 80 },
77
87
  date: "2019-08-12T02:00:00Z",
78
88
  ruleImplementation: {
89
+ id: 23,
79
90
  minimum: 81,
80
91
  goal: 90,
81
92
  result_type: "percentage",
@@ -83,7 +94,7 @@ describe("<RuleResultRow />", () => {
83
94
  };
84
95
 
85
96
  const wrapper = shallow(<RuleResultRow {...ruleResultRowProps} />);
86
- const cell = wrapper.find("TableRow").find("TableCell").at(0).dive();
97
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
87
98
 
88
99
  expect(cell.find("td").find("Icon")).toHaveLength(1);
89
100
 
@@ -94,18 +105,19 @@ describe("<RuleResultRow />", () => {
94
105
 
95
106
  it("yellow icon when result is between minimum and goal", () => {
96
107
  const ruleResultRowProps = {
97
- id: 10,
108
+ rule: { id: 10 },
98
109
  ruleImplementation: {
110
+ id: 23,
99
111
  minimum: 81,
100
112
  goal: 90,
101
113
  result_type: "percentage",
102
114
  },
103
- ruleResult: { result: 82 },
115
+ ruleResult: { id: 54, result: 82 },
104
116
  date: "2019-08-12T02:00:00Z",
105
117
  };
106
118
 
107
119
  const wrapper = shallow(<RuleResultRow {...ruleResultRowProps} />);
108
- const cell = wrapper.find("TableRow").find("TableCell").at(0).dive();
120
+ const cell = wrapper.find("TableRow").find("TableCell").at(1).dive();
109
121
 
110
122
  expect(cell.find("td").find("Icon")).toHaveLength(1);
111
123
 
@@ -116,13 +128,14 @@ describe("<RuleResultRow />", () => {
116
128
 
117
129
  it("renders delete cell in RuleResultRow when user isAdmin is true", () => {
118
130
  const ruleResultRowProps = {
119
- id: 10,
131
+ rule: { id: 10 },
120
132
  ruleImplementation: {
133
+ id: 23,
121
134
  minimum: 81,
122
135
  goal: 90,
123
136
  result_type: "percentage",
124
137
  },
125
- ruleResult: { result: 82 },
138
+ ruleResult: { id: 54, result: 82 },
126
139
  date: "2019-08-12T02:00:00Z",
127
140
  isAdmin: true,
128
141
  deleteRuleResult: jest.fn(),
@@ -138,13 +151,14 @@ describe("<RuleResultRow />", () => {
138
151
 
139
152
  it("does not render delete cell in RuleResultRow when user isAdmin is false", () => {
140
153
  const ruleResultRowProps = {
141
- id: 10,
154
+ rule: { id: 10 },
142
155
  ruleImplementation: {
156
+ id: 23,
143
157
  minimum: 81,
144
158
  goal: 90,
145
159
  result_type: "percentage",
146
160
  },
147
- ruleResult: { result: 82 },
161
+ ruleResult: { id: 54, result: 82 },
148
162
  date: "2019-08-12T02:00:00Z",
149
163
  isAdmin: false,
150
164
  deleteRuleResult: jest.fn(),
@@ -1,73 +1,15 @@
1
1
  import React from "react";
2
- import { intl } from "@truedat/test/intl-stub";
3
- import { IntlProvider } from "react-intl";
2
+ import { waitFor } from "@testing-library/react";
4
3
  import { render } from "@truedat/test/render";
5
- import { shallow } from "enzyme";
6
- import { RuleResultsUpload } from "../RuleResultsUpload";
4
+ import coreEn from "@truedat/core/messages/en";
5
+ import RuleResultsUpload from "../RuleResultsUpload";
6
+ import en from "../../messages/en";
7
7
 
8
- afterEach(() => {
9
- jest.clearAllMocks();
10
- });
8
+ const renderOpts = { messages: { en: { ...coreEn, ...en } } };
11
9
 
12
10
  describe("<RuleResultsUpload />", () => {
13
- const renderOpts = {
14
- messages: {
15
- en: {
16
- "ruleResults.actions.upload.tooltip": "Upload rule results",
17
- "ruleResults.actions.upload.confirmation.header": "Upload rule results",
18
- "uploadModal.actions.upload.confirmation.content": "Drag an drop file or click to select file",
19
- "ruleResults.actions.upload.confirmation.header": "Upload rule results",
20
- },
21
- },
22
- };
23
-
24
- it("matches the latest snapshot", () => {
25
- const spy = jest.spyOn(React, "useContext").mockImplementation(() => intl);
26
- const props = {
27
- uploadResults: jest.fn(),
28
- loading: false,
29
- };
30
- const wrapper = shallow(<RuleResultsUpload {...props} />);
31
- expect(wrapper).toMatchSnapshot();
32
- spy.mockRestore();
33
- });
34
-
35
- it("Renders upload rule results menu item if there is a domain with manage_rule_results permission", () => {
36
- const props = {
37
- uploadResults: jest.fn(),
38
- loading: false,
39
- userDomains: [
40
- {
41
- domains: [
42
- {
43
- external_id: "Truedat",
44
- id: 2,
45
- name: "Truedat",
46
- },
47
- ],
48
- permission: "manage_rule_results",
49
- },
50
- ],
51
- };
52
- const { getByText } = render(<RuleResultsUpload {...props} />, renderOpts);
53
-
54
- const element = getByText("Upload rule results");
55
- expect(element).toBeInTheDocument()
56
- });
57
-
58
- it("Does not render upload rule results menu item if there is no domain with manage_rule_results permission", () => {
59
- const props = {
60
- uploadResults: jest.fn(),
61
- loading: false,
62
- userDomains: [],
63
- };
64
-
65
- const { queryByText } = render(
66
- <RuleResultsUpload {...props} />,
67
- renderOpts
68
- );
69
-
70
- const element = queryByText("Upload rule results");
71
- expect(element).toBeNull();
11
+ it("matches the latest snapshot", async () => {
12
+ const { container } = render(<RuleResultsUpload />, renderOpts);
13
+ await waitFor(() => expect(container).toMatchSnapshot());
72
14
  });
73
15
  });
@@ -115,19 +115,81 @@ exports[`<ExecutionDetails> matches the lastest snapshot 1`] = `
115
115
 
116
116
  exports[`<ExecutionDetails> without details 1`] = `
117
117
  <div>
118
- <div
119
- class="ui message"
120
- style="margin-top: 14px;"
118
+ <table
119
+ class="ui table implementation-results medium"
121
120
  >
122
- <div
123
- class="content"
121
+ <thead
122
+ class=""
124
123
  >
125
- <div
126
- class="header"
124
+ <tr
125
+ class=""
127
126
  >
128
- No details found for current result
129
- </div>
130
- </div>
131
- </div>
127
+ <th
128
+ class=""
129
+ colspan="2"
130
+ >
131
+ Information
132
+ </th>
133
+ </tr>
134
+ </thead>
135
+ <tbody
136
+ class=""
137
+ >
138
+ <tr
139
+ class=""
140
+ >
141
+ <td
142
+ class=""
143
+ >
144
+ Quality
145
+ </td>
146
+ <td
147
+ class=""
148
+ >
149
+ <i
150
+ aria-hidden="true"
151
+ class="grey circle icon"
152
+ />
153
+ NaN %
154
+ </td>
155
+ </tr>
156
+ <tr
157
+ class=""
158
+ >
159
+ <td
160
+ class=""
161
+ >
162
+ Date
163
+ </td>
164
+ <td
165
+ class=""
166
+ />
167
+ </tr>
168
+ <tr
169
+ class=""
170
+ >
171
+ <td
172
+ class=""
173
+ >
174
+ Records
175
+ </td>
176
+ <td
177
+ class=""
178
+ />
179
+ </tr>
180
+ <tr
181
+ class=""
182
+ >
183
+ <td
184
+ class=""
185
+ >
186
+ Errors
187
+ </td>
188
+ <td
189
+ class=""
190
+ />
191
+ </tr>
192
+ </tbody>
193
+ </table>
132
194
  </div>
133
195
  `;
@@ -5,7 +5,6 @@ exports[`<RuleImplementationResults /> matches the latest snapshot 1`] = `
5
5
  <Table
6
6
  as="table"
7
7
  className="implementation-results small"
8
- selectable={true}
9
8
  >
10
9
  <TableHeader
11
10
  as="thead"
@@ -16,11 +15,11 @@ exports[`<RuleImplementationResults /> matches the latest snapshot 1`] = `
16
15
  >
17
16
  <TableHeaderCell
18
17
  as="th"
19
- content="ruleResult.props.quality"
18
+ content="ruleResult.props.date"
20
19
  />
21
20
  <TableHeaderCell
22
21
  as="th"
23
- content="ruleResult.props.date"
22
+ content="ruleResult.props.quality"
24
23
  />
25
24
  <TableHeaderCell
26
25
  as="th"
@@ -28,6 +28,7 @@ exports[`<RuleImplementationsActions /> matches the latest snapshot 1`] = `
28
28
  handleSubmit={[Function]}
29
29
  />
30
30
  <RuleImplementationsOptions
31
+ canUploadResults={true}
31
32
  loading={false}
32
33
  />
33
34
  </div>
@@ -1,58 +1,67 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`<RuleImplementationsOptions /> matches the latest loading snapshot 1`] = `
4
- <Dropdown
5
- additionLabel="Add "
6
- additionPosition="top"
7
- className="button icon group-actions button-update"
8
- closeOnBlur={true}
9
- closeOnEscape={true}
10
- deburr={false}
11
- direction="left"
12
- disabled={true}
13
- floating={true}
14
- icon="ellipsis vertical"
15
- minCharacters={1}
16
- noResultsMessage="No results found."
17
- openOnFocus={true}
18
- renderLabel={[Function]}
19
- searchInput="text"
20
- selectOnBlur={true}
21
- selectOnNavigation={true}
22
- wrapSelection={true}
23
- >
24
- <DropdownMenu>
25
- <Connect(RuleImplementationsDownload) />
26
- <Connect(ImplementationsUploadButton) />
27
- <Connect(RuleResultsUpload) />
28
- </DropdownMenu>
29
- </Dropdown>
30
- `;
31
-
32
3
  exports[`<RuleImplementationsOptions /> matches the latest snapshot 1`] = `
33
- <Dropdown
34
- additionLabel="Add "
35
- additionPosition="top"
36
- className="button icon group-actions button-update"
37
- closeOnBlur={true}
38
- closeOnEscape={true}
39
- deburr={false}
40
- direction="left"
41
- floating={true}
42
- icon="ellipsis vertical"
43
- minCharacters={1}
44
- noResultsMessage="No results found."
45
- openOnFocus={true}
46
- renderLabel={[Function]}
47
- searchInput="text"
48
- selectOnBlur={true}
49
- selectOnNavigation={true}
50
- wrapSelection={true}
51
- >
52
- <DropdownMenu>
53
- <Connect(RuleImplementationsDownload) />
54
- <Connect(ImplementationsUploadButton) />
55
- <Connect(RuleResultsUpload) />
56
- </DropdownMenu>
57
- </Dropdown>
4
+ <div>
5
+ <div
6
+ aria-expanded="false"
7
+ class="ui floating dropdown button icon group-actions button-update"
8
+ role="listbox"
9
+ tabindex="0"
10
+ >
11
+ <i
12
+ aria-hidden="true"
13
+ class="ellipsis vertical icon"
14
+ />
15
+ <div
16
+ class="menu transition left"
17
+ >
18
+ <div
19
+ aria-disabled="true"
20
+ class="disabled item"
21
+ role="option"
22
+ >
23
+ <i
24
+ aria-hidden="true"
25
+ class="download icon"
26
+ />
27
+ <span>
28
+ Download with csv format
29
+ </span>
30
+ <p
31
+ class="menu-item-description"
32
+ >
33
+ There are no Implementations to be downloaded
34
+ </p>
35
+ </div>
36
+ <div
37
+ class="item"
38
+ role="option"
39
+ >
40
+ <i
41
+ aria-hidden="true"
42
+ class="upload icon"
43
+ />
44
+ <span
45
+ class="text"
46
+ >
47
+ Upload Implementations
48
+ </span>
49
+ </div>
50
+ <div
51
+ class="item"
52
+ role="option"
53
+ >
54
+ <i
55
+ aria-hidden="true"
56
+ class="upload icon"
57
+ />
58
+ <span
59
+ class="text"
60
+ >
61
+ Upload rule results
62
+ </span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
58
67
  `;
@@ -1,12 +1,20 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<RuleResultsUpload /> matches the latest snapshot 1`] = `
4
- <UploadModal
5
- content="uploadModal.actions.upload.confirmation.content"
6
- handleSubmit={[Function]}
7
- header="ruleResults.actions.upload.confirmation.header"
8
- icon="upload"
9
- param="rule_results"
10
- trigger={null}
11
- />
4
+ <div>
5
+ <div
6
+ class="item"
7
+ role="option"
8
+ >
9
+ <i
10
+ aria-hidden="true"
11
+ class="upload icon"
12
+ />
13
+ <span
14
+ class="text"
15
+ >
16
+ Upload rule results
17
+ </span>
18
+ </div>
19
+ </div>
12
20
  `;
@@ -0,0 +1,50 @@
1
+ import {
2
+ clearRuleImplementations,
3
+ searchRuleImplementations,
4
+ } from "../../routines";
5
+ import {
6
+ implementationsActions,
7
+ initialState,
8
+ } from "../implementationsActions";
9
+
10
+ const fooState = { foo: "bar" };
11
+ const payload = {};
12
+
13
+ describe("reducers: implementationsActions", () => {
14
+ it("should provide the initial state", () => {
15
+ expect(implementationsActions(undefined, {})).toBe(initialState);
16
+ });
17
+
18
+ it("should be the initial state after receiving the clearRuleImplementations.TRIGGER action", () => {
19
+ expect(
20
+ implementationsActions(fooState, {
21
+ type: clearRuleImplementations.TRIGGER,
22
+ payload,
23
+ })
24
+ ).toBe(initialState);
25
+ });
26
+
27
+ it("should be the initial state after receiving the searchRuleImplementations.TRIGGER action", () => {
28
+ expect(
29
+ implementationsActions(fooState, {
30
+ type: searchRuleImplementations.TRIGGER,
31
+ payload,
32
+ })
33
+ ).toBe(initialState);
34
+ });
35
+
36
+ it("should extract _actions from the searchRuleImplementations.SUCCESS action", () => {
37
+ const _actions = { uploadResults: { foo: "bar" } };
38
+ const payload = { data: { _actions } };
39
+ expect(
40
+ implementationsActions(fooState, {
41
+ type: searchRuleImplementations.SUCCESS,
42
+ payload,
43
+ })
44
+ ).toMatchObject(_actions);
45
+ });
46
+
47
+ it("should ignore unhandled actions", () => {
48
+ expect(implementationsActions(fooState, { type: "FOO" })).toBe(fooState);
49
+ });
50
+ });
@@ -1,18 +1,18 @@
1
1
  import { uploadResults } from "../../routines";
2
- import { uploadingResults } from "..";
2
+ import { resultsUploading } from "../resultsUploading";
3
3
 
4
4
  const fooState = { foo: "bar" };
5
5
 
6
6
  const initialState = false;
7
7
 
8
- describe("reducers: uploadingResults", () => {
8
+ describe("reducers: resultsUploading", () => {
9
9
  it("should provide the initial state", () => {
10
- expect(uploadingResults(undefined, {})).toEqual(initialState);
10
+ expect(resultsUploading(undefined, {})).toEqual(initialState);
11
11
  });
12
12
 
13
13
  it("should handle the uploadResults.TRIGGER action", () => {
14
14
  expect(
15
- uploadingResults(fooState, {
15
+ resultsUploading(fooState, {
16
16
  type: uploadResults.TRIGGER,
17
17
  })
18
18
  ).toBeTruthy();
@@ -20,7 +20,7 @@ describe("reducers: uploadingResults", () => {
20
20
 
21
21
  it("should handle the uploadResults.FULFILL action", () => {
22
22
  expect(
23
- uploadingResults(fooState, {
23
+ resultsUploading(fooState, {
24
24
  type: uploadResults.FULFILL,
25
25
  })
26
26
  ).toBeFalsy();
@@ -0,0 +1,24 @@
1
+ import {
2
+ clearRuleImplementations,
3
+ searchRuleImplementations,
4
+ } from "../routines";
5
+
6
+ export const initialState = null;
7
+
8
+ export const implementationsActions = (
9
+ state = initialState,
10
+ { type, payload }
11
+ ) => {
12
+ switch (type) {
13
+ case clearRuleImplementations.TRIGGER:
14
+ return initialState;
15
+ case searchRuleImplementations.TRIGGER:
16
+ return initialState;
17
+ case searchRuleImplementations.SUCCESS:
18
+ return payload?.data?._actions || initialState;
19
+ default:
20
+ return state;
21
+ }
22
+ };
23
+
24
+ export default implementationsActions;
@@ -3,9 +3,11 @@ import { conceptRulesActions } from "./conceptRulesActions";
3
3
  import { conceptRulesLoading } from "./conceptRulesLoading";
4
4
  import { deletionQuery } from "./deletionQuery";
5
5
  import { dqMessage } from "./dqMessage";
6
- import { previousRuleImplementationQuery } from "./previousRuleImplementationQuery";
7
- import { executionGroupLoading } from "./executionGroupLoading";
8
6
  import { executionGroup } from "./executionGroup";
7
+ import { executionGroupLoading } from "./executionGroupLoading";
8
+ import { implementationsActions } from "./implementationsActions";
9
+ import { previousRuleImplementationQuery } from "./previousRuleImplementationQuery";
10
+ import { resultsUploading } from "./resultsUploading";
9
11
  import { rule } from "./rule";
10
12
  import { ruleActions } from "./ruleActions";
11
13
  import { ruleActiveFilters } from "./ruleActiveFilters";
@@ -14,6 +16,7 @@ import { ruleCreating } from "./ruleCreating";
14
16
  import { ruleFilters } from "./ruleFilters";
15
17
  import { ruleFiltersLoading } from "./ruleFiltersLoading";
16
18
  import { ruleImplementation } from "./ruleImplementation";
19
+ import { ruleImplementationActiveFilters } from "./ruleImplementationActiveFilters";
17
20
  import { ruleImplementationCount } from "./ruleImplementationCount";
18
21
  import { ruleImplementationCreating } from "./ruleImplementationCreating";
19
22
  import { ruleImplementationFilters } from "./ruleImplementationFilters";
@@ -26,7 +29,6 @@ import { ruleImplementationSaving } from "./ruleImplementationSaving";
26
29
  import { ruleImplementationSelectedFilter } from "./ruleImplementationSelectedFilter";
27
30
  import { ruleImplementations } from "./ruleImplementations";
28
31
  import { ruleImplementationsDownloading } from "./ruleImplementationsDownloading";
29
- import { ruleImplementationActiveFilters } from "./ruleImplementationActiveFilters";
30
32
  import { ruleImplementationsLoading } from "./ruleImplementationsLoading";
31
33
  import { ruleImplementationsPageSize } from "./ruleImplementationsPageSize";
32
34
  import { ruleLoading } from "./ruleLoading";
@@ -39,10 +41,9 @@ import { rulesLoading } from "./rulesLoading";
39
41
  import { rulesPageSize } from "./rulesPageSize";
40
42
  import { uploadingImplementationsFile } from "./uploadingImplementationsFile";
41
43
  import { uploadingRulesFile } from "./uploadingRulesFile";
44
+ import { userImplementationsPermissions } from "./userImplementationsPermissions";
42
45
  import { userRulePermissions } from "./userRulePermissions";
43
46
  import { userRulesPermissions } from "./userRulesPermissions";
44
- import { userImplementationsPermissions } from "./userImplementationsPermissions";
45
- import { uploadingResults } from "./uploadingResults";
46
47
 
47
48
  export {
48
49
  conceptRules,
@@ -50,9 +51,11 @@ export {
50
51
  conceptRulesLoading,
51
52
  deletionQuery,
52
53
  dqMessage,
53
- previousRuleImplementationQuery,
54
- executionGroupLoading,
55
54
  executionGroup,
55
+ executionGroupLoading,
56
+ implementationsActions,
57
+ previousRuleImplementationQuery,
58
+ resultsUploading,
56
59
  rule,
57
60
  ruleActions,
58
61
  ruleActiveFilters,
@@ -84,10 +87,9 @@ export {
84
87
  rules,
85
88
  rulesLoading,
86
89
  rulesPageSize,
87
- uploadingRulesFile,
88
90
  uploadingImplementationsFile,
91
+ uploadingRulesFile,
92
+ userImplementationsPermissions,
89
93
  userRulePermissions,
90
94
  userRulesPermissions,
91
- userImplementationsPermissions,
92
- uploadingResults,
93
95
  };
@@ -2,7 +2,7 @@ import { uploadResults } from "../routines";
2
2
 
3
3
  const initialState = false;
4
4
 
5
- export const uploadingResults = (state = initialState, { type }) => {
5
+ export const resultsUploading = (state = initialState, { type }) => {
6
6
  switch (type) {
7
7
  case uploadResults.TRIGGER:
8
8
  return true;
@@ -13,4 +13,4 @@ export const uploadingResults = (state = initialState, { type }) => {
13
13
  }
14
14
  };
15
15
 
16
- export default uploadingResults;
16
+ export default resultsUploading;
@@ -0,0 +1,3 @@
1
+ .rule-result-date {
2
+ margin-right: 5px;
3
+ }