@truedat/dq 4.46.12 → 4.47.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,9 @@ 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
7
  import { Link } from "react-router-dom";
7
- import { Table, Icon } from "semantic-ui-react";
8
+ import { Table, Icon, Label } from "semantic-ui-react";
8
9
  import { ConfirmModal, DateTime } from "@truedat/core/components";
9
10
  import { columnDecorator } from "@truedat/core/services";
10
11
  import { linkTo } from "@truedat/core/routes";
@@ -13,91 +14,118 @@ import { deleteRuleResult } from "../routines";
13
14
  import "../styles/ruleResultRow.less";
14
15
 
15
16
  export const RuleResultRow = ({
17
+ active = false,
16
18
  deleteRuleResult,
17
19
  optionalColumns,
18
20
  ruleResult,
19
21
  customColumns,
20
22
  isAdmin,
21
23
  ruleImplementation,
24
+ tagLabel,
22
25
  }) => {
23
26
  const { formatMessage, formatNumber: _formatNumber } = useIntl();
24
27
  const formatNumber = (num) => (_.isNil(num) ? num : _formatNumber(num));
28
+ const history = useHistory();
25
29
 
26
30
  return (
27
- <Table.Row>
28
- <Table.Cell>
29
- <Link
30
- to={linkTo.IMPLEMENTATION_RESULT_DETAILS({
31
- implementation_id: ruleImplementation.id,
32
- ruleImplementation,
33
- rule_result_id: ruleResult.id,
34
- })}
35
- >
36
- <DateTime className="rule-result-date" value={ruleResult.date} />
37
- {ruleResult.has_segments ? (
38
- <Icon
39
- circular
40
- inverted
41
- name="grid layout"
42
- color="blue"
43
- size="small"
44
- />
45
- ) : null}
46
-
47
- {ruleResult.has_remediation ? (
48
- <Icon circular inverted name="rain" color="blue" size="small" />
49
- ) : null}
50
- </Link>
51
- </Table.Cell>
52
- <Table.Cell>
53
- <Icon
54
- name="circle"
55
- color={selectColor({ ...ruleImplementation, ...ruleResult })}
56
- />
57
- {`${parseFloat(ruleResult.result)} %`}
58
- </Table.Cell>
59
- {_.includes("records")(optionalColumns) ? (
60
- <Table.Cell>{formatNumber(ruleResult.records)}</Table.Cell>
61
- ) : null}
62
- {_.includes("errors")(optionalColumns) ? (
63
- <Table.Cell>{formatNumber(ruleResult.errors)}</Table.Cell>
31
+ <>
32
+ {tagLabel ? (
33
+ <Table.Row>
34
+ <Table.Cell colSpan={8}>
35
+ <Label
36
+ as="a"
37
+ active={active}
38
+ ribbon
39
+ onClick={() =>
40
+ history.push(
41
+ linkTo.IMPLEMENTATION({
42
+ implementation_id: ruleImplementation.id,
43
+ })
44
+ )
45
+ }
46
+ >
47
+ {tagLabel}
48
+ </Label>
49
+ </Table.Cell>
50
+ </Table.Row>
64
51
  ) : null}
65
- {customColumns?.map((column, index) => (
66
- <Table.Cell key={index}>
67
- {columnDecorator(column)(ruleResult)}
68
- </Table.Cell>
69
- ))}
70
- {isAdmin ? (
71
- <Table.Cell onClick={(e) => e.stopPropagation()}>
72
- <ConfirmModal
73
- icon="trash"
74
- trigger={<Icon name="trash alternate outline" color="red" />}
75
- header={formatMessage({
76
- id: "rule_result.actions.delete.confirmation.header",
77
- })}
78
- content={formatMessage({
79
- id: "rule_result.actions.delete.confirmation.content",
52
+ <Table.Row warning={active}>
53
+ <Table.Cell>
54
+ <Link
55
+ to={linkTo.IMPLEMENTATION_RESULT_DETAILS({
56
+ implementation_id: ruleImplementation.id,
57
+ ruleImplementation,
58
+ rule_result_id: ruleResult.id,
80
59
  })}
81
- onConfirm={() =>
82
- deleteRuleResult({
83
- id: ruleResult.id,
84
- rule_implementation_id: ruleImplementation.id,
85
- })
86
- }
60
+ >
61
+ <DateTime className="rule-result-date" value={ruleResult.date} />
62
+ {ruleResult.has_segments ? (
63
+ <Icon
64
+ circular
65
+ inverted
66
+ name="grid layout"
67
+ color="blue"
68
+ size="small"
69
+ />
70
+ ) : null}
71
+
72
+ {ruleResult.has_remediation ? (
73
+ <Icon circular inverted name="rain" color="blue" size="small" />
74
+ ) : null}
75
+ </Link>
76
+ </Table.Cell>
77
+ <Table.Cell>
78
+ <Icon
79
+ name="circle"
80
+ color={selectColor({ ...ruleImplementation, ...ruleResult })}
87
81
  />
82
+ {`${parseFloat(ruleResult.result)} %`}
88
83
  </Table.Cell>
89
- ) : null}
90
- </Table.Row>
84
+ {_.includes("records")(optionalColumns) ? (
85
+ <Table.Cell>{formatNumber(ruleResult.records)}</Table.Cell>
86
+ ) : null}
87
+ {_.includes("errors")(optionalColumns) ? (
88
+ <Table.Cell>{formatNumber(ruleResult.errors)}</Table.Cell>
89
+ ) : null}
90
+ {customColumns?.map((column, index) => (
91
+ <Table.Cell key={index}>
92
+ {columnDecorator(column)(ruleResult)}
93
+ </Table.Cell>
94
+ ))}
95
+ {isAdmin ? (
96
+ <Table.Cell onClick={(e) => e.stopPropagation()}>
97
+ <ConfirmModal
98
+ icon="trash"
99
+ trigger={<Icon name="trash alternate outline" color="red" />}
100
+ header={formatMessage({
101
+ id: "rule_result.actions.delete.confirmation.header",
102
+ })}
103
+ content={formatMessage({
104
+ id: "rule_result.actions.delete.confirmation.content",
105
+ })}
106
+ onConfirm={() =>
107
+ deleteRuleResult({
108
+ id: ruleResult.id,
109
+ rule_implementation_id: ruleImplementation.id,
110
+ })
111
+ }
112
+ />
113
+ </Table.Cell>
114
+ ) : null}
115
+ </Table.Row>
116
+ </>
91
117
  );
92
118
  };
93
119
 
94
120
  RuleResultRow.propTypes = {
121
+ active: PropTypes.bool,
95
122
  customColumns: PropTypes.array,
96
123
  deleteRuleResult: PropTypes.func,
97
124
  isAdmin: PropTypes.bool,
98
125
  optionalColumns: PropTypes.array,
99
126
  ruleImplementation: PropTypes.object,
100
127
  ruleResult: PropTypes.object,
128
+ tagLabel: PropTypes.string,
101
129
  };
102
130
 
103
131
  const mapDispatchToProps = { deleteRuleResult };
@@ -1,11 +1,14 @@
1
1
  import React from "react";
2
- import { Route } from "react-router-dom";
2
+ import { Route, useParams } from "react-router-dom";
3
3
  import { Segment } from "semantic-ui-react";
4
+ import { useQuery } from "@apollo/client";
5
+ import { Loading } from "@truedat/core/components";
4
6
  import {
5
7
  IMPLEMENTATION_RESULT_DETAILS,
6
8
  IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
7
9
  IMPLEMENTATION_RESULT_REMEDIATION_PLAN,
8
10
  } from "@truedat/core/routes";
11
+ import { IMPLEMENTATION_RESULT } from "../api/queries";
9
12
  import RuleResult from "./RuleResult";
10
13
  import RuleResultDetails from "./RuleResultDetails";
11
14
  import RuleResultSegments from "./RuleResultSegments";
@@ -13,46 +16,55 @@ import RuleResultRemediations from "./RuleResultRemediations";
13
16
  import ImplementationCrumbs from "./ImplementationCrumbs";
14
17
  import RuleResultSegmentsLoader from "./RuleResultSegmentsLoader";
15
18
 
16
- export const RuleResultsRoutes = ({}) => (
17
- <>
18
- <Route
19
- exact
20
- path={IMPLEMENTATION_RESULT_DETAILS}
21
- render={() => (
22
- <Segment>
23
- <ImplementationCrumbs />
24
- <RuleResult>
25
- <RuleResultDetails />
26
- </RuleResult>
27
- </Segment>
28
- )}
29
- />
30
- <Route
31
- exact
32
- path={IMPLEMENTATION_RESULT_SEGMENTS_RESULTS}
33
- render={() => (
34
- <Segment>
35
- <ImplementationCrumbs />
36
- <RuleResult>
37
- <RuleResultSegmentsLoader />
38
- <RuleResultSegments />
39
- </RuleResult>
40
- </Segment>
41
- )}
42
- />
43
- <Route
44
- exact
45
- path={IMPLEMENTATION_RESULT_REMEDIATION_PLAN}
46
- render={() => (
47
- <Segment>
48
- <ImplementationCrumbs />
49
- <RuleResult>
50
- <RuleResultRemediations />
51
- </RuleResult>
52
- </Segment>
53
- )}
54
- />
55
- </>
56
- );
19
+ export const RuleResultsRoutes = ({}) => {
20
+ const { rule_result_id } = useParams();
21
+ const { loading, error, data } = useQuery(IMPLEMENTATION_RESULT, {
22
+ variables: { id: rule_result_id },
23
+ });
24
+ if (error) return null;
25
+ if (loading) return <Loading />;
26
+ const result = data?.implementationResult;
27
+ return (
28
+ <>
29
+ <Route
30
+ exact
31
+ path={IMPLEMENTATION_RESULT_DETAILS}
32
+ render={() => (
33
+ <Segment>
34
+ <ImplementationCrumbs ruleResult={result} />
35
+ <RuleResult ruleResult={result}>
36
+ <RuleResultDetails ruleResult={result} />
37
+ </RuleResult>
38
+ </Segment>
39
+ )}
40
+ />
41
+ <Route
42
+ exact
43
+ path={IMPLEMENTATION_RESULT_SEGMENTS_RESULTS}
44
+ render={() => (
45
+ <Segment>
46
+ <ImplementationCrumbs ruleResult={result} />
47
+ <RuleResult ruleResult={result}>
48
+ <RuleResultSegmentsLoader />
49
+ <RuleResultSegments />
50
+ </RuleResult>
51
+ </Segment>
52
+ )}
53
+ />
54
+ <Route
55
+ exact
56
+ path={IMPLEMENTATION_RESULT_REMEDIATION_PLAN}
57
+ render={() => (
58
+ <Segment>
59
+ <ImplementationCrumbs ruleResult={result} />
60
+ <RuleResult ruleResult={result}>
61
+ <RuleResultRemediations ruleResult={result} />
62
+ </RuleResult>
63
+ </Segment>
64
+ )}
65
+ />
66
+ </>
67
+ );
68
+ };
57
69
 
58
70
  export default RuleResultsRoutes;
@@ -14,13 +14,8 @@ describe("<RuleImplementationProperties />", () => {
14
14
  id: 10,
15
15
  executable: true,
16
16
  name: "NameRule2",
17
- system: "SYS",
18
17
  type: "integer_values_range",
19
- system: {
20
- external_id: "Microstrategy",
21
- id: 1,
22
- name: "Microstrategy",
23
- },
18
+ status: "published",
24
19
  system_params: {
25
20
  table: { name: "aaaxxx" },
26
21
  column: {
@@ -61,14 +56,8 @@ describe("<RuleImplementationProperties />", () => {
61
56
  id: 10,
62
57
  executable: true,
63
58
  name: "NameRule2",
64
- system: "SYS",
65
59
  type: "integer_values_range",
66
60
  status: "published",
67
- system: {
68
- external_id: "Microstrategy",
69
- id: 1,
70
- name: "Microstrategy",
71
- },
72
61
  system_params: {
73
62
  table: { name: "aaaxxx" },
74
63
  column: {
@@ -1,35 +1,99 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
- import { shallow } from "enzyme";
3
+ import { render } from "@truedat/test/render";
4
4
  import { intl } from "@truedat/test/intl-stub";
5
5
  import {
6
6
  RuleImplementationResults,
7
7
  getCustomColumnsWithData,
8
8
  } from "../RuleImplementationResults";
9
9
 
10
+ const renderOpts = {
11
+ messages: {
12
+ en: {
13
+ "ruleResult.props.date": "date",
14
+ "ruleResult.props.quality": "quality",
15
+ "ruleResult.props.records": "records",
16
+ "ruleResult.props.errors": "errors",
17
+ "quality.error": "errors",
18
+ },
19
+ },
20
+ };
21
+
10
22
  // workaround for enzyme issue with React.useContext
11
23
  // see https://github.com/airbnb/enzyme/issues/2176#issuecomment-532361526
12
24
  jest.spyOn(React, "useContext").mockImplementation(() => intl);
13
25
 
14
26
  describe("<RuleImplementationResults />", () => {
15
- const rule = {
16
- result_type: "percentage",
17
- minimum: 50,
18
- goal: 100,
19
- };
20
27
  const ruleImplementation = {
21
- results: [{ foo: "bar" }],
28
+ id: 1,
29
+ implementation_key: "default_key",
30
+ versions: [
31
+ {
32
+ implementation_key: "default_key",
33
+ id: 1,
34
+ goal: "20.0",
35
+ minimum: "10.0",
36
+ results: [
37
+ {
38
+ errors: 2,
39
+ has_remediation: false,
40
+ has_segments: true,
41
+ id: "66577",
42
+ records: 31,
43
+ result: "93.54",
44
+ result_type: "percentage",
45
+ },
46
+ {
47
+ errors: 2,
48
+ has_remediation: false,
49
+ has_segments: true,
50
+ id: "66578",
51
+ records: 31,
52
+ result: "93.54",
53
+ result_type: "percentage",
54
+ },
55
+ ],
56
+ },
57
+ {
58
+ implementation_key: "key_2",
59
+ id: 2,
60
+ goal: "20.0",
61
+ minimum: "10.0",
62
+ results: [
63
+ {
64
+ errors: 2,
65
+ has_remediation: false,
66
+ has_segments: true,
67
+ id: "66579",
68
+ records: 31,
69
+ result: "93.54",
70
+ result_type: "percentage",
71
+ },
72
+ {
73
+ errors: 2,
74
+ has_remediation: false,
75
+ has_segments: true,
76
+ id: "66580",
77
+ records: 31,
78
+ result: "93.54",
79
+ result_type: "percentage",
80
+ },
81
+ ],
82
+ },
83
+ ],
22
84
  };
23
- const customColumns = [{ foo: "bar", name: "foo" }];
85
+ const ruleResultsColumns = [{ foo: "bar", name: "foo" }];
24
86
  const props = {
25
- rule,
26
87
  ruleImplementation,
27
- customColumns,
88
+ ruleResultsColumns,
28
89
  };
29
90
 
30
91
  it("matches the latest snapshot", () => {
31
- const wrapper = shallow(<RuleImplementationResults {...props} />);
32
- expect(wrapper).toMatchSnapshot();
92
+ const { container } = render(
93
+ <RuleImplementationResults {...props} />,
94
+ renderOpts
95
+ );
96
+ expect(container).toMatchSnapshot();
33
97
  });
34
98
  });
35
99
 
@@ -1,3 +1,4 @@
1
+ import _ from "lodash/fp";
1
2
  import React, { Suspense } from "react";
2
3
  import { render } from "@truedat/test/render";
3
4
  import messages from "@truedat/dq/messages";
@@ -18,13 +19,33 @@ const renderOpts = {
18
19
  },
19
20
  };
20
21
 
22
+ const commonProps = {
23
+ ruleImplementation: {
24
+ id: 1,
25
+ implementation_key: "key",
26
+ minimum: 10,
27
+ goal: 20,
28
+ result_type: "percentage",
29
+ },
30
+ ruleResult: {
31
+ id: 100,
32
+ result: "93.54",
33
+ params: {},
34
+ details: {},
35
+ records: 31,
36
+ date: "2022-06-24",
37
+ errors: 2,
38
+ has_remediation: true,
39
+ has_segments: true,
40
+ },
41
+ };
42
+
21
43
  describe("<RuleResultDetails>", () => {
22
44
  it("matches the latest snapshot", () => {
23
- const props = {
24
- ruleImplementation: {
25
- results: [{ foo: "bar", id: 100, details: { Query: "ImZvbyI=" } }],
26
- },
27
- };
45
+ const props = _.update("ruleResult.details", () => ({ Query: "ImZvbyI=" }))(
46
+ commonProps
47
+ );
48
+
28
49
  const { container } = render(
29
50
  <Suspense fallback={null}>
30
51
  <RuleResultDetails {...props} />
@@ -35,20 +56,11 @@ describe("<RuleResultDetails>", () => {
35
56
  });
36
57
 
37
58
  it("decodes details with base64 prefix", () => {
38
- const props = {
39
- ruleImplementation: {
40
- results: [
41
- {
42
- foo: "bar",
43
- id: 100,
44
- details: {
45
- value: "valor",
46
- base64_test: "QmFzZTY0X3ZhbHVl", //base64 for Base64_value
47
- },
48
- },
49
- ],
50
- },
51
- };
59
+ const props = _.update("ruleResult.details", () => ({
60
+ value: "valor",
61
+ base64_test: "QmFzZTY0X3ZhbHVl", //base64 for Base64_value
62
+ }))(commonProps);
63
+
52
64
  const { getByText } = render(
53
65
  <Suspense fallback={null}>
54
66
  <RuleResultDetails {...props} />
@@ -60,14 +72,9 @@ describe("<RuleResultDetails>", () => {
60
72
  });
61
73
 
62
74
  it("without details", () => {
63
- const propsWithoutDetails = {
64
- ruleImplementation: {
65
- results: [{ foo: "bar", id: 100 }],
66
- },
67
- };
68
75
  const { container } = render(
69
76
  <Suspense fallback={null}>
70
- <RuleResultDetails {...propsWithoutDetails} />
77
+ <RuleResultDetails {...commonProps} />
71
78
  </Suspense>,
72
79
  renderOpts
73
80
  );
@@ -20,30 +20,22 @@ describe("<RuleResultRemediations />", () => {
20
20
  it("renders remediation plan if has rule result", () => {
21
21
  const props = {
22
22
  ruleImplementation: {
23
- id: 23,
23
+ id: 852,
24
24
  minimum: 1,
25
25
  goal: 10,
26
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
27
  },
46
- ruleResultId: 65599,
28
+ ruleResult: {
29
+ id: 65599,
30
+ result: "93.54",
31
+ params: {},
32
+ details: {},
33
+ records: 31,
34
+ date: "2022-06-24",
35
+ errors: 2,
36
+ has_remediation: true,
37
+ has_segments: true,
38
+ },
47
39
  };
48
40
 
49
41
  const { container } = render(
@@ -1,12 +1,84 @@
1
1
  import React from "react";
2
2
  import { render } from "@truedat/test/render";
3
+ import {
4
+ IMPLEMENTATION_RESULT_DETAILS,
5
+ IMPLEMENTATION_RESULT_SEGMENTS_RESULTS,
6
+ IMPLEMENTATION_RESULT_REMEDIATION_PLAN,
7
+ } from "@truedat/core/routes";
8
+ import { IMPLEMENTATION_RESULT } from "../../api/queries";
3
9
  import { RuleResultsRoutes } from "../RuleResultsRoutes";
4
10
 
11
+ jest.mock("react-router-dom", () => ({
12
+ ...jest.requireActual("react-router-dom"),
13
+ useParams: () => ({ rule_result_id: 1 }),
14
+ }));
15
+
5
16
  describe("<RuleResultsRoutes>", () => {
6
- const props = {};
17
+ const implementationResult = {
18
+ implementationResult: {
19
+ date: "2022-06-24",
20
+ errors: 2,
21
+ has_remediation: true,
22
+ has_segments: true,
23
+ id: 1,
24
+ records: 31,
25
+ details: {},
26
+ result: "93.54",
27
+ params: {},
28
+ result_type: "percentage",
29
+ },
30
+ };
31
+
32
+ const mock = {
33
+ request: { query: IMPLEMENTATION_RESULT, variables: { id: 1 } },
34
+ result: { data: implementationResult },
35
+ };
36
+ const key = "mi implementación molona";
37
+ const state = {
38
+ ruleImplementation: {
39
+ id: 2,
40
+ implementation_key: key,
41
+ },
42
+ authentication: { role: "admin" },
43
+ };
44
+
45
+ const renderOpts = { mocks: [mock], state };
46
+
47
+ it("matches the latest loading snapshot", () => {
48
+ const routes = [IMPLEMENTATION_RESULT_DETAILS];
49
+ const { container } = render(<RuleResultsRoutes />, {
50
+ ...renderOpts,
51
+ routes,
52
+ });
53
+ expect(container).toMatchSnapshot();
54
+ });
7
55
 
8
- it("matches the latest snapshot", () => {
9
- const { container } = render(<RuleResultsRoutes {...props} />, {});
56
+ it("matches the latest success details route snapshot", async () => {
57
+ const routes = [IMPLEMENTATION_RESULT_DETAILS];
58
+ const { container, findByText } = render(<RuleResultsRoutes />, {
59
+ ...renderOpts,
60
+ routes,
61
+ });
62
+ await findByText(key);
63
+ expect(container).toMatchSnapshot();
64
+ });
65
+
66
+ it("matches the latest success segments route snapshot", async () => {
67
+ const routes = [IMPLEMENTATION_RESULT_SEGMENTS_RESULTS];
68
+ const { container, findByText } = render(<RuleResultsRoutes />, {
69
+ ...renderOpts,
70
+ routes,
71
+ });
72
+ await findByText(key);
73
+ expect(container).toMatchSnapshot();
74
+ });
75
+ it("matches the latest success remediation route snapshot", async () => {
76
+ const routes = [IMPLEMENTATION_RESULT_REMEDIATION_PLAN];
77
+ const { container, findByText } = render(<RuleResultsRoutes />, {
78
+ ...renderOpts,
79
+ routes,
80
+ });
81
+ await findByText(key);
10
82
  expect(container).toMatchSnapshot();
11
83
  });
12
84
  });