@truedat/df 5.0.0 → 5.0.2

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 (25) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +5 -5
  3. package/src/api/hierarchies.js +4 -0
  4. package/src/components/hierarchies/Hierarchies.js +43 -0
  5. package/src/components/hierarchies/HierarchiesView.js +32 -0
  6. package/src/components/hierarchies/Hierarchy.js +301 -0
  7. package/src/components/hierarchies/HierarchyCrumbs.js +22 -0
  8. package/src/components/hierarchies/HierarchyRoutes.js +105 -0
  9. package/src/components/hierarchies/HierarchyView.js +261 -0
  10. package/src/components/hierarchies/__tests__/Hierarchies.spec.js +34 -0
  11. package/src/components/hierarchies/__tests__/HierarchiesView.spec.js +34 -0
  12. package/src/components/hierarchies/__tests__/Hierarchy.spec.js +42 -0
  13. package/src/components/hierarchies/__tests__/HierarchyCrumbs.spec.js +11 -0
  14. package/src/components/hierarchies/__tests__/HierarchyView.spec.js +36 -0
  15. package/src/components/hierarchies/__tests__/__snapshots__/Hierarchies.spec.js.snap +48 -0
  16. package/src/components/hierarchies/__tests__/__snapshots__/HierarchiesView.spec.js.snap +81 -0
  17. package/src/components/hierarchies/__tests__/__snapshots__/Hierarchy.spec.js.snap +186 -0
  18. package/src/components/hierarchies/__tests__/__snapshots__/HierarchyCrumbs.spec.js.snap +102 -0
  19. package/src/components/hierarchies/__tests__/__snapshots__/HierarchyView.spec.js.snap +145 -0
  20. package/src/components/hierarchies/index.js +3 -0
  21. package/src/components/index.js +1 -0
  22. package/src/hooks/useHierarchies.js +111 -0
  23. package/src/messages/en.js +94 -72
  24. package/src/messages/es.js +99 -77
  25. package/src/styles/hierarchy.less +91 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.2] 2023-01-26
4
+
5
+ ### Added
6
+
7
+ - [TD-3805] Hierarchy functionality
8
+
3
9
  ## [4.59.6] 2023-01-13
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/df",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
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": "5.0.0",
37
+ "@truedat/test": "5.0.2",
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",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@apollo/client": "^3.7.1",
90
- "@truedat/auth": "5.0.0",
91
- "@truedat/core": "5.0.0",
90
+ "@truedat/auth": "5.0.2",
91
+ "@truedat/core": "5.0.2",
92
92
  "decode-uri-component": "^0.2.2",
93
93
  "path-to-regexp": "^1.7.0",
94
94
  "prop-types": "^15.8.1",
@@ -109,5 +109,5 @@
109
109
  "react-dom": ">= 16.8.6 < 17",
110
110
  "semantic-ui-react": ">= 2.0.3 < 2.2"
111
111
  },
112
- "gitHead": "688cd20866f3c91f8390588f65d8c75402ad06e4"
112
+ "gitHead": "627767d6f31541ef09a077d7e535fb488a05700c"
113
113
  }
@@ -0,0 +1,4 @@
1
+ const API_HIERARCHIES = "/api/hierarchies";
2
+ const API_HIERARCHY = "/api/hierarchies/:id";
3
+
4
+ export { API_HIERARCHIES, API_HIERARCHY };
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import { Table } from "semantic-ui-react";
3
+ import { Link } from "react-router-dom";
4
+ import { Loading } from "@truedat/core/components";
5
+ import { FormattedMessage } from "react-intl";
6
+ import { linkTo } from "@truedat/core/routes";
7
+ import { useHierarchies } from "../../hooks/useHierarchies";
8
+
9
+ export const Hierarchies = () => {
10
+ const { data: hierarchies, loading } = useHierarchies();
11
+ return loading ? (
12
+ <Loading />
13
+ ) : hierarchies.length == 0 ? null : (
14
+ <Table celled>
15
+ <Table.Header>
16
+ <Table.Row>
17
+ <Table.HeaderCell>
18
+ <FormattedMessage id="hierarchy.name" />
19
+ </Table.HeaderCell>
20
+ <Table.HeaderCell>
21
+ <FormattedMessage id="hierarchy.description" />
22
+ </Table.HeaderCell>
23
+ </Table.Row>
24
+ </Table.Header>
25
+
26
+ <Table.Body>
27
+ {hierarchies.map((hierarchy) => {
28
+ const { id, name, description } = hierarchy;
29
+ return (
30
+ <Table.Row key={id}>
31
+ <Table.Cell>
32
+ <Link to={linkTo.HIERARCHY({ hierarchyId: id })}>{name}</Link>
33
+ </Table.Cell>
34
+ <Table.Cell>{description}</Table.Cell>
35
+ </Table.Row>
36
+ );
37
+ })}
38
+ </Table.Body>
39
+ </Table>
40
+ );
41
+ };
42
+
43
+ export default Hierarchies;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { Button, Header, Icon, Segment } from "semantic-ui-react";
3
+ import { Link } from "react-router-dom";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { HIERARCHY_CREATE } from "@truedat/core/routes";
6
+ import Hierarchies from "./Hierarchies";
7
+
8
+ export const HierarchiesView = () => (
9
+ <Segment>
10
+ <Header as="h2">
11
+ <Button
12
+ floated="right"
13
+ primary
14
+ as={Link}
15
+ to={HIERARCHY_CREATE}
16
+ content={<FormattedMessage id="hierarchy.create" />}
17
+ />
18
+ <Icon circular name="tree" />
19
+ <Header.Content>
20
+ <FormattedMessage id="hierarchies.header" />
21
+ <Header.Subheader>
22
+ <FormattedMessage id="hierarchies.subheader" />
23
+ </Header.Subheader>
24
+ </Header.Content>
25
+ </Header>
26
+ <Segment attached="bottom">
27
+ <Hierarchies />
28
+ </Segment>
29
+ </Segment>
30
+ );
31
+
32
+ export default HierarchiesView;
@@ -0,0 +1,301 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { useIntl } from "react-intl";
4
+ import { Icon, Grid, Label, Input, Form } from "semantic-ui-react";
5
+ import { DescriptionInput } from "@truedat/core/components";
6
+
7
+ const ListHierarchyItem = ({
8
+ node,
9
+ hierarchy,
10
+ onChange,
11
+ addNode,
12
+ removeNode,
13
+ isEditionMode,
14
+ }) => {
15
+ const { formatMessage } = useIntl();
16
+ const { id, name, error, editionStatus, description } = node;
17
+ const [expanded, setExpanded] = useState(false);
18
+ const [mouseOver, setMouseOver] = useState(false);
19
+ const generation = hierarchy.nodes.filter((n) => n.parentId == id);
20
+ const hasChildren = !generation.length == 0;
21
+
22
+ return (
23
+ <li
24
+ key={id}
25
+ onMouseOver={() => setMouseOver(true)}
26
+ onMouseOut={() => setMouseOver(false)}
27
+ >
28
+ <Icon
29
+ className="li_trash"
30
+ size="large"
31
+ name="cut"
32
+ title={formatMessage({ id: "hierarchy.delete_node" })}
33
+ style={{
34
+ cursor: "pointer",
35
+ visibility:
36
+ isEditionMode && !hasChildren && mouseOver ? "visible" : "hidden",
37
+ }}
38
+ onClick={() => removeNode(id)}
39
+ />
40
+
41
+ <div
42
+ style={{
43
+ display: "flex",
44
+ flexDirection: "row",
45
+ alignItems: "self-start",
46
+ }}
47
+ >
48
+ <div>
49
+ <Input
50
+ labelPosition="left"
51
+ type="text"
52
+ placeholder={formatMessage({ id: "hierarchy.node" })}
53
+ value={name == null ? "" : name}
54
+ error={error}
55
+ style={isEditionMode ? {} : { opacity: 1 }}
56
+ disabled={!isEditionMode}
57
+ onChange={(_e, { value }) =>
58
+ onChange(id, { name: value, description: description })
59
+ }
60
+ >
61
+ {!hasChildren ? (
62
+ isEditionMode && mouseOver && name && name != "" ? (
63
+ <Label
64
+ basic
65
+ title={formatMessage({ id: "hierarchy.add_child" })}
66
+ onClick={() => {
67
+ addNode(id, null);
68
+ setExpanded(true);
69
+ }}
70
+ >
71
+ <Icon
72
+ fitted
73
+ name="times circle outline"
74
+ style={{ transform: "rotate(45deg)", cursor: "pointer" }}
75
+ color={
76
+ editionStatus == "dirty"
77
+ ? "orange"
78
+ : editionStatus == "added"
79
+ ? "green"
80
+ : null
81
+ }
82
+ />
83
+ </Label>
84
+ ) : (
85
+ <Label basic>
86
+ <Icon
87
+ fitted
88
+ name="circle outline"
89
+ color={
90
+ editionStatus == "dirty"
91
+ ? "orange"
92
+ : editionStatus == "added"
93
+ ? "green"
94
+ : null
95
+ }
96
+ />
97
+ </Label>
98
+ )
99
+ ) : (
100
+ <Label basic title={expanded ? "Collapse" : "Expand"}>
101
+ <Icon
102
+ fitted
103
+ onClick={() => setExpanded(!expanded)}
104
+ style={{ cursor: "pointer" }}
105
+ color={
106
+ editionStatus == "dirty"
107
+ ? "orange"
108
+ : editionStatus == "added"
109
+ ? "green"
110
+ : null
111
+ }
112
+ name={expanded ? "dot circle outline" : "circle"}
113
+ />
114
+ </Label>
115
+ )}
116
+ <input />
117
+ </Input>
118
+ </div>
119
+
120
+ <div className="description_wrapper">
121
+ <DescriptionInput
122
+ disabled={!isEditionMode}
123
+ value={description}
124
+ onChange={(_e, { value }) =>
125
+ onChange(id, { name: name, description: value })
126
+ }
127
+ />
128
+ </div>
129
+ </div>
130
+ {generation.length == 0 || !expanded ? null : (
131
+ <ListHierarchy
132
+ hierarchy={hierarchy}
133
+ parentId={id}
134
+ onItemChange={onChange}
135
+ addNode={addNode}
136
+ removeNode={removeNode}
137
+ isEditionMode={isEditionMode}
138
+ />
139
+ )}
140
+ </li>
141
+ );
142
+ };
143
+
144
+ ListHierarchyItem.propTypes = {
145
+ node: PropTypes.object,
146
+ hierarchy: PropTypes.object,
147
+ onChange: PropTypes.func,
148
+ addNode: PropTypes.func,
149
+ removeNode: PropTypes.func,
150
+ isEditionMode: PropTypes.bool,
151
+ };
152
+
153
+ const ListHierarchy = ({
154
+ hierarchy,
155
+ parentId = null,
156
+ onItemChange,
157
+ addNode,
158
+ removeNode,
159
+ isEditionMode,
160
+ }) => {
161
+ const { formatMessage } = useIntl();
162
+ const [newNodeName, setNewNodeName] = useState();
163
+ const generation = hierarchy.nodes.filter(
164
+ (node) => node.parentId == parentId
165
+ );
166
+ const addNodeTo = (id = null) => (
167
+ <Form
168
+ onSubmit={() => {
169
+ addNode(id, newNodeName);
170
+ setNewNodeName("");
171
+ }}
172
+ >
173
+ <Input
174
+ labelPosition={newNodeName && newNodeName !== "" ? "right" : null}
175
+ onChange={(_e, { value }) => setNewNodeName(value)}
176
+ placeholder={formatMessage({ id: "hierarchy.new_node" })}
177
+ value={newNodeName}
178
+ >
179
+ <input />
180
+
181
+ {!newNodeName || newNodeName == "" ? null : (
182
+ <Label
183
+ style={{ cursor: "pointer" }}
184
+ onClick={() => {
185
+ addNode(id, newNodeName);
186
+ setNewNodeName("");
187
+ }}
188
+ >
189
+ {formatMessage({ id: "hierarchy.add" })}
190
+ </Label>
191
+ )}
192
+ </Input>
193
+ </Form>
194
+ );
195
+
196
+ return generation.length == 0 ? (
197
+ isEditionMode ? (
198
+ <ul className="wtree expanded">
199
+ <li>{addNodeTo()}</li>
200
+ </ul>
201
+ ) : null
202
+ ) : (
203
+ <ul className="wtree expanded">
204
+ {generation.map((node) => {
205
+ return (
206
+ <ListHierarchyItem
207
+ key={node.id}
208
+ node={node}
209
+ hierarchy={hierarchy}
210
+ onChange={onItemChange}
211
+ addNode={addNode}
212
+ removeNode={removeNode}
213
+ isEditionMode={isEditionMode}
214
+ />
215
+ );
216
+ })}
217
+ {isEditionMode ? (
218
+ <li className="add_node" key={generation.length}>
219
+ <div>{addNodeTo(parentId)}</div>
220
+ </li>
221
+ ) : null}
222
+ </ul>
223
+ );
224
+ };
225
+
226
+ ListHierarchy.propTypes = {
227
+ hierarchy: PropTypes.object,
228
+ parentId: PropTypes.number,
229
+ onItemChange: PropTypes.func,
230
+ addNode: PropTypes.func,
231
+ removeNode: PropTypes.func,
232
+ isEditionMode: PropTypes.bool,
233
+ };
234
+
235
+ const Hierarchy = ({
236
+ addNode,
237
+ hierarchy,
238
+ parentId = null,
239
+ updateMetadata,
240
+ updateNode,
241
+ removeNode,
242
+ isEditionMode,
243
+ }) => {
244
+ const { formatMessage } = useIntl();
245
+ return (
246
+ <Grid.Column>
247
+ <Grid.Row>
248
+ <div
249
+ style={{
250
+ display: "flex",
251
+ flexDirection: "row",
252
+ alignItems: "self-start",
253
+ }}
254
+ >
255
+ <Input
256
+ type="text"
257
+ placeholder={formatMessage({ id: "hierarchy.name" })}
258
+ disabled={!isEditionMode}
259
+ style={isEditionMode ? {} : { opacity: 1 }}
260
+ value={hierarchy.name}
261
+ onChange={(_e, { value }) =>
262
+ updateMetadata({
263
+ name: value,
264
+ description: hierarchy.description,
265
+ })
266
+ }
267
+ />
268
+ <div className="description_wrapper">
269
+ <DescriptionInput
270
+ disabled={!isEditionMode}
271
+ value={hierarchy.description}
272
+ onChange={(_e, { value }) =>
273
+ updateMetadata({ name: hierarchy.name, description: value })
274
+ }
275
+ />
276
+ </div>
277
+ </div>
278
+ </Grid.Row>
279
+ <ListHierarchy
280
+ hierarchy={hierarchy}
281
+ parentId={parentId}
282
+ onItemChange={updateNode}
283
+ addNode={addNode}
284
+ removeNode={removeNode}
285
+ isEditionMode={isEditionMode}
286
+ />
287
+ </Grid.Column>
288
+ );
289
+ };
290
+
291
+ Hierarchy.propTypes = {
292
+ addNode: PropTypes.func,
293
+ hierarchy: PropTypes.object,
294
+ parentId: PropTypes.number,
295
+ updateMetadata: PropTypes.func,
296
+ updateNode: PropTypes.func,
297
+ removeNode: PropTypes.func,
298
+ isEditionMode: PropTypes.bool,
299
+ };
300
+
301
+ export default Hierarchy;
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Breadcrumb } from "semantic-ui-react";
4
+ import { Link } from "react-router-dom";
5
+ import { FormattedMessage } from "react-intl";
6
+ import { HIERARCHIES } from "@truedat/core/routes";
7
+
8
+ export const HierarchyCrumbs = ({ name }) => (
9
+ <Breadcrumb>
10
+ <Breadcrumb.Section as={Link} to={HIERARCHIES} active={false}>
11
+ <FormattedMessage id="navigation.hierarchies" />
12
+ </Breadcrumb.Section>
13
+ <Breadcrumb.Divider icon="right angle" />
14
+ <Breadcrumb.Section active>{name}</Breadcrumb.Section>
15
+ </Breadcrumb>
16
+ );
17
+
18
+ HierarchyCrumbs.propTypes = {
19
+ name: PropTypes.string,
20
+ };
21
+
22
+ export default HierarchyCrumbs;
@@ -0,0 +1,105 @@
1
+ import React from "react";
2
+ import { Route, Switch } from "react-router-dom";
3
+ import { Unauthorized } from "@truedat/core/components";
4
+ import { useAuthorized } from "@truedat/core/hooks";
5
+ import { useParams } from "react-router-dom";
6
+ import { useHistory } from "react-router-dom";
7
+ import {
8
+ linkTo,
9
+ HIERARCHY,
10
+ HIERARCHY_EDIT,
11
+ HIERARCHY_CREATE,
12
+ HIERARCHIES,
13
+ } from "@truedat/core/routes";
14
+ import {
15
+ useInitialHiearchy,
16
+ useHierarchy,
17
+ useHierarchyPatch,
18
+ useHierarchyPost,
19
+ useHierarchyDelete,
20
+ } from "../../hooks/useHierarchies";
21
+
22
+ import HierarchiesView from "./HierarchiesView";
23
+ import HierarchyView from "./HierarchyView";
24
+
25
+ const HierarchyEdit = () => {
26
+ const history = useHistory();
27
+ const navigateToDetail = ({ id: id }) =>
28
+ history.push(linkTo.HIERARCHY({ hierarchyId: id }));
29
+ const { hierarchyId } = useParams();
30
+ const { trigger: patch } = useHierarchyPatch(hierarchyId, navigateToDetail);
31
+ const { data: originalHierarchy, loading } = useHierarchy(hierarchyId);
32
+ return originalHierarchy ? (
33
+ <HierarchyView
34
+ isEditionMode={true}
35
+ onSubmit={patch}
36
+ originalHierarchy={originalHierarchy}
37
+ loading={loading}
38
+ />
39
+ ) : null;
40
+ };
41
+
42
+ const HierarchyNew = () => {
43
+ const history = useHistory();
44
+ const navigateToDetail = ({ id: id }) =>
45
+ history.push(linkTo.HIERARCHY({ hierarchyId: id }));
46
+ const { trigger: post } = useHierarchyPost(navigateToDetail);
47
+ const { data: originalHierarchy, loading } = useInitialHiearchy();
48
+ return (
49
+ <HierarchyView
50
+ isEditionMode={true}
51
+ onSubmit={post}
52
+ originalHierarchy={originalHierarchy}
53
+ loading={loading}
54
+ />
55
+ );
56
+ };
57
+
58
+ const HierarchyDetail = () => {
59
+ const history = useHistory();
60
+ const navigateToList = () => history.push(linkTo.HIERARCHIES());
61
+ const { hierarchyId } = useParams();
62
+ const { trigger: onDelete } = useHierarchyDelete(hierarchyId, navigateToList);
63
+ const { data: originalHierarchy, loading } = useHierarchy(hierarchyId);
64
+ return originalHierarchy ? (
65
+ <HierarchyView
66
+ isEditionMode={false}
67
+ originalHierarchy={originalHierarchy}
68
+ loading={loading}
69
+ onDelete={onDelete}
70
+ />
71
+ ) : null;
72
+ };
73
+
74
+ export const HierarchyRoutes = () => {
75
+ const authorized = useAuthorized();
76
+ return (
77
+ <Route
78
+ path={HIERARCHIES}
79
+ render={() => (
80
+ <Switch>
81
+ <Route
82
+ exact
83
+ path={HIERARCHY_EDIT}
84
+ render={() => (authorized ? <HierarchyEdit /> : <Unauthorized />)}
85
+ />
86
+ <Route
87
+ exact
88
+ path={HIERARCHY_CREATE}
89
+ render={() => (authorized ? <HierarchyNew /> : <Unauthorized />)}
90
+ />
91
+ <Route
92
+ exact
93
+ path={HIERARCHY}
94
+ render={() => (authorized ? <HierarchyDetail /> : <Unauthorized />)}
95
+ />
96
+ <Route
97
+ render={() => (authorized ? <HierarchiesView /> : <Unauthorized />)}
98
+ />
99
+ </Switch>
100
+ )}
101
+ />
102
+ );
103
+ };
104
+
105
+ export default HierarchyRoutes;