@truedat/dq 8.11.3 → 8.11.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/package.json +16 -16
- package/src/components/ExecutionForm.js +76 -75
- package/src/components/ExecutionPopup.js +27 -29
- package/src/components/ExecutionTemplateSelector.js +108 -0
- package/src/components/ExecutionWarning.js +36 -0
- package/src/components/ImplementationActions.js +4 -2
- package/src/components/RuleImplementation.js +2 -2
- package/src/components/Subscription.js +1 -1
- package/src/components/__tests__/ExecutionForm.spec.js +109 -15
- package/src/components/__tests__/ExecutionTemplateSelector.spec.js +168 -0
- package/src/components/__tests__/ExecutionWarning.spec.js +40 -0
- package/src/components/__tests__/RuleImplementation.spec.js +2 -2
- package/src/components/__tests__/__snapshots__/ExecutionForm.spec.js.snap +116 -72
- package/src/components/__tests__/__snapshots__/ExecutionPopup.spec.js.snap +8 -4
- package/src/components/__tests__/__snapshots__/ExecutionWarning.spec.js.snap +22 -0
- package/src/components/__tests__/__snapshots__/ImplementationActions.spec.js.snap +8 -4
- package/src/components/__tests__/__snapshots__/ImplementationSearchResults.spec.js.snap +18 -10
- package/src/components/__tests__/__snapshots__/RuleImplementation.spec.js.snap +79 -71
- package/src/components/__tests__/__snapshots__/RuleImplementationsActions.spec.js.snap +10 -6
- package/src/components/__tests__/__snapshots__/Subscription.spec.js.snap +1 -1
- package/src/styles/executionPopup.less +261 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dq",
|
|
3
|
-
"version": "8.11.
|
|
3
|
+
"version": "8.11.6",
|
|
4
4
|
"description": "Truedat Web Data Quality Module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -55,42 +55,42 @@
|
|
|
55
55
|
]
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@testing-library/dom": "^10.4.
|
|
59
|
-
"@testing-library/jest-dom": "^6.6.
|
|
60
|
-
"@testing-library/react": "^16.3.
|
|
61
|
-
"@testing-library/user-event": "^14.6.
|
|
62
|
-
"@truedat/test": "8.11.
|
|
58
|
+
"@testing-library/dom": "^10.4.0",
|
|
59
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
60
|
+
"@testing-library/react": "^16.3.0",
|
|
61
|
+
"@testing-library/user-event": "^14.6.1",
|
|
62
|
+
"@truedat/test": "8.11.6",
|
|
63
63
|
"identity-obj-proxy": "^3.0.0",
|
|
64
64
|
"jest": "^29.7.0",
|
|
65
65
|
"redux-saga-test-plan": "^4.0.6"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@apollo/client": "^3.13.
|
|
69
|
-
"axios": "^1.15.
|
|
68
|
+
"@apollo/client": "^3.13.8",
|
|
69
|
+
"axios": "^1.15.0",
|
|
70
70
|
"file-saver": "^2.0.5",
|
|
71
71
|
"graphql": "^16.11.0",
|
|
72
72
|
"is-hotkey": "^0.2.0",
|
|
73
73
|
"is-url": "^1.2.4",
|
|
74
|
-
"lodash": "^4.17.
|
|
74
|
+
"lodash": "^4.17.21",
|
|
75
75
|
"moment": "^2.30.1",
|
|
76
76
|
"path-to-regexp": "^8.2.0",
|
|
77
77
|
"prop-types": "^15.8.1",
|
|
78
78
|
"query-string": "^7.1.3",
|
|
79
|
-
"react": "^19.2.
|
|
79
|
+
"react": "^19.2.7",
|
|
80
80
|
"react-csv": "^2.2.2",
|
|
81
|
-
"react-dom": "^19.2.
|
|
81
|
+
"react-dom": "^19.2.7",
|
|
82
82
|
"react-dropzone": "^14.3.8",
|
|
83
83
|
"react-hook-form": "^7.56.4",
|
|
84
|
-
"react-intl": "^7.1.
|
|
85
|
-
"react-moment": "^1.1.
|
|
84
|
+
"react-intl": "^7.1.11",
|
|
85
|
+
"react-moment": "^1.1.3",
|
|
86
86
|
"react-redux": "^9.2.0",
|
|
87
|
-
"react-router": "^8.3.
|
|
87
|
+
"react-router": "^8.3.0",
|
|
88
88
|
"redux": "^5.0.1",
|
|
89
89
|
"redux-saga": "^1.3.0",
|
|
90
90
|
"redux-saga-routines": "^3.2.3",
|
|
91
91
|
"reselect": "^5.1.1",
|
|
92
92
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
93
|
-
"swr": "^2.3.
|
|
93
|
+
"swr": "^2.3.3"
|
|
94
94
|
},
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "bfd7e4d934a88ec244c45032ca76103f236e9462"
|
|
96
96
|
}
|
|
@@ -1,96 +1,96 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import {
|
|
3
|
-
import { Button,
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Button, Modal } from "semantic-ui-react";
|
|
4
4
|
import PropTypes from "prop-types";
|
|
5
5
|
import { useIntl } from "react-intl";
|
|
6
|
-
import { TemplateSelector } from "@truedat/core/components";
|
|
7
6
|
import { validateContent } from "@truedat/df/utils";
|
|
7
|
+
import { ExecutionTemplateSelector } from "./ExecutionTemplateSelector";
|
|
8
|
+
import { ExecutionWarning } from "./ExecutionWarning";
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
export const ExecutionForm = ({ count, onSubmit, onCancel }) => {
|
|
10
|
+
export const ExecutionForm = ({ count, onSubmit, onCancel, open }) => {
|
|
12
11
|
const { formatMessage } = useIntl();
|
|
13
12
|
const [content, setContent] = useState({});
|
|
14
13
|
const [template, setTemplate] = useState();
|
|
15
|
-
const [
|
|
16
|
-
|
|
17
|
-
const handleContentChange = (content) => setContent(content);
|
|
14
|
+
const [templates, setTemplates] = useState();
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (open) {
|
|
18
|
+
setTemplate(templates?.length === 1 ? templates[0] : undefined);
|
|
19
|
+
setContent({});
|
|
23
20
|
}
|
|
24
|
-
};
|
|
21
|
+
}, [open, templates]);
|
|
25
22
|
|
|
26
|
-
const
|
|
23
|
+
const handleTemplatesLoaded = ({ templates }) =>
|
|
24
|
+
setTemplates(templates || []);
|
|
25
|
+
|
|
26
|
+
const handleTemplateSelected = (e, { template }) => {
|
|
27
|
+
setTemplate(template);
|
|
28
|
+
setContent({});
|
|
29
|
+
};
|
|
27
30
|
|
|
28
|
-
const
|
|
29
|
-
template &&
|
|
31
|
+
const isExecuteDisabled =
|
|
32
|
+
!!template &&
|
|
33
|
+
!_.isEmpty(template ? validateContent(template)(content) : []);
|
|
30
34
|
|
|
31
35
|
return (
|
|
32
36
|
<>
|
|
33
|
-
<Header
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
onLoad={handleTemplatesLoaded}
|
|
45
|
-
clearable
|
|
46
|
-
/>
|
|
47
|
-
{template?.id ? (
|
|
48
|
-
<>
|
|
49
|
-
<Header
|
|
50
|
-
as="h3"
|
|
51
|
-
content={formatMessage({
|
|
52
|
-
id: "implementations.actions.execution.confirmation.legend",
|
|
37
|
+
<Modal.Header>
|
|
38
|
+
<div className="execution-popup-header">
|
|
39
|
+
<div>
|
|
40
|
+
{formatMessage({
|
|
41
|
+
id: "implementations.actions.execution.confirmation.header",
|
|
42
|
+
defaultMessage: "Implementations execution",
|
|
43
|
+
})}
|
|
44
|
+
<p className="subtitle">
|
|
45
|
+
{formatMessage({
|
|
46
|
+
id: "implementations.actions.execution.subtitle",
|
|
47
|
+
defaultMessage: "Select template for additional information",
|
|
53
48
|
})}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}}
|
|
60
|
-
>
|
|
61
|
-
<DynamicForm
|
|
62
|
-
onChange={handleContentChange}
|
|
63
|
-
content={content}
|
|
64
|
-
template={template}
|
|
65
|
-
/>
|
|
66
|
-
</div>
|
|
67
|
-
</>
|
|
68
|
-
) : null}
|
|
69
|
-
<p>
|
|
70
|
-
{formatMessage(
|
|
71
|
-
{
|
|
72
|
-
id:
|
|
73
|
-
count === 1
|
|
74
|
-
? "implementation.actions.execution.confirmation.content"
|
|
75
|
-
: "implementations.actions.execution.confirmation.content",
|
|
76
|
-
},
|
|
77
|
-
{ implementations_count: count },
|
|
78
|
-
)}
|
|
79
|
-
</p>
|
|
80
|
-
<div className="actions">
|
|
81
|
-
<Button
|
|
82
|
-
secondary
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className="close-button"
|
|
83
54
|
onClick={onCancel}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/>
|
|
55
|
+
aria-label={formatMessage({
|
|
56
|
+
id: "actions.close",
|
|
57
|
+
defaultMessage: "Close",
|
|
58
|
+
})}
|
|
59
|
+
>
|
|
60
|
+
×
|
|
61
|
+
</button>
|
|
92
62
|
</div>
|
|
93
|
-
</
|
|
63
|
+
</Modal.Header>
|
|
64
|
+
<Modal.Content className={template ? "has-template" : undefined}>
|
|
65
|
+
<ExecutionTemplateSelector
|
|
66
|
+
templates={templates}
|
|
67
|
+
template={template}
|
|
68
|
+
content={content}
|
|
69
|
+
onTemplateSelected={handleTemplateSelected}
|
|
70
|
+
onTemplatesLoaded={handleTemplatesLoaded}
|
|
71
|
+
onContentChange={setContent}
|
|
72
|
+
/>
|
|
73
|
+
{count !== 1 ? <ExecutionWarning count={count} /> : null}
|
|
74
|
+
</Modal.Content>
|
|
75
|
+
<Modal.Actions>
|
|
76
|
+
<Button
|
|
77
|
+
secondary
|
|
78
|
+
content={formatMessage({
|
|
79
|
+
id: "actions.cancel",
|
|
80
|
+
defaultMessage: "Cancel",
|
|
81
|
+
})}
|
|
82
|
+
onClick={onCancel}
|
|
83
|
+
/>
|
|
84
|
+
<Button
|
|
85
|
+
primary
|
|
86
|
+
disabled={isExecuteDisabled}
|
|
87
|
+
content={formatMessage({
|
|
88
|
+
id: "actions.create",
|
|
89
|
+
defaultMessage: "Execute",
|
|
90
|
+
})}
|
|
91
|
+
onClick={() => onSubmit(template ? content : {})}
|
|
92
|
+
/>
|
|
93
|
+
</Modal.Actions>
|
|
94
94
|
</>
|
|
95
95
|
);
|
|
96
96
|
};
|
|
@@ -99,6 +99,7 @@ ExecutionForm.propTypes = {
|
|
|
99
99
|
count: PropTypes.number,
|
|
100
100
|
onSubmit: PropTypes.func,
|
|
101
101
|
onCancel: PropTypes.func,
|
|
102
|
+
open: PropTypes.bool,
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
export default ExecutionForm;
|
|
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { connect } from "react-redux";
|
|
4
4
|
import { useIntl } from "react-intl";
|
|
5
|
-
import { Button,
|
|
5
|
+
import { Button, Modal } from "semantic-ui-react";
|
|
6
6
|
import ExecutionForm from "./ExecutionForm";
|
|
7
7
|
|
|
8
8
|
export const ExecutionPopup = ({
|
|
@@ -14,36 +14,34 @@ export const ExecutionPopup = ({
|
|
|
14
14
|
const { formatMessage } = useIntl();
|
|
15
15
|
const [open, setOpen] = useState(false);
|
|
16
16
|
|
|
17
|
+
const executeLabelId =
|
|
18
|
+
count === 1
|
|
19
|
+
? "implementation.actions.do_execution"
|
|
20
|
+
: "implementations.actions.do_execution";
|
|
21
|
+
|
|
17
22
|
return (
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
position="bottom center"
|
|
26
|
-
size="small"
|
|
27
|
-
trigger={
|
|
28
|
-
<Button
|
|
29
|
-
secondary
|
|
30
|
-
disabled={disabled}
|
|
31
|
-
loading={executionGroupLoading}
|
|
32
|
-
content={formatMessage({
|
|
33
|
-
id:
|
|
34
|
-
count === 1
|
|
35
|
-
? "implementation.actions.do_execution"
|
|
36
|
-
: "implementations.actions.do_execution",
|
|
37
|
-
})}
|
|
38
|
-
/>
|
|
39
|
-
}
|
|
40
|
-
>
|
|
41
|
-
<ExecutionForm
|
|
42
|
-
count={count}
|
|
43
|
-
onSubmit={onSubmit}
|
|
44
|
-
onCancel={() => setOpen(false)}
|
|
23
|
+
<div className="execution-popup-wrapper">
|
|
24
|
+
<Button
|
|
25
|
+
primary
|
|
26
|
+
disabled={disabled}
|
|
27
|
+
loading={executionGroupLoading}
|
|
28
|
+
content={formatMessage({ id: executeLabelId })}
|
|
29
|
+
onClick={() => setOpen(true)}
|
|
45
30
|
/>
|
|
46
|
-
|
|
31
|
+
<Modal
|
|
32
|
+
className="execution-popup-modal"
|
|
33
|
+
open={open}
|
|
34
|
+
onClose={() => setOpen(false)}
|
|
35
|
+
size="small"
|
|
36
|
+
>
|
|
37
|
+
<ExecutionForm
|
|
38
|
+
count={count}
|
|
39
|
+
onSubmit={onSubmit}
|
|
40
|
+
onCancel={() => setOpen(false)}
|
|
41
|
+
open={open}
|
|
42
|
+
/>
|
|
43
|
+
</Modal>
|
|
44
|
+
</div>
|
|
47
45
|
);
|
|
48
46
|
};
|
|
49
47
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { lazy } from "react";
|
|
2
|
+
import { Icon } from "semantic-ui-react";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import _ from "lodash/fp";
|
|
6
|
+
import { TemplateSelector } from "@truedat/core/components";
|
|
7
|
+
import { validateContent, flattenFields, isRequired } from "@truedat/df/utils";
|
|
8
|
+
|
|
9
|
+
const DynamicForm = lazy(() => import("@truedat/df/components/DynamicForm"));
|
|
10
|
+
|
|
11
|
+
export const ExecutionTemplateSelector = ({
|
|
12
|
+
templates,
|
|
13
|
+
template,
|
|
14
|
+
content,
|
|
15
|
+
onTemplateSelected,
|
|
16
|
+
onTemplatesLoaded,
|
|
17
|
+
onContentChange,
|
|
18
|
+
}) => {
|
|
19
|
+
const { formatMessage } = useIntl();
|
|
20
|
+
|
|
21
|
+
const validationErrors = template ? validateContent(template)(content) : [];
|
|
22
|
+
const hasPendingMandatoryFields = !_.isEmpty(validationErrors);
|
|
23
|
+
const templateHasMandatoryFields = template
|
|
24
|
+
? _.some((field) => isRequired(field, {}))(flattenFields(template))
|
|
25
|
+
: false;
|
|
26
|
+
|
|
27
|
+
const isSingleTemplate = templates?.length === 1;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="execution-popup-template-area">
|
|
31
|
+
{templates?.length !== 0 && !isSingleTemplate ? (
|
|
32
|
+
<div className="execution-popup-selector-label">
|
|
33
|
+
<span>
|
|
34
|
+
{formatMessage({
|
|
35
|
+
id: "implementations.actions.execution.template_label",
|
|
36
|
+
defaultMessage: "Select template",
|
|
37
|
+
})}
|
|
38
|
+
</span>
|
|
39
|
+
<span
|
|
40
|
+
className={
|
|
41
|
+
templateHasMandatoryFields
|
|
42
|
+
? hasPendingMandatoryFields
|
|
43
|
+
? "mandatory-badge pending"
|
|
44
|
+
: "mandatory-badge complete"
|
|
45
|
+
: "optional-badge"
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
{templateHasMandatoryFields
|
|
49
|
+
? formatMessage({
|
|
50
|
+
id: "implementations.actions.execution.mandatory_fields",
|
|
51
|
+
defaultMessage: "Mandatory fields",
|
|
52
|
+
})
|
|
53
|
+
: formatMessage({
|
|
54
|
+
id: "implementations.actions.execution.optional",
|
|
55
|
+
defaultMessage: "optional",
|
|
56
|
+
})}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
<TemplateSelector
|
|
61
|
+
scope="qe"
|
|
62
|
+
selectedValue={template?.id}
|
|
63
|
+
onChange={onTemplateSelected}
|
|
64
|
+
onLoad={onTemplatesLoaded}
|
|
65
|
+
clearable
|
|
66
|
+
hideLabel
|
|
67
|
+
/>
|
|
68
|
+
{isSingleTemplate ? (
|
|
69
|
+
<div className="execution-popup-single-template-label">
|
|
70
|
+
{formatMessage({
|
|
71
|
+
id: "implementations.actions.execution.label.single",
|
|
72
|
+
defaultMessage:
|
|
73
|
+
"Complete the template for additional information (optional)",
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
) : null}
|
|
77
|
+
{template?.id ? (
|
|
78
|
+
<div className="execution-popup-template-fields">
|
|
79
|
+
<h4>
|
|
80
|
+
<Icon name="info circle" size="small" />
|
|
81
|
+
{formatMessage({
|
|
82
|
+
id: "implementations.actions.execution.confirmation.legend",
|
|
83
|
+
defaultMessage: "Template fields",
|
|
84
|
+
})}
|
|
85
|
+
</h4>
|
|
86
|
+
<div className="execution-popup-template-scroll">
|
|
87
|
+
<DynamicForm
|
|
88
|
+
onChange={onContentChange}
|
|
89
|
+
content={content}
|
|
90
|
+
template={template}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
) : null}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
ExecutionTemplateSelector.propTypes = {
|
|
100
|
+
templates: PropTypes.array,
|
|
101
|
+
template: PropTypes.object,
|
|
102
|
+
content: PropTypes.object,
|
|
103
|
+
onTemplateSelected: PropTypes.func,
|
|
104
|
+
onTemplatesLoaded: PropTypes.func,
|
|
105
|
+
onContentChange: PropTypes.func,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default ExecutionTemplateSelector;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Icon } from "semantic-ui-react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
|
|
5
|
+
export const ExecutionWarning = ({ count }) => {
|
|
6
|
+
const { formatMessage } = useIntl();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="execution-popup-warning">
|
|
10
|
+
<Icon name="warning sign" color="yellow" size="large" />
|
|
11
|
+
<p>
|
|
12
|
+
{formatMessage(
|
|
13
|
+
{
|
|
14
|
+
id: "implementations.actions.execution.confirmation.warning",
|
|
15
|
+
defaultMessage:
|
|
16
|
+
"<b>{count}</b> implementations will be executed. Is this correct?",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
count,
|
|
20
|
+
b: (chunks) => (
|
|
21
|
+
<strong key="count" className="count">
|
|
22
|
+
{chunks}
|
|
23
|
+
</strong>
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
)}
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
ExecutionWarning.propTypes = {
|
|
33
|
+
count: PropTypes.number,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default ExecutionWarning;
|
|
@@ -101,7 +101,7 @@ export const ImplementationActions = ({
|
|
|
101
101
|
<>
|
|
102
102
|
{actions?.edit ? (
|
|
103
103
|
<Button
|
|
104
|
-
|
|
104
|
+
secondary
|
|
105
105
|
content={formatMessage({ id: "actions.edit" })}
|
|
106
106
|
as={Link}
|
|
107
107
|
to={linkTo.IMPLEMENTATION_EDIT({ implementation_id: id })}
|
|
@@ -134,7 +134,9 @@ export const ImplementationActions = ({
|
|
|
134
134
|
onClick={() => submitImplementation({ id })}
|
|
135
135
|
/>
|
|
136
136
|
) : null}
|
|
137
|
-
{actions?.execute ?
|
|
137
|
+
{actions?.execute ? (
|
|
138
|
+
<ExecutionPopup count={1} onSubmit={handleExecute} />
|
|
139
|
+
) : null}
|
|
138
140
|
{_.isEmpty(secondaryActions) ? null : (
|
|
139
141
|
<GroupActions availableActions={secondaryActions} />
|
|
140
142
|
)}
|
|
@@ -23,7 +23,7 @@ export const RuleImplementation = ({ children, ruleImplementation }) => {
|
|
|
23
23
|
</Header>
|
|
24
24
|
</Grid.Column>
|
|
25
25
|
<Grid.Column width={8} textAlign="right">
|
|
26
|
-
|
|
26
|
+
<div className="rule-implementation-actions">
|
|
27
27
|
{deletedAt ? null : (
|
|
28
28
|
<Subscription
|
|
29
29
|
resource={ruleImplementation}
|
|
@@ -31,7 +31,7 @@ export const RuleImplementation = ({ children, ruleImplementation }) => {
|
|
|
31
31
|
/>
|
|
32
32
|
)}
|
|
33
33
|
<ImplementationActions />
|
|
34
|
-
|
|
34
|
+
</div>
|
|
35
35
|
</Grid.Column>
|
|
36
36
|
</Grid>
|
|
37
37
|
<RuleImplementationTabs />
|
|
@@ -223,7 +223,7 @@ export const Subscription = ({
|
|
|
223
223
|
<Button
|
|
224
224
|
basic
|
|
225
225
|
icon={<Icon name="eye" className={className} />}
|
|
226
|
-
className="button icon group-actions"
|
|
226
|
+
className="button icon group-actions subscription-action-button"
|
|
227
227
|
data-tooltip={buttonMesage}
|
|
228
228
|
/>
|
|
229
229
|
}
|
|
@@ -1,24 +1,118 @@
|
|
|
1
1
|
import userEvent from "@testing-library/user-event";
|
|
2
2
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
multipleTemplatesMock,
|
|
5
|
+
singleTemplateMock,
|
|
6
|
+
templatesMockWith,
|
|
7
|
+
} from "@truedat/test/mocks";
|
|
4
8
|
import { ExecutionForm } from "../ExecutionForm";
|
|
5
9
|
|
|
10
|
+
const variables = { scope: "qe" };
|
|
11
|
+
const optionalTemplate = {
|
|
12
|
+
__typename: "Template",
|
|
13
|
+
id: "optional",
|
|
14
|
+
name: "optional",
|
|
15
|
+
scope: "qe",
|
|
16
|
+
label: "Optional template",
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
name: "additional_information",
|
|
20
|
+
fields: [{ name: "notes", label: "Notes" }],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
const requiredTemplate = {
|
|
25
|
+
...optionalTemplate,
|
|
26
|
+
id: "required",
|
|
27
|
+
label: "Required template",
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
name: "additional_information",
|
|
31
|
+
fields: [
|
|
32
|
+
{ name: "notes", label: "Notes", type: "string", cardinality: "1" },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const renderForm = (props = {}, mocks = [multipleTemplatesMock(variables)]) =>
|
|
39
|
+
render(
|
|
40
|
+
<ExecutionForm
|
|
41
|
+
count={2}
|
|
42
|
+
onSubmit={jest.fn()}
|
|
43
|
+
onCancel={jest.fn()}
|
|
44
|
+
open
|
|
45
|
+
{...props}
|
|
46
|
+
/>,
|
|
47
|
+
{ mocks },
|
|
48
|
+
);
|
|
49
|
+
|
|
6
50
|
describe("<ExecutionForm />", () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props = {
|
|
10
|
-
count: 1,
|
|
11
|
-
templates: [],
|
|
12
|
-
handleSubmit,
|
|
13
|
-
selectTemplate,
|
|
14
|
-
};
|
|
15
|
-
const renderOpts = {
|
|
16
|
-
mocks: [multipleTemplatesMock({ scope: "qe" })],
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
it("matches the latest snapshot", async () => {
|
|
20
|
-
const rendered = render(<ExecutionForm {...props} />, renderOpts);
|
|
51
|
+
it("shows the template selector and the bulk execution warning", async () => {
|
|
52
|
+
const rendered = renderForm();
|
|
21
53
|
await waitForLoad(rendered);
|
|
54
|
+
|
|
55
|
+
expect(rendered.queryByRole("radio")).not.toBeInTheDocument();
|
|
56
|
+
expect(
|
|
57
|
+
rendered.getByText("Select template for additional information"),
|
|
58
|
+
).toBeInTheDocument();
|
|
59
|
+
expect(rendered.getByText("Select template")).toBeInTheDocument();
|
|
60
|
+
expect(rendered.getByText("optional")).toHaveClass("optional-badge");
|
|
61
|
+
expect(rendered.getByRole("combobox")).toBeInTheDocument();
|
|
62
|
+
expect(rendered.getByText("2")).toHaveClass("count");
|
|
22
63
|
expect(rendered.container).toMatchSnapshot();
|
|
23
64
|
});
|
|
65
|
+
|
|
66
|
+
it("shows a single template directly without selector or info banner", async () => {
|
|
67
|
+
const rendered = renderForm({ count: 1 }, [singleTemplateMock(variables)]);
|
|
68
|
+
await waitForLoad(rendered);
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
rendered.getByText(
|
|
72
|
+
"Complete the template for additional information (optional)",
|
|
73
|
+
),
|
|
74
|
+
).toBeInTheDocument();
|
|
75
|
+
expect(rendered.queryByRole("combobox")).not.toBeInTheDocument();
|
|
76
|
+
expect(rendered.queryByText(/will be executed/i)).not.toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("enables execution when the template has no required fields", async () => {
|
|
80
|
+
const rendered = renderForm({ count: 1 }, [
|
|
81
|
+
templatesMockWith(variables, [optionalTemplate]),
|
|
82
|
+
]);
|
|
83
|
+
await waitForLoad(rendered);
|
|
84
|
+
|
|
85
|
+
expect(rendered.getByRole("button", { name: "Execute" })).toBeEnabled();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("marks a selected template as mandatory and switches from pending to complete", async () => {
|
|
89
|
+
const rendered = renderForm({ count: 1 }, [
|
|
90
|
+
templatesMockWith(variables, [requiredTemplate, optionalTemplate]),
|
|
91
|
+
]);
|
|
92
|
+
await waitForLoad(rendered);
|
|
93
|
+
const user = userEvent.setup({ delay: null });
|
|
94
|
+
|
|
95
|
+
await user.click(rendered.getByRole("combobox"));
|
|
96
|
+
await user.click(
|
|
97
|
+
rendered.getByRole("option", { name: "Required template" }),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const executeButton = rendered.getByRole("button", { name: "Execute" });
|
|
101
|
+
expect(rendered.getByText("Mandatory fields")).toHaveClass(
|
|
102
|
+
"mandatory-badge",
|
|
103
|
+
"pending",
|
|
104
|
+
);
|
|
105
|
+
expect(executeButton).toBeDisabled();
|
|
106
|
+
|
|
107
|
+
await user.type(
|
|
108
|
+
rendered.container.querySelector('input[name="notes"]'),
|
|
109
|
+
"Additional information",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(rendered.getByText("Mandatory fields")).toHaveClass(
|
|
113
|
+
"mandatory-badge",
|
|
114
|
+
"complete",
|
|
115
|
+
);
|
|
116
|
+
expect(executeButton).toBeEnabled();
|
|
117
|
+
});
|
|
24
118
|
});
|