@truedat/df 4.37.5 → 4.38.3

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.38.2]
4
+
5
+ ### Added
6
+
7
+ - [TD-2511] DynamicFormViewer support for editing individual fields
8
+
3
9
  ## [4.35.6] 2022-01-04
4
10
 
5
11
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/df",
3
- "version": "4.37.5",
3
+ "version": "4.38.3",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -30,7 +30,7 @@
30
30
  "@babel/plugin-transform-modules-commonjs": "^7.15.0",
31
31
  "@babel/preset-env": "^7.15.0",
32
32
  "@babel/preset-react": "^7.14.5",
33
- "@truedat/test": "4.34.0",
33
+ "@truedat/test": "4.38.3",
34
34
  "babel-jest": "^27.0.6",
35
35
  "babel-plugin-dynamic-import-node": "^2.3.3",
36
36
  "babel-plugin-lodash": "^3.3.4",
@@ -80,8 +80,8 @@
80
80
  ]
81
81
  },
82
82
  "dependencies": {
83
- "@truedat/auth": "4.37.5",
84
- "@truedat/core": "4.37.5",
83
+ "@truedat/auth": "4.38.3",
84
+ "@truedat/core": "4.38.3",
85
85
  "axios": "^0.19.2",
86
86
  "path-to-regexp": "^1.7.0",
87
87
  "prop-types": "^15.7.2",
@@ -100,5 +100,5 @@
100
100
  "react-dom": ">= 16.8.6 < 17",
101
101
  "semantic-ui-react": ">= 0.88.2 < 2.1"
102
102
  },
103
- "gitHead": "98ff8669e99824ef1bdbd3d12035e6371029e4ca"
103
+ "gitHead": "0ad3a7fe5d8d09b9c5e6e7007d8298748f6677fe"
104
104
  }
@@ -0,0 +1,64 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { Icon, List, Container } from "semantic-ui-react";
6
+ import SystemPreview from "./widgets/SystemPreview";
7
+ import FieldViewerValue from "./FieldViewerValue";
8
+ import "../styles/fieldGroupDetail.less";
9
+
10
+ export const DynamicFieldValue = (field) => {
11
+ const { label, value, type, values, widget, isSecret } = field;
12
+ return (
13
+ <List.Item>
14
+ <List.Header className="dynamic-field-header">{label}</List.Header>
15
+
16
+ <List.Description>
17
+ {_.isNil(value) || (!_.isNumber(value) && _.isEmpty(value)) ? (
18
+ isSecret ? (
19
+ <Container className="hidden-secret-value">
20
+ <FormattedMessage id="template.field.hidden_secret" />
21
+ </Container>
22
+ ) : (
23
+ <Icon name="minus" color="grey" />
24
+ )
25
+ ) : _.isArray(value) ? (
26
+ type == "table" ? (
27
+ <FieldViewerValue value={value} type={type} values={values} />
28
+ ) : type == "system" ? (
29
+ <SystemPreview value={value} />
30
+ ) : (
31
+ _.map.convert({ cap: false })((v, i) => (
32
+ <FieldViewerValue
33
+ key={i}
34
+ value={v}
35
+ type={type}
36
+ multiple
37
+ values={values}
38
+ />
39
+ ))(value)
40
+ )
41
+ ) : widget === "password" ? (
42
+ "*****"
43
+ ) : (
44
+ <FieldViewerValue
45
+ value={value}
46
+ type={type}
47
+ values={values}
48
+ widget={widget}
49
+ />
50
+ )}
51
+ </List.Description>
52
+ </List.Item>
53
+ );
54
+ };
55
+
56
+ DynamicFieldValue.propTypes = {
57
+ label: PropTypes.string,
58
+ value: PropTypes.any,
59
+ type: PropTypes.string,
60
+ values: PropTypes.any,
61
+ widget: PropTypes.string,
62
+ };
63
+
64
+ export default DynamicFieldValue;
@@ -3,19 +3,8 @@ import React, { useEffect } from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { connect } from "react-redux";
5
5
  import { makeGetApplyTemplate, makeGetTemplate } from "../selectors";
6
- import { enrichRequired } from "../utils";
6
+ import { parseGroups } from "../utils";
7
7
  import FieldGroupSegment from "./FieldGroupSegment";
8
- const hasDependentKeys = (field) =>
9
- !_.isNil(_.path("depends.to_be")(field)) &&
10
- !_.isNil(_.path("depends.on")(field));
11
-
12
- const checkDependency = (field, content) => {
13
- const on = _.prop(_.path("depends.on")(field))(content);
14
- const to_be = _.path("depends.to_be")(field);
15
- return _.isArray(on)
16
- ? _.some((d) => to_be.includes(d))(on)
17
- : to_be.includes(on);
18
- };
19
8
 
20
9
  export const DynamicForm = ({
21
10
  applyTemplate,
@@ -51,61 +40,12 @@ export const DynamicForm = ({
51
40
  onChange(newContent, {});
52
41
  };
53
42
 
54
- const stringToValueTextList = (values) =>
55
- _.map((v) => ({ value: v, text: v }))(values);
56
-
57
- const parseValues = (field) => {
58
- // FIXME: Refactor this function
59
- const value = content[field.name];
60
- if (field.values) {
61
- if ("switch" in field.values) {
62
- const values =
63
- field.values.switch.values[content[field.values.switch.on]];
64
- const parsed_values = stringToValueTextList(values);
65
- return { ...field, value, parsed_values };
66
- } else if ("fixed" in field.values) {
67
- const parsed_values = stringToValueTextList(field.values.fixed);
68
- return { ...field, value, parsed_values };
69
- } else if ("fixed_tuple" in field.values) {
70
- const parsed_values = field.values.fixed_tuple;
71
- return { ...field, value, parsed_values };
72
- } else if ("role_users" in field.values) {
73
- const values = _.isArray(field.values.processed_users)
74
- ? field.values.processed_users
75
- : [];
76
- const parsed_values = stringToValueTextList(values);
77
- return { ...field, value, parsed_values };
78
- } else if ("domain" in field.values) {
79
- const values = _.propOr(
80
- [],
81
- _.toString(selectedDomain?.id)
82
- )(field.values.domain);
83
- const parsed_values = stringToValueTextList(values);
84
- return { ...field, value, parsed_values };
85
- }
86
- }
87
-
88
- return { ...field, value };
89
- };
90
-
91
- const parsedGroups = _.flow(
92
- _.map((group) => {
93
- const fields = _.flow(
94
- _.filter((field) => !_.includes(fieldsToOmit)(field.name)),
95
- _.filter(
96
- (field) =>
97
- (!("depends" in field) ||
98
- (hasDependentKeys(field) && checkDependency(field, content))) &&
99
- (!(field.values && "switch" in field.values) ||
100
- content[field.values.switch.on] in field.values.switch.values)
101
- ),
102
- _.map(parseValues),
103
- _.map((field) => enrichRequired(field, content))
104
- )(group.fields);
105
- return { ...group, fields };
106
- }),
107
- _.filter(({ fields }) => _.negate(_.isEmpty)(fields))
108
- )(template.content);
43
+ const parsedGroups = parseGroups(
44
+ template,
45
+ content,
46
+ fieldsToOmit,
47
+ selectedDomain
48
+ );
109
49
 
110
50
  return (
111
51
  <>
@@ -3,21 +3,15 @@ import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { FormattedMessage } from "react-intl";
5
5
  import { Message } from "semantic-ui-react";
6
+ import { parseGroups } from "../utils";
6
7
  import FieldGroupDetail from "./FieldGroupDetail";
7
8
 
8
- const hasDependentKeys = (field) =>
9
- !_.isNil(_.path("depends.to_be")(field)) &&
10
- !_.isNil(_.path("depends.on")(field));
11
-
12
- const checkDependency = (field, content) => {
13
- const on = _.prop(_.path("depends.on")(field))(content);
14
- const to_be = _.path("depends.to_be")(field);
15
- return _.isArray(on)
16
- ? _.some((d) => to_be.includes(d))(on)
17
- : to_be.includes(on);
18
- };
19
-
20
- export const DynamicFormViewer = ({ template, content, boxLayout }) => {
9
+ export const DynamicFormViewer = ({
10
+ template,
11
+ content,
12
+ boxLayout,
13
+ editFunctions,
14
+ }) => {
21
15
  if (!template)
22
16
  return (
23
17
  <Message negative>
@@ -27,22 +21,7 @@ export const DynamicFormViewer = ({ template, content, boxLayout }) => {
27
21
  </Message>
28
22
  );
29
23
 
30
- const parsedGroups = _.flow(
31
- _.map((group) => {
32
- const fields = _.flow(
33
- _.filter(
34
- (field) =>
35
- (!("depends" in field) ||
36
- (hasDependentKeys(field) && checkDependency(field, content))) &&
37
- (!(field.values && "switch" in field.values) ||
38
- content[field.values.switch.on] in field.values.switch.values)
39
- ),
40
- _.map((field) => ({ ...field, value: content[field.name] }))
41
- )(group.fields);
42
- return { ...group, fields };
43
- }),
44
- _.filter(({ fields }) => _.negate(_.isEmpty)(fields))
45
- )(template.content);
24
+ const parsedGroups = parseGroups(template, content);
46
25
 
47
26
  return (
48
27
  <>
@@ -52,6 +31,7 @@ export const DynamicFormViewer = ({ template, content, boxLayout }) => {
52
31
  name={name}
53
32
  fields={fields}
54
33
  boxLayout={boxLayout}
34
+ editFunctions={editFunctions}
55
35
  />
56
36
  ))}
57
37
  </>
@@ -62,6 +42,7 @@ DynamicFormViewer.propTypes = {
62
42
  template: PropTypes.object,
63
43
  content: PropTypes.object,
64
44
  boxLayout: PropTypes.bool,
45
+ editFunctions: PropTypes.func,
65
46
  };
66
47
 
67
48
  export default DynamicFormViewer;
@@ -0,0 +1,137 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { useIntl } from "react-intl";
6
+ import { Icon, List, Button, Container } from "semantic-ui-react";
7
+ import SystemPreview from "./widgets/SystemPreview";
8
+ import DynamicField from "./widgets/DynamicField";
9
+ import FieldViewerValue from "./FieldViewerValue";
10
+ import "../styles/fieldGroupDetail.less";
11
+
12
+ export const EditableDynamicFieldValue = (field) => {
13
+ const { formatMessage } = useIntl();
14
+ const [updatedFailed, setUpdateFailed] = useState(false);
15
+ const { label, name, value, type, values, widget, isSecret, editFunctions } =
16
+ field;
17
+ const {
18
+ onChange,
19
+ onCancel,
20
+ onSubmit,
21
+ editingField,
22
+ setEditingField,
23
+ updateStatus,
24
+ } = editFunctions;
25
+
26
+ useEffect(() => {
27
+ if (editingField !== name) return;
28
+ switch (updateStatus) {
29
+ case "success":
30
+ setEditingField(null);
31
+ break;
32
+ case "fail":
33
+ setUpdateFailed(true);
34
+ break;
35
+ case "updating":
36
+ setUpdateFailed(false);
37
+ }
38
+ }, [editingField, name, updateStatus]);
39
+
40
+ const handleCancel = () => {
41
+ setEditingField(null);
42
+ onCancel();
43
+ };
44
+ const handleSubmit = () => {
45
+ onSubmit(name);
46
+ };
47
+ return (
48
+ <List.Item>
49
+ {!editingField ? (
50
+ <List.Header
51
+ className="editable-dynamic-field-header"
52
+ onClick={() => setEditingField(name)}
53
+ >
54
+ {label}
55
+ <Icon name={"pencil alternate"} />
56
+ </List.Header>
57
+ ) : (
58
+ <List.Header className="dynamic-field-header">{label}</List.Header>
59
+ )}
60
+
61
+ <List.Description>
62
+ {editingField === name ? (
63
+ <>
64
+ <DynamicField onChange={onChange} field={field} />
65
+ <Container className="editable-dynamic-field-buttons">
66
+ <Button
67
+ className={
68
+ updatedFailed ? "editable-dynamic-field-button-shake" : ""
69
+ }
70
+ onClick={handleSubmit}
71
+ positive
72
+ icon
73
+ size="mini"
74
+ loading={updateStatus === "updating"}
75
+ >
76
+ {formatMessage({ id: "template.field.action.save" })}
77
+ </Button>
78
+ <Button
79
+ onClick={handleCancel}
80
+ negative
81
+ icon
82
+ size="mini"
83
+ disabled={updateStatus === "updating"}
84
+ >
85
+ {formatMessage({ id: "template.field.action.cancel" })}
86
+ </Button>
87
+ </Container>
88
+ </>
89
+ ) : _.isNil(value) || (!_.isNumber(value) && _.isEmpty(value)) ? (
90
+ isSecret ? (
91
+ <Container className="hidden-secret-value">
92
+ <FormattedMessage id="template.field.hidden_secret" />
93
+ </Container>
94
+ ) : (
95
+ <Icon name="minus" color="grey" />
96
+ )
97
+ ) : _.isArray(value) ? (
98
+ type == "table" ? (
99
+ <FieldViewerValue value={value} type={type} values={values} />
100
+ ) : type == "system" ? (
101
+ <SystemPreview value={value} />
102
+ ) : (
103
+ _.map.convert({ cap: false })((v, i) => (
104
+ <FieldViewerValue
105
+ key={i}
106
+ value={v}
107
+ type={type}
108
+ multiple
109
+ values={values}
110
+ />
111
+ ))(value)
112
+ )
113
+ ) : widget === "password" ? (
114
+ "*****"
115
+ ) : (
116
+ <FieldViewerValue
117
+ value={value}
118
+ type={type}
119
+ values={values}
120
+ widget={widget}
121
+ />
122
+ )}
123
+ </List.Description>
124
+ </List.Item>
125
+ );
126
+ };
127
+
128
+ EditableDynamicFieldValue.propTypes = {
129
+ label: PropTypes.string,
130
+ value: PropTypes.any,
131
+ type: PropTypes.string,
132
+ values: PropTypes.any,
133
+ widget: PropTypes.string,
134
+ editFunctions: PropTypes.object,
135
+ };
136
+
137
+ export default EditableDynamicFieldValue;
@@ -1,98 +1,11 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
- import {
5
- Divider,
6
- Icon,
7
- Label,
8
- List,
9
- Button,
10
- Table,
11
- Segment,
12
- } from "semantic-ui-react";
13
- import DomainPreview from "./widgets/DomainPreview";
14
- import FieldGroupCopy from "./FieldGroupCopy";
15
- import ImagePreview from "./widgets/ImagePreview";
16
- import SystemPreview from "./widgets/SystemPreview";
4
+ import { Divider, List, Segment } from "semantic-ui-react";
5
+ import DynamicFieldValue from "./DynamicFieldValue";
6
+ import EditableDynamicFieldValue from "./EditableDynamicFieldValue";
17
7
  import "../styles/fieldGroupDetail.less";
18
8
 
19
- const RichTextEditor = React.lazy(() =>
20
- import("@truedat/core/components/RichTextEditor")
21
- );
22
-
23
- const ValueRenderByType = ({ value, type, multiple, values, widget }) => {
24
- switch (type) {
25
- case "url":
26
- return (
27
- <Button compact href={value.url_value} target="_blank">
28
- {value.url_name}
29
- </Button>
30
- );
31
- case "enriched_text":
32
- return <RichTextEditor readOnly value={value} />;
33
- case "table":
34
- const { table_columns } = values;
35
- return (
36
- <Table style={{ marginTop: "10px" }} celled striped compact fixed>
37
- <Table.Header>
38
- <Table.Row>
39
- {table_columns.map(({ name }, i) => (
40
- <Table.HeaderCell key={i}>{name}</Table.HeaderCell>
41
- ))}
42
- </Table.Row>
43
- </Table.Header>
44
-
45
- <Table.Body>
46
- {value.map((row, i) => (
47
- <Table.Row key={i}>
48
- {table_columns.map(({ name }, i) => (
49
- <Table.Cell key={i}>{row[name]}</Table.Cell>
50
- ))}
51
- </Table.Row>
52
- ))}
53
- </Table.Body>
54
- </Table>
55
- );
56
- case "image":
57
- return <ImagePreview value={value} />;
58
- case "system":
59
- return <SystemPreview value={value} />;
60
- case "domain":
61
- return <DomainPreview value={value} />;
62
- case "copy":
63
- return <FieldGroupCopy value={value} />;
64
- case "string":
65
- case "user":
66
- case "number":
67
- case "date":
68
- case "datetime":
69
- default:
70
- if (multiple) {
71
- return <Label>{value}</Label>;
72
- }
73
- return (
74
- <div
75
- className={[
76
- "default-value",
77
- widget === "identifier" ? "identifier-value" : "",
78
- ]
79
- .join(" ")
80
- .trim()}
81
- >
82
- {value}
83
- </div>
84
- );
85
- }
86
- };
87
-
88
- ValueRenderByType.propTypes = {
89
- value: PropTypes.any,
90
- type: PropTypes.string,
91
- values: PropTypes.any,
92
- multiple: PropTypes.bool,
93
- widget: PropTypes.string,
94
- };
95
-
96
9
  const parseField = (field) => {
97
10
  const { value, values } = field;
98
11
  if (_.has("fixed_tuple")(values)) {
@@ -114,60 +27,22 @@ const parseField = (field) => {
114
27
  }
115
28
  };
116
29
 
117
- const DynamicFieldValue = ({ label, value, type, values, widget }) => {
118
- return (
119
- <List.Item>
120
- <List.Header className="dynamic-field-header">{label}</List.Header>
121
- <List.Description>
122
- {_.isNil(value) || (!_.isNumber(value) && _.isEmpty(value)) ? (
123
- <Icon name="minus" color="grey" />
124
- ) : _.isArray(value) ? (
125
- type == "table" ? (
126
- <ValueRenderByType value={value} type={type} values={values} />
127
- ) : type == "system" ? (
128
- <SystemPreview value={value} />
129
- ) : (
130
- _.map.convert({ cap: false })((v, i) => (
131
- <ValueRenderByType
132
- key={i}
133
- value={v}
134
- type={type}
135
- multiple
136
- values={values}
137
- />
138
- ))(value)
139
- )
140
- ) : widget === "password" ? (
141
- "*****"
142
- ) : (
143
- <ValueRenderByType
144
- value={value}
145
- type={type}
146
- values={values}
147
- widget={widget}
148
- />
149
- )}
150
- </List.Description>
151
- </List.Item>
152
- );
153
- };
154
-
155
- DynamicFieldValue.propTypes = {
156
- label: PropTypes.string,
157
- value: PropTypes.any,
158
- type: PropTypes.string,
159
- values: PropTypes.any,
160
- widget: PropTypes.string,
161
- };
162
-
163
- export const FieldGroupDetail = ({ name, fields, boxLayout }) =>
164
- boxLayout ? (
30
+ export const FieldGroupDetail = ({
31
+ name,
32
+ fields,
33
+ boxLayout,
34
+ editFunctions,
35
+ }) => {
36
+ const FieldComponent = editFunctions
37
+ ? EditableDynamicFieldValue
38
+ : DynamicFieldValue;
39
+ return boxLayout ? (
165
40
  <>
166
41
  <Segment>
167
42
  {name && name !== "undefined" && <h4>{name}</h4>}
168
43
  <List>
169
44
  {fields.map(parseField).map((f, i) => (
170
- <DynamicFieldValue key={i} {...f} />
45
+ <FieldComponent key={i} {...f} editFunctions={editFunctions} />
171
46
  ))}
172
47
  </List>
173
48
  </Segment>
@@ -181,16 +56,17 @@ export const FieldGroupDetail = ({ name, fields, boxLayout }) =>
181
56
  )}
182
57
  <List size="big" relaxed="very">
183
58
  {fields.map(parseField).map((f, i) => (
184
- <DynamicFieldValue key={i} {...f} />
59
+ <FieldComponent key={i} {...f} editFunctions={editFunctions} />
185
60
  ))}
186
61
  </List>
187
62
  </>
188
63
  );
189
-
64
+ };
190
65
  FieldGroupDetail.propTypes = {
191
66
  name: PropTypes.string,
192
67
  fields: PropTypes.array,
193
68
  boxLayout: PropTypes.bool,
69
+ editFunctions: PropTypes.func,
194
70
  };
195
71
 
196
72
  export default FieldGroupDetail;
@@ -0,0 +1,88 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Label, Button, Table } from "semantic-ui-react";
5
+ import DomainPreview from "./widgets/DomainPreview";
6
+ import FieldGroupCopy from "./FieldGroupCopy";
7
+ import ImagePreview from "./widgets/ImagePreview";
8
+ import SystemPreview from "./widgets/SystemPreview";
9
+ import "../styles/fieldGroupDetail.less";
10
+
11
+ const RichTextEditor = React.lazy(() =>
12
+ import("@truedat/core/components/RichTextEditor")
13
+ );
14
+
15
+ export const FieldViewerValue = ({ value, type, multiple, values, widget }) => {
16
+ switch (type) {
17
+ case "url":
18
+ return (
19
+ <Button compact href={value.url_value} target="_blank">
20
+ {value.url_name}
21
+ </Button>
22
+ );
23
+ case "enriched_text":
24
+ return <RichTextEditor readOnly value={value} />;
25
+ case "table":
26
+ const { table_columns } = values;
27
+ return (
28
+ <Table style={{ marginTop: "10px" }} celled striped compact fixed>
29
+ <Table.Header>
30
+ <Table.Row>
31
+ {table_columns.map(({ name }, i) => (
32
+ <Table.HeaderCell key={i}>{name}</Table.HeaderCell>
33
+ ))}
34
+ </Table.Row>
35
+ </Table.Header>
36
+
37
+ <Table.Body>
38
+ {value.map((row, i) => (
39
+ <Table.Row key={i}>
40
+ {table_columns.map(({ name }, i) => (
41
+ <Table.Cell key={i}>{row[name]}</Table.Cell>
42
+ ))}
43
+ </Table.Row>
44
+ ))}
45
+ </Table.Body>
46
+ </Table>
47
+ );
48
+ case "image":
49
+ return <ImagePreview value={value} />;
50
+ case "system":
51
+ return <SystemPreview value={value} />;
52
+ case "domain":
53
+ return <DomainPreview value={value} />;
54
+ case "copy":
55
+ return <FieldGroupCopy value={value} />;
56
+ case "string":
57
+ case "user":
58
+ case "number":
59
+ case "date":
60
+ case "datetime":
61
+ default:
62
+ if (multiple) {
63
+ return <Label>{value}</Label>;
64
+ }
65
+ return (
66
+ <div
67
+ className={[
68
+ "default-value",
69
+ widget === "identifier" ? "identifier-value" : "",
70
+ ]
71
+ .join(" ")
72
+ .trim()}
73
+ >
74
+ {value}
75
+ </div>
76
+ );
77
+ }
78
+ };
79
+
80
+ FieldViewerValue.propTypes = {
81
+ value: PropTypes.any,
82
+ type: PropTypes.string,
83
+ values: PropTypes.any,
84
+ multiple: PropTypes.bool,
85
+ widget: PropTypes.string,
86
+ };
87
+
88
+ export default FieldViewerValue;