@truedat/dq 4.40.5 → 4.40.8

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 (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +5 -5
  3. package/src/api.js +2 -0
  4. package/src/components/ExecutionDetails.js +65 -40
  5. package/src/components/ImplementationResultBar.js +1 -1
  6. package/src/components/NewRemediation.js +62 -0
  7. package/src/components/RemediationCrumbs.js +76 -0
  8. package/src/components/RemediationForm.js +129 -0
  9. package/src/components/RemediationPlan.js +161 -0
  10. package/src/components/RuleImplementation.js +1 -1
  11. package/src/components/RuleImplementationResults.js +2 -1
  12. package/src/components/RuleResultRow.js +4 -1
  13. package/src/components/RuleRoutes.js +324 -296
  14. package/src/components/__tests__/ExecutionDetails.spec.js +18 -6
  15. package/src/components/__tests__/QualityEventError.spec.js +2 -2
  16. package/src/components/__tests__/RemediationForm.spec.js +68 -0
  17. package/src/components/__tests__/RemediationPlan.spec.js +106 -0
  18. package/src/components/__tests__/RuleRoutes.spec.js +1 -1
  19. package/src/components/__tests__/__snapshots__/ExecutionDetails.spec.js.snap +105 -1
  20. package/src/components/__tests__/__snapshots__/QualityEventError.spec.js.snap +1 -1
  21. package/src/components/__tests__/__snapshots__/RemediationForm.spec.js.snap +19 -0
  22. package/src/components/__tests__/__snapshots__/RemediationPlan.spec.js.snap +3 -0
  23. package/src/components/__tests__/__snapshots__/RuleRoutes.spec.js.snap +1 -1
  24. package/src/messages/en.js +7 -0
  25. package/src/messages/es.js +7 -0
  26. package/src/reducers/__tests__/remediation.spec.js +83 -0
  27. package/src/reducers/__tests__/remediationActions.spec.js +46 -0
  28. package/src/reducers/__tests__/remediationLoading.spec.js +50 -0
  29. package/src/reducers/dqMessage.js +8 -2
  30. package/src/reducers/index.js +6 -0
  31. package/src/reducers/remediation.js +35 -0
  32. package/src/reducers/remediationActions.js +19 -0
  33. package/src/reducers/remediationLoading.js +29 -0
  34. package/src/routines.js +6 -0
  35. package/src/sagas/__tests__/createRemediation.spec.js +83 -0
  36. package/src/sagas/__tests__/deleteRemediation.spec.js +82 -0
  37. package/src/sagas/__tests__/fetchRemediation.spec.js +78 -0
  38. package/src/sagas/__tests__/fetchRuleImplementation.spec.js +1 -1
  39. package/src/sagas/__tests__/updateRemediation.spec.js +81 -0
  40. package/src/sagas/createRemediation.js +31 -0
  41. package/src/sagas/deleteRemediation.js +29 -0
  42. package/src/sagas/fetchRemediation.js +30 -0
  43. package/src/sagas/index.js +12 -0
  44. package/src/sagas/updateRemediation.js +29 -0
  45. package/src/styles/executionDetails.less +3 -0
  46. package/src/styles/remediationPlan.less +8 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.40.8] 2022-03-18
4
+
5
+ ### Added
6
+
7
+ - [TD-3233] Rule result remediation plan forms
8
+
3
9
  ## [4.40.3] 2022-03-14
4
10
 
5
11
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/dq",
3
- "version": "4.40.5",
3
+ "version": "4.40.8",
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.40.5",
34
+ "@truedat/test": "4.40.8",
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.40.5",
86
- "@truedat/df": "4.40.5",
85
+ "@truedat/core": "4.40.8",
86
+ "@truedat/df": "4.40.8",
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": "0fbea5da0db04c2d79c6237e7bd450091988bf19"
106
+ "gitHead": "b3570c4151f142384ee7201d05f72ed274f2eeb9"
107
107
  }
package/src/api.js CHANGED
@@ -2,6 +2,7 @@ const API_CONCEPT_RULES = "/api/rules/concept/:id";
2
2
  const API_EXECUTION_GROUP = "/api/execution_groups/:id";
3
3
  const API_EXECUTION_GROUPS = "/api/execution_groups";
4
4
  const API_RULE = "/api/rules/:id";
