@truedat/dq 4.49.5 → 4.49.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.
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useState } from "react";
2
+ import React, { useState, useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { useHistory } from "react-router-dom";
@@ -8,6 +8,8 @@ import { gql, useQuery } from "@apollo/client";
8
8
  import { Loading } from "@truedat/core/components";
9
9
  import { Button, Form, Icon, Popup } from "semantic-ui-react";
10
10
  import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
11
+ import { DomainSelector } from "@truedat/core/components";
12
+ import { DOMAIN_QUERY } from "@truedat/core/api/queries";
11
13
  import LimitsForm from "./LimitsForm";
12
14
  import { areLimitsValid } from "./limitsValidation";
13
15
 
@@ -15,9 +17,14 @@ const SelectableDynamicForm = React.lazy(() =>
15
17
  import("@truedat/df/components/SelectableDynamicForm")
16
18
  );
17
19
 
18
- const DomainDropdownSelector = React.lazy(() =>
19
- import("@truedat/bg/taxonomy/components/DomainDropdownSelector")
20
- );
20
+ const DomainActionsLoader = ({ id, actions, onLoad }) => {
21
+ useQuery(DOMAIN_QUERY, {
22
+ fetchPolicy: "cache-and-network",
23
+ variables: { id, actions },
24
+ onCompleted: onLoad,
25
+ });
26
+ return null;
27
+ };
21
28
 
22
29
  const Help = ({ message }) => {
23
30
  const { formatMessage } = useIntl();
@@ -38,7 +45,6 @@ Help.propTypes = {
38
45
  };
39
46
 
40
47
  export const RuleImplementationRawForm = ({
41
- actions,
42
48
  implementationKey,
43
49
  isSubmitting,
44
50
  onChange,
@@ -51,10 +57,30 @@ export const RuleImplementationRawForm = ({
51
57
  sources,
52
58
  sourcesLoading,
53
59
  }) => {
60
+ const domainActions = ["publishImplementation"];
54
61
  const { formatMessage } = useIntl();
55
62
  const history = useHistory();
56
63
  const [isContentValid, setIsContentValid] = useState();
57
64
 
65
+ const [domains, setDomains] = useState();
66
+ const [canPublish, setCanPublish] = useState(false);
67
+
68
+ useEffect(() => {
69
+ const currentDomainActions = _.flow(
70
+ _.find((domain) => domain.id === String(ruleImplementation.domain_id)),
71
+ _.pathOr([], "actions")
72
+ )(domains);
73
+
74
+ _.flow(
75
+ _.any((action) => action == "publishImplementation"),
76
+ setCanPublish
77
+ )(currentDomainActions);
78
+
79
+ return () => {
80
+ setCanPublish(false);
81
+ };
82
+ }, [domains, ruleImplementation.domain_id]);
83
+
58
84
  const handleContentChange = ({ content, valid }) => {
59
85
  onChange("dfContent", content);
60
86
  setIsContentValid(_.isEmpty(valid));
@@ -68,10 +94,9 @@ export const RuleImplementationRawForm = ({
68
94
  const hasNoErrors = (propName) =>
69
95
  _.path([propName, "hasErrors"])(errors) == false;
70
96
 
71
- const doSubmit = () => {
97
+ const doSubmit = (params) => {
72
98
  if (isValidForm()) {
73
- const canPublish = !!actions?.publish;
74
- onSubmit(canPublish ? { status: "published" } : {});
99
+ onSubmit(params);
75
100
  }
76
101
  };
77
102
 
@@ -113,7 +138,7 @@ export const RuleImplementationRawForm = ({
113
138
  ruleImplementation?.dfName &&
114
139
  !_.isEmpty(ruleImplementation?.dfName) &&
115
140
  isContentValid &&
116
- (!_.isEmpty(rule) || _.isNumber(_.prop("domain_id")(ruleImplementation))) &&
141
+ (!_.isEmpty(rule) || _.prop("domain_id")(ruleImplementation)) &&
117
142
  areLimitsValid(ruleImplementation);
118
143
 
119
144
  const isValidForm = () =>
@@ -147,7 +172,6 @@ export const RuleImplementationRawForm = ({
147
172
 
148
173
  const doCancel = () => history.goBack();
149
174
  const errors = getErrors();
150
-
151
175
  const domainId = ruleImplementation?.domain_id || rule?.domain_id;
152
176
  return (
153
177
  <Form className="rule">
@@ -175,13 +199,26 @@ export const RuleImplementationRawForm = ({
175
199
  disabled={ruleImplementation?.status === "published"}
176
200
  />
177
201
  </Form.Field>
178
- {_.isEmpty(rule) && !ruleImplementation?.rule_id && (
202
+ {_.isEmpty(rule) && !ruleImplementation?.rule_id ? (
179
203
  <Form.Field>
180
- <DomainDropdownSelector
181
- value={domainId}
204
+ <DomainSelector
205
+ action={
206
+ ruleImplementation.rule_id
207
+ ? "manageRawImplementations"
208
+ : "manageRawRulelessImplementations"
209
+ }
210
+ value={ruleImplementation.domain_id}
211
+ domainActions={domainActions}
212
+ onLoad={(data) => setDomains(data.domains)}
182
213
  onChange={(_e, { value }) => onChange("domain_id", value)}
183
214
  />
184
215
  </Form.Field>
216
+ ) : (
217
+ <DomainActionsLoader
218
+ id={domainId}
219
+ actions={domainActions}
220
+ onLoad={(data) => setDomains([data.domain])}
221
+ />
185
222
  )}
186
223
  <LimitsForm onChange={onChange} ruleImplementation={ruleImplementation} />
187
224
  <SelectableDynamicForm
@@ -303,13 +340,24 @@ export const RuleImplementationRawForm = ({
303
340
  size="small"
304
341
  value={_.prop("validations")(rawContent) || ""}
305
342
  />
306
- <Form.Button
343
+ {canPublish ? (
344
+ <Button
345
+ floated="right"
346
+ disabled={!isValidForm()}
347
+ type="submit"
348
+ primary
349
+ loading={isSubmitting}
350
+ onClick={() => doSubmit({ status: "published" })}
351
+ content={formatMessage({ id: "actions.publish" })}
352
+ />
353
+ ) : null}
354
+ <Button
307
355
  floated="right"
308
356
  disabled={!isValidForm()}
309
357
  type="submit"
310
358
  primary
311
359
  loading={isSubmitting}
312
- onClick={() => doSubmit()}
360
+ onClick={() => doSubmit({ status: "draft" })}
313
361
  content={formatMessage({ id: "actions.save" })}
314
362
  />
315
363
  <Button
@@ -323,7 +371,6 @@ export const RuleImplementationRawForm = ({
323
371
  };
324
372
 
325
373
  RuleImplementationRawForm.propTypes = {
326
- actions: PropTypes.object,
327
374
  implementationKey: PropTypes.string,
328
375
  isSubmitting: PropTypes.bool,
329
376
  onChange: PropTypes.func,
@@ -363,12 +410,7 @@ export const RuleImplementationRawFormLoader = (props) => {
363
410
  );
364
411
  };
365
412
 
366
- const mapStateToProps = ({
367
- implementationActions,
368
- rule,
369
- ruleImplementationCreating,
370
- }) => ({
371
- actions: implementationActions,
413
+ const mapStateToProps = ({ rule, ruleImplementationCreating }) => ({
372
414
  isSubmitting: ruleImplementationCreating,
373
415
  rule,
374
416
  });
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import { waitFor } from "@testing-library/react";
3
+ import { render } from "@truedat/test/render";
4
+ import { DOMAINS_QUERY } from "@truedat/core/api/queries";
5
+ import InformationForm from "../InformationForm";
6
+
7
+ const domains = [
8
+ {
9
+ id: "123",
10
+ name: "foo",
11
+ parentId: "",
12
+ actions: ["publishImplementation", "manageSegments"],
13
+ },
14
+ ];
15
+ const requestVariables = {
16
+ action: "manageRulelessImplementations",
17
+ domainActions: ["publishImplementation", "manageSegments"],
18
+ };
19
+ const domainsMock = {
20
+ request: { query: DOMAINS_QUERY, variables: requestVariables },
21
+ result: { data: { domains: domains } },
22
+ };
23
+ const renderOpts = {
24
+ mocks: [domainsMock],
25
+ state: {
26
+ rule: {},
27
+ ruleImplementationCreating: true,
28
+ },
29
+ fallback: "lazy",
30
+ };
31
+
32
+ describe("<RuleImplementationForm />", () => {
33
+ const props = {
34
+ setImplementationKey: jest.fn(),
35
+ onChange: jest.fn(),
36
+ setIsValid: jest.fn(),
37
+ onDomainsLoad: jest.fn(),
38
+ ruleImplementation: {
39
+ implementationKey: "foo",
40
+ executable: true,
41
+ result_type: "percentage",
42
+ goal: 20,
43
+ minimum: 10,
44
+ },
45
+ };
46
+
47
+ it("matches the latest snapshot", async () => {
48
+ const { container, queryByText } = render(
49
+ <InformationForm {...props} />,
50
+ renderOpts
51
+ );
52
+ await waitFor(() => expect(queryByText(/lazy/i)).not.toBeInTheDocument());
53
+ await waitFor(() =>
54
+ expect(queryByText(/loading/i)).not.toBeInTheDocument()
55
+ );
56
+
57
+ expect(container).toMatchSnapshot();
58
+ });
59
+ });
@@ -2,13 +2,30 @@ import React from "react";
2
2
  import { waitFor } from "@testing-library/react";
3
3
  import { render } from "@truedat/test/render";
4
4
  import { multipleTemplatesMock } from "@truedat/test/mocks";
5
+ import { DOMAINS_QUERY } from "@truedat/core/api/queries";
5
6
  import RuleImplementationForm from "../RuleImplementationForm";
6
7
 
8
+ const domains = [
9
+ {
10
+ id: "123",
11
+ name: "foo",
12
+ parentId: "",
13
+ actions: ["publishImplementation", "manageSegments"],
14
+ },
15
+ ];
16
+ const requestVariables = {
17
+ action: "manageRulelessImplementations",
18
+ domainActions: ["publishImplementation", "manageSegments"],
19
+ };
20
+
21
+ const domainsMock = {
22
+ request: { query: DOMAINS_QUERY, variables: requestVariables },
23
+ result: { data: { domains: domains } },
24
+ };
7
25
  const renderOpts = {
8
- mocks: [multipleTemplatesMock({ scope: "ri", domainIds: null })],
26
+ mocks: [multipleTemplatesMock({ scope: "ri", domainIds: null }), domainsMock],
9
27
  state: {
10
28
  rule: {},
11
- implementationActions: { manage_segments: {} },
12
29
  ruleImplementationCreating: true,
13
30
  },
14
31
  fallback: "lazy",
@@ -3,17 +3,32 @@ import React from "react";
3
3
  import { waitFor } from "@testing-library/react";
4
4
  import userEvent from "@testing-library/user-event";
5
5
  import { render } from "@truedat/test/render";
6
+ import { DOMAIN_QUERY, DOMAINS_QUERY } from "@truedat/core/api/queries";
6
7
  import { multipleTemplatesMock } from "@truedat/test/mocks";
7
8
  import { RuleImplementationRawForm } from "../RuleImplementationRawForm";
8
9
 
9
10
  jest.setTimeout(30000);
10
11
 
12
+ const domains = [{ id: 1, name: "domain1", actions: [] }];
13
+ const domainsMock = {
14
+ request: { query: DOMAINS_QUERY },
15
+ result: { data: { domains: domains } },
16
+ };
17
+
18
+ const domainMock = {
19
+ request: { query: DOMAIN_QUERY },
20
+ result: { data: { domain: domains } },
21
+ };
22
+
11
23
  const renderOpts = {
12
- mocks: [multipleTemplatesMock({ scope: "ri", domainIds: [1] })],
24
+ mocks: [
25
+ multipleTemplatesMock({ scope: "ri", domainIds: [1] }),
26
+ domainMock,
27
+ domainsMock,
28
+ ],
13
29
  state: {
14
30
  rule: { domain_id: 1 },
15
31
  ruleImplementationCreating: false,
16
- domains: [{ id: 1, name: "domain1" }],
17
32
  },
18
33
  fallback: "lazy",
19
34
  };
@@ -0,0 +1,260 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<RuleImplementationForm /> matches the latest snapshot 1`] = `
4
+ <div>
5
+ <div
6
+ class="field"
7
+ style=""
8
+ >
9
+ <div
10
+ class="ui checked toggle checkbox"
11
+ >
12
+ <input
13
+ checked=""
14
+ class="hidden"
15
+ name="executable"
16
+ readonly=""
17
+ tabindex="0"
18
+ type="radio"
19
+ value=""
20
+ />
21
+ <label>
22
+ Executable
23
+ </label>
24
+ </div>
25
+ </div>
26
+ <div
27
+ class="field"
28
+ style=""
29
+ >
30
+ <label>
31
+ Implementation Key
32
+ <i
33
+ aria-hidden="true"
34
+ class="question circle outline icon rule-form-popup"
35
+ />
36
+ </label>
37
+ <div
38
+ class="field"
39
+ >
40
+ <div
41
+ class="ui input"
42
+ >
43
+ <input
44
+ autocomplete="off"
45
+ name="implementation_key"
46
+ placeholder="Rule Implementation Key"
47
+ type="text"
48
+ value="foo"
49
+ />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ <div
54
+ class="required field"
55
+ style=""
56
+ >
57
+ <label>
58
+ Domain
59
+ </label>
60
+ <div
61
+ class="field"
62
+ >
63
+ <div
64
+ aria-expanded="false"
65
+ aria-multiselectable="false"
66
+ class="ui floating dropdown"
67
+ role="listbox"
68
+ tabindex="0"
69
+ >
70
+ <label>
71
+ Select a domain...
72
+ </label>
73
+ <i
74
+ aria-hidden="true"
75
+ class="dropdown icon"
76
+ />
77
+ <div
78
+ class="menu transition"
79
+ >
80
+ <div
81
+ class="ui left icon input search"
82
+ >
83
+ <input
84
+ type="text"
85
+ />
86
+ <i
87
+ aria-hidden="true"
88
+ class="search icon"
89
+ />
90
+ </div>
91
+ <div
92
+ class="scrolling menu transition"
93
+ >
94
+ <div
95
+ aria-selected="false"
96
+ class="item"
97
+ role="option"
98
+ >
99
+ <div
100
+ style="margin-left: 0px;"
101
+ >
102
+ <i
103
+ aria-hidden="true"
104
+ class="icon"
105
+ />
106
+ foo
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <div
115
+ class="ui segment"
116
+ style=""
117
+ >
118
+ <div
119
+ class="field"
120
+ >
121
+ <label
122
+ class="rule-form-label"
123
+ >
124
+ Result Type
125
+ <span>
126
+ *
127
+ </span>
128
+ <i
129
+ aria-hidden="true"
130
+ class="question circle outline icon rule-form-popup"
131
+ />
132
+ </label>
133
+ <div
134
+ class="inline fields"
135
+ >
136
+ <div
137
+ class="field"
138
+ >
139
+ <div
140
+ class="ui checked radio checkbox"
141
+ >
142
+ <input
143
+ checked=""
144
+ class="hidden"
145
+ name="result_type"
146
+ readonly=""
147
+ tabindex="0"
148
+ type="radio"
149
+ value="percentage"
150
+ />
151
+ <label>
152
+ Percentage
153
+ </label>
154
+ </div>
155
+ </div>
156
+ <div
157
+ class="field"
158
+ >
159
+ <div
160
+ class="ui radio checkbox"
161
+ >
162
+ <input
163
+ class="hidden"
164
+ name="result_type"
165
+ readonly=""
166
+ tabindex="0"
167
+ type="radio"
168
+ value="deviation"
169
+ />
170
+ <label>
171
+ Deviation
172
+ </label>
173
+ </div>
174
+ </div>
175
+ <div
176
+ class="field"
177
+ >
178
+ <div
179
+ class="ui radio checkbox"
180
+ >
181
+ <input
182
+ class="hidden"
183
+ name="result_type"
184
+ readonly=""
185
+ tabindex="0"
186
+ type="radio"
187
+ value="errors_number"
188
+ />
189
+ <label>
190
+ Error count
191
+ </label>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <div
197
+ class="field"
198
+ >
199
+ <label
200
+ class="rule-form-label"
201
+ >
202
+ Threshold
203
+ <span>
204
+ *
205
+ </span>
206
+ <i
207
+ aria-hidden="true"
208
+ class="question circle outline icon rule-form-popup"
209
+ />
210
+ </label>
211
+ <div
212
+ class="field"
213
+ >
214
+ <div
215
+ class="ui input"
216
+ >
217
+ <input
218
+ autocomplete="off"
219
+ name="minimum"
220
+ placeholder="Threshold value"
221
+ type="text"
222
+ value="10"
223
+ />
224
+ </div>
225
+ </div>
226
+ </div>
227
+ <div
228
+ class="field"
229
+ >
230
+ <label
231
+ class="rule-form-label"
232
+ >
233
+ Goal
234
+ <span>
235
+ *
236
+ </span>
237
+ <i
238
+ aria-hidden="true"
239
+ class="question circle outline icon rule-form-popup"
240
+ />
241
+ </label>
242
+ <div
243
+ class="field"
244
+ >
245
+ <div
246
+ class="ui input"
247
+ >
248
+ <input
249
+ autocomplete="off"
250
+ name="goal"
251
+ placeholder="Goal value"
252
+ type="text"
253
+ value="20"
254
+ />
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ `;