@truedat/dq 4.58.2 → 4.58.4

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.58.4] 2022-12-22
4
+
5
+ ### Added
6
+
7
+ - [TD-4300] Add `basic` type for implementations
8
+
9
+ ## [4.58.3] 2022-12-21
10
+
11
+ ### Changed
12
+
13
+ - [TD-5426] Update `decode-uri-component` (CVE-2022-38900)
14
+
3
15
  ## [4.56.9] 2022-12-12
4
16
 
5
17
  ### Added
@@ -10,7 +22,7 @@
10
22
 
11
23
  ### Added
12
24
 
13
- - # [TD-4179] inserted_at and updated_at columns on Implementations
25
+ - [TD-4179] inserted_at and updated_at columns on Implementations
14
26
 
15
27
  ## [4.56.6] 2022-11-28
16
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.58.2",
3
+ "version": "4.58.4",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.5",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "4.58.2",
37
+ "@truedat/test": "4.58.4",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -92,12 +92,14 @@
92
92
  },
93
93
  "dependencies": {
94
94
  "@apollo/client": "^3.7.1",
95
- "@truedat/core": "4.58.2",
96
- "@truedat/df": "4.58.2",
95
+ "@truedat/core": "4.58.4",
96
+ "@truedat/df": "4.58.4",
97
+ "decode-uri-component": "^0.2.2",
97
98
  "graphql": "^15.5.3",
98
99
  "moment": "^2.29.4",
99
100
  "path-to-regexp": "^1.7.0",
100
101
  "prop-types": "^15.8.1",
102
+ "query-string": "^6.11.0",
101
103
  "rc-slider": "^8.6.1",
102
104
  "react-intl": "^5.20.10",
103
105
  "react-moment": "^1.1.2",
@@ -116,5 +118,5 @@
116
118
  "react-dom": ">= 16.8.6 < 17",
117
119
  "semantic-ui-react": ">= 2.0.3 < 2.2"
118
120
  },
119
- "gitHead": "2e7042f380612d48784dc5641085fbe6dba7c7d1"
121
+ "gitHead": "852f36764d42a458272b3d5a5d204d928d5243c4"
120
122
  }