5
+ const API_REMEDIATION_PLAN = "/api/rule_results/:rule_result_id/remediation";
5
6
  const API_RULES = "/api/rules";
6
7
  const API_RULES_SEARCH = "/api/rules/search";
7
8
  const API_RULES_UPLOAD = "/api/rules/upload";
@@ -25,6 +26,7 @@ export {
25
26
  API_CONCEPT_RULES,
26
27
  API_EXECUTION_GROUP,
27
28
  API_EXECUTION_GROUPS,
29
+ API_REMEDIATION_PLAN,
28
30
  API_RULE,
29
31
  API_RULES,
30
32
  API_RULES_SEARCH,
@@ -4,9 +4,12 @@ import PropTypes from "prop-types";
4
4
  import { useIntl } from "react-intl";
5
5
  import { connect } from "react-redux";
6
6
  import { useParams } from "react-router-dom";
7
- import { Table, Message, Icon } from "semantic-ui-react";
7
+ import { Header, Table, Icon } from "semantic-ui-react";
8
8
  import { DateTime } from "@truedat/core/components";
9
+ import { FormattedMessage } from "react-intl";
9
10
  import { selectColor } from "../functions/selectors";
11
+ import RemediationPlan from "./RemediationPlan";
12
+ import "../styles/executionDetails.less";
10
13
 
11
14
  const GeneralInformation = ({ ruleImplementation, ruleResult }) => {
12
15
  const { formatMessage, formatNumber: _formatNumber } = useIntl();
@@ -81,52 +84,69 @@ const DetailRow = ({ details }) => {
81
84
 
82
85
  DetailRow.propTypes = { details: PropTypes.object };
83
86
 
87
+ const TemplatesLoader = React.lazy(() =>
88
+ import("@truedat/df/templates/components/TemplatesLoader")
89
+ );
90
+
84
91
  export const ExecutionDetails = ({ ruleImplementation, ruleResultId }) => {
85
92
  const { formatMessage } = useIntl();
86
93
  const { rule_result_id: paramsId } = useParams();
87
94
 
88
- const result_id = _.defaultTo(_.toNumber(paramsId))(ruleResultId);
95
+ const resultId = _.defaultTo(_.toNumber(paramsId))(ruleResultId);
89
96
 
90
- const ruleResult = _.find(_.propEq("id", result_id))(
97
+ const ruleResult = _.find(_.propEq("id", resultId))(
91
98
  ruleImplementation.results
92
99
  );
93
100
 
94
- return (
95
- <Table className="implementation-results medium">
96
- <Table.Header>
97
- <Table.Row>
98
- <Table.HeaderCell
99
- colSpan={2}
100
- content={formatMessage({
101
- id: "ruleResult.props.header.information",
102
- })}
101
+ return _.isEmpty(ruleResult) ? null : (
102
+ <>
103
+ <Header as="h2">
104
+ <Icon circular name="chart pie" />
105
+ <Header.Content>
106
+ <FormattedMessage id="ruleResult" />
107
+ </Header.Content>
108
+ </Header>
109
+ <Table className="implementation-results medium">
110
+ <Table.Header>
111
+ <Table.Row>
112
+ <Table.HeaderCell
113
+ colSpan={2}
114
+ content={formatMessage({
115
+ id: "ruleResult.props.header.information",
116
+ })}
117
+ />
118
+ </Table.Row>
119
+ </Table.Header>
120
+ <Table.Body>
121
+ <GeneralInformation
122
+ ruleImplementation={ruleImplementation}
123
+ ruleResult={ruleResult}
103
124
  />
104
- </Table.Row>
105
- </Table.Header>
106
- <Table.Body>
107
- <GeneralInformation
108
- ruleImplementation={ruleImplementation}
109
- ruleResult={ruleResult}
110
- />
111
- </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}
129
- </Table>
125
+ </Table.Body>
126
+ {!_.isEmpty(ruleResult?.details) ? (
127
+ <>
128
+ <Table.Header>
129
+ <Table.Row>
130
+ <Table.HeaderCell
131
+ colSpan={2}
132
+ content={formatMessage({
133
+ id: "ruleResult.props.header.details",
134
+ })}
135
+ />
136
+ </Table.Row>
137
+ </Table.Header>
138
+ <Table.Body>
139
+ <DetailRow details={ruleResult?.details} />
140
+ </Table.Body>
141
+ </>
142
+ ) : null}
143
+ </Table>
144
+ <TemplatesLoader scope="remediation" />
145
+ <RemediationPlan
146
+ className="execution-details-remediation"
147
+ latestResultId={resultId}
148
+ />
149
+ </>
130
150
  );
131
151
  };
132
152
 
@@ -135,8 +155,13 @@ ExecutionDetails.propTypes = {
135
155
  ruleResultId: PropTypes.number,
136
156
  };
137
157
 
138
- const mapStateToProps = (state) => ({
139
- ruleImplementation: state.ruleImplementation,
158
+ const mapStateToProps = ({
159
+ ruleImplementation,
160
+ templatesLoading,
161
+ templates,
162
+ }) => ({
163
+ templatesLoaded: !templatesLoading && !_.isEmpty(templates),
164
+ ruleImplementation: ruleImplementation,
140
165
  });
141
166
 
142
167
  export default connect(mapStateToProps)(ExecutionDetails);
@@ -47,7 +47,7 @@ export default function ImplementationResultBar({ implementation }) {
47
47
  },
48
48
  };
49
49
  const summaryProps = propsByColor[color];
50
- return (
50
+ return _.isEmpty(implementation) ? null : (
51
51
  <div className="rule-summary-wrapper">
52
52
  <Link
53
53
  as="div"
@@ -0,0 +1,62 @@
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 { Button, Header, Icon, Segment } from "semantic-ui-react";
6
+ import { FormattedMessage, useIntl } from "react-intl";
7
+ import { createRemediation, fetchRuleImplementation } from "../routines";
8
+ import RemediationForm from "./RemediationForm";
9
+
10
+ export const NewRemediation = ({
11
+ manageRemediations,
12
+ onSaveRemediation,
13
+ templates,
14
+ }) => {
15
+ const { formatMessage } = useIntl();
16
+ const [isEdit, setIsEdit] = useState(false);
17
+
18
+ const onSave = () => {
19
+ setIsEdit(false);
20
+ onSaveRemediation();
21
+ };
22
+
23
+ return !manageRemediations || _.isEmpty(templates) ? null : (
24
+ <>
25
+ {!isEdit ? (
26
+ <Button
27
+ primary
28
+ onClick={() => setIsEdit(!isEdit)}
29
+ content={formatMessage({ id: "remediation.actions.create" })}
30
+ />
31
+ ) : (
32
+ <>
33
+ <Header as="h2">
34
+ <Icon name="plug" />
35
+ <Header.Content>
36
+ <FormattedMessage id="remediation.actions.create" />
37
+ </Header.Content>
38
+ </Header>
39
+ <Segment>
40
+ <RemediationForm onSave={onSave} />
41
+ </Segment>
42
+ </>
43
+ )}
44
+ </>
45
+ );
46
+ };
47
+
48
+ NewRemediation.propTypes = {
49
+ onSaveRemediation: PropTypes.func,
50
+ manageRemediations: PropTypes.bool,
51
+ templates: PropTypes.array,
52
+ };
53
+
54
+ const mapStateToProps = ({ templates, remediationActions }) => ({
55
+ manageRemediations: _.has("create")(remediationActions),
56
+ templates,
57
+ });
58
+
59
+ export default connect(mapStateToProps, {
60
+ fetchRuleImplementation,
61
+ createRemediation,
62
+ })(NewRemediation);
@@ -0,0 +1,76 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Breadcrumb } from "semantic-ui-react";
5
+ import { connect } from "react-redux";
6
+ import { Link, useParams } from "react-router-dom";
7
+ import { FormattedMessage } from "react-intl";
8
+ import { RULES, linkTo } from "@truedat/core/routes";
9
+
10
+ export const RemediationCrumbs = ({ rule, ruleImplementation }) => {
11
+ const { rule_result_id: ruleResultId } = useParams();
12
+
13
+ return rule?.name ? (
14
+ <Breadcrumb>
15
+ <Breadcrumb.Section as={Link} to={RULES} active={false}>
16
+ <FormattedMessage id="rules.crumbs.top" />
17
+ </Breadcrumb.Section>
18
+ {rule.name && (
19
+ <>
20
+ <Breadcrumb.Divider icon="right angle" />
21
+ {ruleImplementation.id ? (
22
+ <Breadcrumb.Section
23
+ as={Link}
24
+ to={linkTo.RULE({ id: rule.id })}
25
+ active={_.isEmpty(ruleImplementation)}
26
+ >
27
+ {rule.name}
28
+ </Breadcrumb.Section>
29
+ ) : (
30
+ <Breadcrumb.Section active>{rule.name}</Breadcrumb.Section>
31
+ )}
32
+ </>
33
+ )}
34
+ {ruleImplementation.id && (
35
+ <>
36
+ <Breadcrumb.Divider icon="right angle" />
37
+ {ruleResultId ? (
38
+ <Breadcrumb.Section
39
+ as={Link}
40
+ to={linkTo.RULE_IMPLEMENTATION({
41
+ id: rule.id,
42
+ implementation_id: ruleImplementation.id,
43
+ })}
44
+ active={_.isEmpty(ruleImplementation)}
45
+ >
46
+ {ruleImplementation.implementation_key}
47
+ </Breadcrumb.Section>
48
+ ) : (
49
+ <Breadcrumb.Section active>
50
+ {ruleImplementation.implementation_key}
51
+ </Breadcrumb.Section>
52
+ )}
53
+ </>
54
+ )}
55
+ {ruleResultId && (
56
+ <>
57
+ <Breadcrumb.Divider icon="right angle" />
58
+ <Breadcrumb.Section active>
59
+ <span>Result {ruleResultId}</span>
60
+ </Breadcrumb.Section>
61
+ </>
62
+ )}
63
+ </Breadcrumb>
64
+ ) : null;
65
+ };
66
+
67
+ RemediationCrumbs.propTypes = {
68
+ rule: PropTypes.object,
69
+ ruleImplementation: PropTypes.object,
70
+ };
71
+
72
+ const mapStateToProps = ({ rule, ruleImplementation }) => ({
73
+ rule,
74
+ ruleImplementation,
75
+ });
76
+ export default connect(mapStateToProps)(RemediationCrumbs);
@@ -0,0 +1,129 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useEffect, useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
5
+ import { applyTemplate, validateContent } from "@truedat/df/utils";
6
+ import { connect } from "react-redux";
7
+ import { useParams } from "react-router-dom";
8
+ import { Button, Form } from "semantic-ui-react";
9
+ import { createRemediation, updateRemediation } from "../routines";
10
+
11
+ const DynamicForm = React.lazy(() =>
12
+ import("@truedat/df/components/DynamicForm")
13
+ );
14
+
15
+ const TemplateSelector = React.lazy(() =>
16
+ import("@truedat/df/templates/components/TemplateSelector")
17
+ );
18
+
19
+ export const RemediationForm = ({
20
+ createRemediation,
21
+ updateRemediation,
22
+ templates,
23
+ remediation,
24
+ onSave,
25
+ latestResultId,
26
+ }) => {
27
+ const { formatMessage } = useIntl();
28
+ const [template, setTemplate] = useState();
29
+ const [content, setContent] = useState();
30
+ const {
31
+ id,
32
+ implementation_id: implementationId,
33
+ rule_result_id: ruleResultId,
34
+ } = useParams();
35
+
36
+ const isValidContent = _.flow(validateContent(template), _.isEmpty);
37
+ const isValid = _.has("name")(template) && isValidContent(content);
38
+
39
+ const handleTemplateSelected = (e, { value }) => {
40
+ setTemplate(_.find({ id: value }, templates));
41
+ };
42
+
43
+ const handleContentChange = (content) => {
44
+ setContent(content);
45
+ };
46
+
47
+ const handleSubmit = () => {
48
+ const actionFunction = _.has("id")(remediation)
49
+ ? updateRemediation
50
+ : createRemediation;
51
+
52
+ actionFunction({
53
+ remediation: {
54
+ ...remediation,
55
+ df_name: template?.name,
56
+ df_content: template ? applyTemplate(template)(content) : {},
57
+ },
58
+ id,
59
+ implementation_id: implementationId,
60
+ rule_result_id: ruleResultId || latestResultId,
61
+ });
62
+ onSave();
63
+ };
64
+
65
+ useEffect(() => {
66
+ _.flow(
67
+ _.cond([
68
+ [(templates) => _.size(templates) === 1, _.head],
69
+ [
70
+ () => !_.isEmpty(remediation),
71
+ _.find(_.propEq("name")(remediation?.df_name)),
72
+ ],
73
+ ]),
74
+ (template) => {
75
+ setTemplate(template);
76
+ return template;
77
+ },
78
+ (template) => applyTemplate(template)(remediation?.df_content),
79
+ setContent
80
+ )(templates);
81
+ }, [remediation, templates]);
82
+
83
+ return (
84
+ <Form aria-label="remediation-form">
85
+ {_.size(templates) > 1 && (
86
+ <TemplateSelector
87
+ name="template"
88
+ selectedValue={_.prop("id")(template)}
89
+ onChange={handleTemplateSelected}
90
+ />
91
+ )}
92
+
93
+ {!template ? null : (
94
+ <DynamicForm
95
+ template={template}
96
+ onChange={handleContentChange}
97
+ content={content || {}}
98
+ />
99
+ )}
100
+
101
+ <Button
102
+ primary
103
+ disabled={!isValid}
104
+ onClick={handleSubmit}
105
+ content={formatMessage({
106
+ id: _.isEmpty(remediation) ? "actions.create" : "actions.save",
107
+ })}
108
+ />
109
+ </Form>
110
+ );
111
+ };
112
+
113
+ RemediationForm.propTypes = {
114
+ createRemediation: PropTypes.func.isRequired,
115
+ updateRemediation: PropTypes.func,
116
+ templates: PropTypes.array,
117
+ remediation: PropTypes.object,
118
+ onSave: PropTypes.func,
119
+ latestResultId: PropTypes.number,
120
+ };
121
+
122
+ const mapStateToProps = ({ templates }) => ({
123
+ templates,
124
+ });
125
+
126
+ export default connect(mapStateToProps, {
127
+ createRemediation,
128
+ updateRemediation,
129
+ })(RemediationForm);
@@ -0,0 +1,161 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
5
+ import { useIntl } from "react-intl";
6
+ import { useParams } from "react-router-dom";
7
+ import { Button, Header, Icon, Grid, Segment } from "semantic-ui-react";
8
+ import { FormattedMessage } from "react-intl";
9
+ import { Loading } from "@truedat/core/components";
10
+ import { ConfirmModal } from "@truedat/core/components";
11
+ import {
12
+ fetchRemediation,
13
+ clearRemediation,
14
+ deleteRemediation,
15
+ } from "../routines";
16
+ import NewRemediation from "./NewRemediation";
17
+ import RemediationForm from "./RemediationForm";
18
+ import "../styles/remediationPlan.less";
19
+
20
+ const DynamicFormViewer = React.lazy(() =>
21
+ import("@truedat/df/components/DynamicFormViewer")
22
+ );
23
+
24
+ export const RemediationPlan = ({
25
+ remediationLoading,
26
+ className,
27
+ deleteRemediation,
28
+ fetchRemediation,
29
+ clearRemediation,
30
+ remediation,
31
+ templates,
32
+ latestResultId,
33
+ manageRemediations,
34
+ }) => {
35
+ const {
36
+ implementation_id: implementationId,
37
+ rule_result_id: paramsRuleResultId,
38
+ } = useParams();
39
+ const { formatMessage } = useIntl();
40
+ const [isEdit, setIsEdit] = useState(false);
41
+ const [ruleResultId, setRuleResultId] = useState(null);
42
+
43
+ useEffect(() => {
44
+ setRuleResultId(paramsRuleResultId || latestResultId);
45
+ }, [paramsRuleResultId, latestResultId]);
46
+
47
+ useEffect(() => {
48
+ ruleResultId && fetchRemediation({ rule_result_id: ruleResultId });
49
+ }, [fetchRemediation, ruleResultId]);
50
+
51
+ useEffect(
52
+ () => () => {
53
+ clearRemediation();
54
+ },
55
+ [clearRemediation]
56
+ );
57
+
58
+ const onSave = () => {
59
+ setIsEdit(false);
60
+ };
61
+
62
+ return !ruleResultId ? null : remediationLoading ? (
63
+ <Loading />
64
+ ) : _.isEmpty(remediation) && manageRemediations ? (
65
+ <NewRemediation onSaveRemediation={onSave} />
66
+ ) : (
67
+ <>
68
+ <Header as="h2">
69
+ <Icon circular name="rain" />
70
+ <Header.Content>
71
+ <FormattedMessage
72
+ id={!isEdit ? "remediation" : "remediation.actions.edit"}
73
+ />
74
+ </Header.Content>
75
+ </Header>
76
+
77
+ <Segment className={className}>
78
+ {!_.isEmpty(remediation) && manageRemediations ? (
79
+ <div className="ui actions remediation">
80
+ <ConfirmModal
81
+ icon="trash"
82
+ trigger={<Button secondary icon="trash" basic color="red" />}
83
+ header={
84
+ <FormattedMessage id="remediation.actions.delete.confirmation.header" />
85
+ }
86
+ content={
87
+ <FormattedMessage
88
+ id="remediation.actions.delete.confirmation.content"
89
+ values={{ ruleResultId: <b>{ruleResultId}</b> }}
90
+ />
91
+ }
92
+ onConfirm={() =>
93
+ deleteRemediation({
94
+ implementation_id: implementationId,
95
+ rule_result_id: ruleResultId,
96
+ })
97
+ }
98
+ />
99
+ <Button
100
+ className="button-edit-remediation"
101
+ primary
102
+ onClick={() => setIsEdit(!isEdit)}
103
+ content={formatMessage({
104
+ id: !isEdit ? "actions.edit" : "actions.cancel",
105
+ })}
106
+ />
107
+ </div>
108
+ ) : null}
109
+
110
+ <Grid>
111
+ <Grid.Column width={8}>
112
+ {!isEdit && remediation?.df_name ? (
113
+ <DynamicFormViewer
114
+ template={_.find(_.propEq("name", remediation.df_name))(
115
+ templates
116
+ )}
117
+ content={remediation.df_content}
118
+ />
119
+ ) : (
120
+ <RemediationForm
121
+ onSave={onSave}
122
+ remediation={remediation}
123
+ latestResultId={latestResultId}
124
+ />
125
+ )}
126
+ </Grid.Column>
127
+ </Grid>
128
+ </Segment>
129
+ </>
130
+ );
131
+ };
132
+
133
+ RemediationPlan.propTypes = {
134
+ remediationLoading: PropTypes.bool,
135
+ className: PropTypes.string,
136
+ deleteRemediation: PropTypes.func,
137
+ clearRemediation: PropTypes.func,
138
+ fetchRemediation: PropTypes.func,
139
+ remediation: PropTypes.object,
140
+ templates: PropTypes.array,
141
+ latestResultId: PropTypes.number,
142
+ manageRemediations: PropTypes.bool,
143
+ };
144
+
145
+ const mapStateToProps = ({
146
+ templates,
147
+ remediation,
148
+ remediationLoading,
149
+ remediationActions,
150
+ }) => ({
151
+ templates,
152
+ remediation,
153
+ remediationLoading,
154
+ manageRemediations: _.has("create")(remediationActions),
155
+ });
156
+
157
+ export default connect(mapStateToProps, {
158
+ deleteRemediation,
159
+ fetchRemediation,
160
+ clearRemediation,
161
+ })(RemediationPlan);
@@ -13,7 +13,7 @@ import RuleImplementationTabs from "./RuleImplementationTabs";
13
13
  import ImplementationResultBar from "./ImplementationResultBar";
14
14
 
15
15
  const getAvailableActions = (props, formatMessage) => {
16
- const contentActions = [
16
+ const contentActions = _.isEmpty(props?.ruleImplementation) ? [] : [
17
17
  {
18
18
  key: "delete",
19
19
  value: "delete",
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash/fp";
2
- import React from "react";
2
+ import React, { useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { useIntl } from "react-intl";
5
5
  import { connect } from "react-redux";
@@ -126,6 +126,7 @@ RuleImplementationResults.propTypes = {
126
126
  rule: PropTypes.object,
127
127
  customColumns: PropTypes.array,
128
128
  isAdmin: PropTypes.bool,
129
+ ruleResult: PropTypes.object,
129
130
  };
130
131
 
131
132
  const mapStateToProps = (state) => ({
@@ -38,7 +38,10 @@ export const RuleResultRow = ({
38
38
  >
39
39
  <DateTime className="rule-result-date" value={ruleResult.date} />
40
40
  {!_.isEmpty(ruleResult.details) ? (
41
- <Icon name="info circle" color="blue" />
41
+ <Icon circular inverted name="info" color="blue" size="small" />
42
+ ) : null}
43
+ {ruleResult.has_remediation ? (
44
+ <Icon circular inverted name="rain" color="blue" size="small" />
42
45
  ) : null}
43
46
  </Link>
44
47
  </Table.Cell>