@@ -16,6 +16,7 @@ import {
16
16
  IMPLEMENTATION_HISTORY,
17
17
  IMPLEMENTATION_MOVE,
18
18
  IMPLEMENTATION_NEW,
19
+ IMPLEMENTATION_NEW_BASIC,
19
20
  IMPLEMENTATION_NEW_RAW,
20
21
  IMPLEMENTATION_RESULT_DETAILS,
21
22
  IMPLEMENTATION_RESULTS_DETAILS,
@@ -38,6 +39,7 @@ import ImplementationStructuresNew from "./ImplementationStructuresNew";
38
39
  import Implementations from "./Implementations";
39
40
  import MoveImplementation from "./MoveImplementation";
40
41
  import NewRuleImplementation from "./NewRuleImplementation";
42
+ import NewBasicRuleImplementation from "./NewBasicRuleImplementation";
41
43
  import RuleImplementation from "./RuleImplementation";
42
44
  import RuleImplementationEvents from "./RuleImplementationEvents";
43
45
  import RuleImplementationHistory from "./RuleImplementationHistory";
@@ -77,6 +79,7 @@ export const ImplementationsRoutes = ({
77
79
  ruleImplementationLoaded,
78
80
  structuresAliasesLoading,
79
81
  systemsLoading,
82
+ isRuleImplemenationBasic,
80
83
  }) => {
81
84
  const authorized = useAuthorized();
82
85
  const { rule_result_id: ruleResultId } = useParams();
@@ -93,6 +96,16 @@ export const ImplementationsRoutes = ({
93
96
  </>
94
97
  )}
95
98
  />
99
+ <Route
100
+ exact
101
+ path={IMPLEMENTATION_NEW_BASIC}
102
+ render={() => (
103
+ <>
104
+ <TemplatesLoader scope="ri" />
105
+ <NewBasicRuleImplementation />
106
+ </>
107
+ )}
108
+ />
96
109
  <Route
97
110
  exact
98
111
  path={IMPLEMENTATION_NEW_RAW}
@@ -174,7 +187,11 @@ export const ImplementationsRoutes = ({
174
187
  {!structuresAliasesLoading &&
175
188
  ruleImplementationLoaded &&
176
189
  implementationStructuresLoaded ? (
177
- <NewRuleImplementation edition clone />
190
+ isRuleImplemenationBasic ? (
191
+ <NewBasicRuleImplementation edition clone />
192
+ ) : (
193
+ <NewRuleImplementation edition clone />
194
+ )
178
195
  ) : null}
179
196
  </>
180
197
  )}
@@ -192,7 +209,11 @@ export const ImplementationsRoutes = ({
192
209
  {!structuresAliasesLoading &&
193
210
  ruleImplementationLoaded &&
194
211
  implementationStructuresLoaded ? (
195
- <NewRuleImplementation edition />
212
+ isRuleImplemenationBasic ? (
213
+ <NewBasicRuleImplementation edition />
214
+ ) : (
215
+ <NewRuleImplementation edition />
216
+ )
196
217
  ) : null}
197
218
  </>
198
219
  )}
@@ -379,6 +400,7 @@ ImplementationsRoutes.propTypes = {
379
400
  ruleImplementationLoaded: PropTypes.bool,
380
401
  structuresAliasesLoading: PropTypes.bool,
381
402
  systemsLoading: PropTypes.bool,
403
+ isRuleImplemenationBasic: PropTypes.bool,
382
404
  };
383
405
 
384
406
  export const mapStateToProps = (state) => ({
@@ -388,6 +410,8 @@ export const mapStateToProps = (state) => ({
388
410
  !state.ruleImplementationLoading && !_.isEmpty(state.ruleImplementation),
389
411
  structuresAliasesLoading: state.structuresAliasesLoading,
390
412
  systemsLoading: state.systemsLoading,
413
+ isRuleImplemenationBasic:
414
+ state.ruleImplementation?.implementation_type == "basic",
391
415
  });
392
416
 
393
417
  export default connect(mapStateToProps)(ImplementationsRoutes);
@@ -0,0 +1,139 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
5
+ import { FormattedMessage } from "react-intl";
6
+ import { Divider, Header, Grid } from "semantic-ui-react";
7
+ import {
8
+ createRuleImplementation,
9
+ updateRuleImplementation,
10
+ } from "../routines";
11
+ import RuleImplementationBasicForm from "./ruleImplementationForm/RuleImplementationBasicForm";
12
+
13
+ export const NewBasicRuleImplementation = ({
14
+ clone = false,
15
+ createRuleImplementation,
16
+ updateRuleImplementation,
17
+ edition = false,
18
+ ruleImplementationProps,
19
+ rule,
20
+ }) => {
21
+ const [ruleImplementation, setRuleImplementation] = useState(
22
+ edition
23
+ ? {
24
+ id: clone ? null : ruleImplementationProps.id,
25
+ implementationKey: clone
26
+ ? ""
27
+ : ruleImplementationProps.implementation_key,
28
+ dfName: ruleImplementationProps.df_name,
29
+ dfContent: ruleImplementationProps.df_content,
30
+ result_type: ruleImplementationProps.result_type,
31
+ minimum: ruleImplementationProps.minimum,
32
+ goal: ruleImplementationProps.goal,
33
+ rule: ruleImplementationProps.rule_id
34
+ ? ruleImplementationProps.rule
35
+ : null,
36
+ rule_id: ruleImplementationProps.rule_id,
37
+ domain_id: ruleImplementationProps.domain_id,
38
+ status: ruleImplementationProps.status,
39
+ }
40
+ : {
41
+ implementationKey: "",
42
+ dfName: "",
43
+ dfContent: {},
44
+ result_type: "percentage",
45
+ minimum: null,
46
+ goal: null,
47
+ rule_id: _.propOr(null, "id")(rule),
48
+ domain_id: rule?.domain_id || null,
49
+ rule: rule,
50
+ }
51
+ );
52
+
53
+ const onChange = (prop, value) =>
54
+ setRuleImplementation((prevValue) => ({ ...prevValue, [prop]: value }));
55
+
56
+ const setImplementationKey = (implementationKey) =>
57
+ setRuleImplementation({ ...ruleImplementation, implementationKey });
58
+
59
+ const doSubmit = (props = {}) => {
60
+ const rule_implementation = {
61
+ executable: false,
62
+ implementation_key: ruleImplementation.implementationKey,
63
+ implementation_type: "basic",
64
+ df_name: ruleImplementation?.dfName,
65
+ df_content: ruleImplementation?.dfContent,
66
+ rule_id: ruleImplementation.rule_id,
67
+ result_type: ruleImplementation.result_type,
68
+ minimum: ruleImplementation.minimum,
69
+ goal: ruleImplementation.goal,
70
+ domain_id: ruleImplementation.domain_id,
71
+ status: props?.status,
72
+ };
73
+ clone || !edition
74
+ ? createRuleImplementation({
75
+ rule_implementation,
76
+ redirectUrl: null,
77
+ })
78
+ : updateRuleImplementation({
79
+ rule_implementation: {
80
+ ...rule_implementation,
81
+ id: ruleImplementation.id,
82
+ },
83
+ });
84
+ };
85
+
86
+ return (
87
+ <Grid divided className="full-height">
88
+ <Grid.Column width="11">
89
+ <Grid.Row>
90
+ <Header as="h2">
91
+ <Header.Content>
92
+ <FormattedMessage
93
+ id={
94
+ clone
95
+ ? "ruleImplementations.actions.create"
96
+ : edition
97
+ ? "ruleImplementations.actions.edit"
98
+ : "ruleImplementations.actions.create"
99
+ }
100
+ />
101
+ </Header.Content>
102
+ </Header>
103
+ <Divider hidden />
104
+ </Grid.Row>
105
+ <Grid.Row stretched>
106
+ <RuleImplementationBasicForm
107
+ mode={clone ? "clone" : edition ? "edition" : ""}
108
+ implementationKey={ruleImplementation.implementationKey}
109
+ setImplementationKey={setImplementationKey}
110
+ ruleImplementation={ruleImplementation}
111
+ onChange={onChange}
112
+ onSubmit={doSubmit}
113
+ />
114
+ </Grid.Row>
115
+ </Grid.Column>
116
+ <Grid.Column width="5"></Grid.Column>
117
+ </Grid>
118
+ );
119
+ };
120
+
121
+ NewBasicRuleImplementation.propTypes = {
122
+ clone: PropTypes.bool,
123
+ createRuleImplementation: PropTypes.func.isRequired,
124
+ edition: PropTypes.bool,
125
+ ruleImplementationProps: PropTypes.object,
126
+ updateRuleImplementation: PropTypes.func,
127
+ };
128
+
129
+ const mapStateToProps = (state) => ({
130
+ rule: state.rule,
131
+ ruleImplementationProps: {
132
+ ...state.ruleImplementation,
133
+ },
134
+ });
135
+
136
+ export default connect(mapStateToProps, {
137
+ createRuleImplementation,
138
+ updateRuleImplementation,
139
+ })(NewBasicRuleImplementation);
@@ -276,10 +276,7 @@ export const NewRuleImplementation = ({
276
276
  implementationKey: clone
277
277
  ? ""
278
278
  : ruleImplementationProps.implementation_key,
279
- implementationType:
280
- ruleImplementationProps.implementation_type == "draft"
281
- ? "default"
282
- : ruleImplementationProps.implementation_type,
279
+ implementationType: ruleImplementationProps.implementation_type,
283
280
  dataset: precalculatedDataset,
284
281
  populations: _.flow(
285
282
  _.pathOr([], "populations"),
@@ -6,11 +6,25 @@ import { Link, useParams } from "react-router-dom";
6
6
  import { Button } from "semantic-ui-react";
7
7
  import { linkTo } from "@truedat/core/routes";
8
8
 
9
- const RuleImplementationActions = ({ canCreate, canCreateRaw }) => {
9
+ const RuleImplementationActions = ({
10
+ canCreate,
11
+ canCreateRaw,
12
+ canCreateBasic,
13
+ }) => {
10
14
  const { formatMessage } = useIntl();
11
15
  const params = useParams();
12
16
  return (
13
17
  <>
18
+ {canCreateBasic && (
19
+ <Button
20
+ as={Link}
21
+ to={linkTo.IMPLEMENTATION_NEW_BASIC(params)}
22
+ className="primary_action"
23
+ content={formatMessage({
24
+ id: "implementations.actions.create_basic",
25
+ })}
26
+ />
27
+ )}
14
28
  {canCreateRaw && (
15
29
  <Button
16
30
  as={Link}
@@ -35,11 +49,13 @@ const RuleImplementationActions = ({ canCreate, canCreateRaw }) => {
35
49
  RuleImplementationActions.propTypes = {
36
50
  canCreate: PropTypes.bool,
37
51
  canCreateRaw: PropTypes.bool,
52
+ canCreateBasic: PropTypes.bool,
38
53
  };
39
54
 
40
55
  const mapStateToProps = ({ implementationActions }) => ({
41
56
  canCreate: !!implementationActions?.create,
42
57
  canCreateRaw: !!implementationActions?.createRaw,
58
+ canCreateBasic: !!implementationActions?.createBasic,
43
59
  });
44
60
 
45
61
  export default connect(mapStateToProps)(RuleImplementationActions);
@@ -102,6 +102,7 @@ export const RuleImplementationsActions = ({
102
102
  <RuleImplementationsOptions
103
103
  loading={ruleImplementationsLoading}
104
104
  canUploadResults={canUploadResults}
105
+ canCreateBasicImplementations={!!actions?.createBasicRuleLess}
105
106
  />
106
107
  </div>
107
108
  );
@@ -1,29 +1,52 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { Dropdown } from "semantic-ui-react";
4
+ import { useIntl } from "react-intl";
5
+ import { Link } from "react-router-dom";
6
+ import { IMPLEMENTATION_NEW_BASIC } from "@truedat/core/routes";
4
7
  import RuleImplementationsDownload from "./RuleImplementationsDownload";
5
8
  import RuleResultsUpload from "./RuleResultsUpload";
6
9
  import ImplementationsUploadButton from "./ImplementationsUploadButton";
7
10
 
8
- export const RuleImplementationsOptions = ({ loading, canUploadResults }) => (
9
- <Dropdown
10
- icon="ellipsis vertical"
11
- className="button icon group-actions button-update"
12
- direction="left"
13
- floating
14
- disabled={loading}
15
- >
16
- <Dropdown.Menu>
17
- <RuleImplementationsDownload />
18
- <ImplementationsUploadButton />
19
- {canUploadResults ? <RuleResultsUpload /> : null}
20
- </Dropdown.Menu>
21
- </Dropdown>
22
- );
11
+ export const RuleImplementationsOptions = ({
12
+ loading,
13
+ canUploadResults,
14
+ canCreateBasicImplementations,
15
+ }) => {
16
+ const { formatMessage } = useIntl();
17
+ return (
18
+ <Dropdown
19
+ icon="ellipsis vertical"
20
+ className="button icon group-actions button-update"
21
+ direction="left"
22
+ floating
23
+ disabled={loading}
24
+ >
25
+ <Dropdown.Menu>
26
+ {canCreateBasicImplementations ? (
27
+ <Dropdown.Item
28
+ name="implementation_basic"
29
+ icon="file alternate"
30
+ as={Link}
31
+ to={IMPLEMENTATION_NEW_BASIC}
32
+ content={formatMessage({
33
+ id: "implementations.actions.create_basic",
34
+ })}
35
+ onClick={(e) => e.stopPropagation()}
36
+ />
37
+ ) : null}
38
+ <RuleImplementationsDownload />
39
+ <ImplementationsUploadButton />
40
+ {canUploadResults ? <RuleResultsUpload /> : null}
41
+ </Dropdown.Menu>
42
+ </Dropdown>
43
+ );
44
+ };
23
45
 
24
46
  RuleImplementationsOptions.propTypes = {
25
47
  loading: PropTypes.bool,
26
48
  canUploadResults: PropTypes.bool,
49
+ canCreateBasicImplementations: PropTypes.bool,
27
50
  };
28
51
 
29
52
  export default RuleImplementationsOptions;
@@ -0,0 +1,166 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { waitFor } from "@testing-library/react";
4
+ import { render } from "@truedat/test/render";
5
+ import { DOMAIN_QUERY, DOMAINS_QUERY } from "@truedat/core/api/queries";
6
+ import { multipleTemplatesMock } from "@truedat/test/mocks";
7
+ import userEvent from "@testing-library/user-event";
8
+ import NewBasicRuleImplementation from "../NewBasicRuleImplementation";
9
+ import {
10
+ createRuleImplementation,
11
+ updateRuleImplementation,
12
+ } from "../../routines";
13
+
14
+ const ruleImplementationLoaderActions = {
15
+ createRuleImplementation,
16
+ updateRuleImplementation,
17
+ };
18
+
19
+ const ruleImplementation = {
20
+ deleted_at: null,
21
+ df_content: {},
22
+ df_name: "fooDomain",
23
+ domain_id: 1,
24
+ id: 1330,
25
+ implementation_key: "bar_impl",
26
+ implementation_type: "basic",
27
+ goal: 20,
28
+ minimum: 10,
29
+ result_type: "percentage",
30
+ results: [],
31
+ rule_id: null,
32
+ status: "draft",
33
+ version: 1,
34
+ };
35
+
36
+ const domainActions = ["publishImplementation", "manageSegments"];
37
+ const domains = [
38
+ {
39
+ id: "2",
40
+ externalId: "td",
41
+ name: "Truedat",
42
+ parentId: "",
43
+ actions: domainActions,
44
+ },
45
+ ];
46
+ const requestVariables = {
47
+ action: "manageBasicRulelessImplementations",
48
+ domainActions: domainActions,
49
+ };
50
+ const domainsMock = {
51
+ request: { query: DOMAINS_QUERY, variables: requestVariables },
52
+ result: { data: { domains: domains } },
53
+ };
54
+ const domainMock = {
55
+ request: {
56
+ query: DOMAIN_QUERY,
57
+ variables: { id: 2, actions: ["publishImplementation"] },
58
+ },
59
+ result: { data: { domain: { id: "2", actions: domainActions } } },
60
+ };
61
+
62
+ describe("<NewBasicRuleImplementation />", () => {
63
+ const createRuleImplementation = jest.fn();
64
+ const updateRuleImplementation = jest.fn();
65
+
66
+ const props = {
67
+ ruleImplementation,
68
+ createRuleImplementation,
69
+ updateRuleImplementation,
70
+ };
71
+ const renderOpts = {
72
+ mocks: [
73
+ multipleTemplatesMock({
74
+ scope: "ri",
75
+ domainIds: null,
76
+ }),
77
+ domainsMock,
78
+ domainMock,
79
+ ],
80
+ fallback: "lazy",
81
+ };
82
+
83
+ it("matches the latest snapshot", async () => {
84
+ const { container, queryByText } = render(
85
+ <NewBasicRuleImplementation {...props} />,
86
+ renderOpts
87
+ );
88
+
89
+ await waitFor(() => expect(queryByText(/lazy/i)).not.toBeInTheDocument());
90
+ await waitFor(() =>
91
+ expect(queryByText(/loading/i)).not.toBeInTheDocument()
92
+ );
93
+ expect(container).toMatchSnapshot();
94
+ });
95
+ });
96
+
97
+ describe("<NewBasicRuleImplementation> NewBasicRuleImplementation doSubmit", () => {
98
+ const dispatch = jest.fn();
99
+
100
+ const renderOptsImplementationEdit = {
101
+ mocks: [
102
+ multipleTemplatesMock({
103
+ scope: "ri",
104
+ domainIds: [1],
105
+ }),
106
+ domainsMock,
107
+ domainMock,
108
+ ],
109
+ state: {
110
+ rule: {},
111
+ ruleImplementation,
112
+ },
113
+ fallback: "lazy",
114
+ dispatch,
115
+ };
116
+
117
+ const props = {
118
+ ...ruleImplementationLoaderActions,
119
+ edition: true,
120
+ };
121
+
122
+ const expectedRuleImplementationBase = {
123
+ df_content: {},
124
+ df_name: "template1",
125
+ domain_id: 1,
126
+ executable: false,
127
+ goal: 20,
128
+ id: 1329 + 1,
129
+ implementation_key: "bar_impl",
130
+ implementation_type: "basic",
131
+ minimum: 10,
132
+ result_type: "percentage",
133
+ rule_id: null,
134
+ status: "draft",
135
+ };
136
+
137
+ it("doSubmit", async () => {
138
+ const expectedRuleImplementation = {
139
+ ...expectedRuleImplementationBase,
140
+ };
141
+
142
+ const { queryByText, getByRole, findByRole } = render(
143
+ <NewBasicRuleImplementation {...props} />,
144
+ renderOptsImplementationEdit
145
+ );
146
+
147
+ // Information Form
148
+
149
+ await waitFor(() => {
150
+ expect(queryByText(/Template/)).toBeTruthy();
151
+ });
152
+
153
+ userEvent.click(await findByRole("option", { name: "template1" }));
154
+
155
+ await waitFor(() => {
156
+ expect(getByRole("button", { name: "Save" })).toBeEnabled();
157
+ });
158
+
159
+ userEvent.click(await getByRole("button", { name: "Save" }));
160
+
161
+ expect(dispatch).toHaveBeenCalledWith({
162
+ ...ruleImplementationLoaderActions.updateRuleImplementation(),
163
+ payload: { rule_implementation: expectedRuleImplementation },
164
+ });
165
+ });
166
+ });