@truedat/lm 4.41.5 → 4.42.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.42.0] 2022-04-05
4
+
5
+ ### Added
6
+
7
+ - [TD-4513] Limit related concepts graph depth
8
+
3
9
  ## [4.40.5] 2022-03-15
4
10
 
5
11
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/lm",
3
- "version": "4.41.5",
3
+ "version": "4.42.3",
4
4
  "description": "Truedat Link Manager",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -33,12 +33,13 @@
33
33
  "@testing-library/jest-dom": "^5.14.1",
34
34
  "@testing-library/react": "^12.0.0",
35
35
  "@testing-library/user-event": "^13.2.1",
36
- "@truedat/test": "4.41.5",
36
+ "@truedat/test": "4.42.0",
37
37
  "babel-jest": "^27.0.6",
38
38
  "babel-plugin-dynamic-import-node": "^2.3.3",
39
39
  "babel-plugin-lodash": "^3.3.4",
40
40
  "babel-plugin-react-intl": "^5.1.18",
41
41
  "babel-plugin-transform-semantic-ui-react-imports": "^1.4.1",
42
+ "canvas": "^2.9.1",
42
43
  "enzyme": "^3.11.0",
43
44
  "enzyme-adapter-react-16": "^1.15.6",
44
45
  "enzyme-to-json": "^3.6.2",
@@ -83,7 +84,7 @@
83
84
  ]
84
85
  },
85
86
  "dependencies": {
86
- "@truedat/core": "4.41.5",
87
+ "@truedat/core": "4.42.3",
87
88
  "path-to-regexp": "^1.7.0",
88
89
  "prop-types": "^15.7.2",
89
90
  "react-graph-vis": "1.0.5",
@@ -100,5 +101,5 @@
100
101
  "react-dom": ">= 16.8.6 < 17",
101
102
  "semantic-ui-react": ">= 0.88.2 < 2.1"
102
103
  },
103
- "gitHead": "a4436b1f8d9fcf01424b44e6a7fc26ad1159a45c"
104
+ "gitHead": "5adf3b4e2996ade1df6c42dcacec8cf74807db3e"
104
105
  }
@@ -1,15 +1,27 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useState } from "react";
2
+ import React, { useEffect, useState } from "react";
3
3
  import { connect } from "react-redux";
4
4
  import { useIntl } from "react-intl";
5
5
  import PropTypes from "prop-types";
6
6
  import Graph from "react-graph-vis";
7
+ import "../styles/relationGraph.less";
8
+ import { dfsMaxDepth, dfs, getEdges } from "../services/relationGraphTraversal";
9
+ import RelationGraphDepth from "./RelationGraphDepth";
7
10
 
8
- export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
11
+ export const RelationGraph = ({
12
+ navigate,
13
+ currentId,
14
+ relationsGraph,
15
+ initialDepth,
16
+ }) => {
9
17
  const { formatMessage } = useIntl();
10
18
  const [network, setNetwork] = useState({});
19
+ const [depth, setDepth] = useState(initialDepth || Infinity);
20
+ const [maxDepth, setMaxDepth] = useState();
21
+ const [limitedRelationsGraph, setLimitedRelationsGraph] = useState();
22
+ const [start, setStart] = useState();
11
23
 
12
- const nodes = _.flow(
24
+ const getVisNodes = _.flow(
13
25
  _.get("nodes"),
14
26
  _.map(({ id, name: label }) => ({
15
27
  id,
@@ -19,7 +31,7 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
19
31
  currentId === id
20
32
  ? {
21
33
  background: "#ed5c17",
22
- border: "black"
34
+ border: "black",
23
35
  }
24
36
  : null,
25
37
 
@@ -28,28 +40,57 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
28
40
  ? {
29
41
  color: "white",
30
42
  bold: true,
31
- face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif"
43
+ face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif",
32
44
  }
33
- : null
45
+ : null,
34
46
  }))
35
- )(relationsGraph);
47
+ );
36
48
 
37
- const edges = _.flow(
49
+ const getVisEdges = _.flow(
38
50
  _.get("edges"),
39
51
  _.map(({ source_id: from, target_id: to, tags }) => ({
40
52
  from,
41
53
  to,
42
54
  label: _.flow(
43
- _.map(tag =>
55
+ _.map((tag) =>
44
56
  formatMessage({
45
57
  id: `source.${_.prop("value.type")(tag)}`,
46
- defaultMessage: _.prop("value.type")(tag)
58
+ defaultMessage: _.prop("value.type")(tag),
47
59
  })
48
60
  ),
49
61
  _.join("\n")
50
- )(tags)
62
+ )(tags),
51
63
  }))
52
- )(relationsGraph);
64
+ );
65
+
66
+ useEffect(() => {
67
+ if (!_.isEmpty(relationsGraph)) {
68
+ const start = _.flow(
69
+ _.getOr([], "nodes"),
70
+ _.find({ id: currentId })
71
+ )(relationsGraph);
72
+
73
+ const useEffectMaxDepth = dfsMaxDepth(relationsGraph, start, {}, 0);
74
+
75
+ setStart(start);
76
+ setMaxDepth(useEffectMaxDepth);
77
+ if (depth === Infinity) {
78
+ setDepth(useEffectMaxDepth);
79
+ }
80
+ }
81
+ }, [relationsGraph]);
82
+
83
+ useEffect(() => {
84
+ if (!_.isEmpty(relationsGraph) && !_.isEmpty(start) && depth !== Infinity) {
85
+ const nodes = dfs(relationsGraph, start, {}, depth);
86
+ depth < maxDepth
87
+ ? setLimitedRelationsGraph({
88
+ nodes,
89
+ edges: getEdges(relationsGraph, nodes),
90
+ })
91
+ : setLimitedRelationsGraph(relationsGraph);
92
+ }
93
+ }, [depth, start]);
53
94
 
54
95
  const options = {
55
96
  autoResize: true,
@@ -60,43 +101,43 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
60
101
  direction: "UD",
61
102
  sortMethod: "directed",
62
103
  levelSeparation: 170,
63
- edgeMinimization: false
64
- }
104
+ edgeMinimization: false,
105
+ },
65
106
  },
66
107
  edges: {
67
108
  color: "#000000",
68
109
  smooth: true,
69
110
  font: {
70
- face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif"
71
- }
111
+ face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif",
112
+ },
72
113
  },
73
114
  nodes: {
74
115
  shape: "box",
75
116
  margin: 10,
76
117
  color: {
77
118
  background: "white",
78
- border: "grey"
119
+ border: "grey",
79
120
  },
80
121
  widthConstraint: {
81
122
  maximum: 200,
82
- minimum: 100
123
+ minimum: 100,
83
124
  },
84
125
  font: {
85
- face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif"
86
- }
126
+ face: "Lato, Helvetica Neue, Arial, Helvetica, sans-serif",
127
+ },
87
128
  },
88
129
  physics: {
89
130
  hierarchicalRepulsion: {
90
- nodeDistance: 250
91
- }
131
+ nodeDistance: 250,
132
+ },
92
133
  },
93
134
  interaction: {
94
- hover: true
95
- }
135
+ hover: true,
136
+ },
96
137
  };
97
138
 
98
139
  const events = {
99
- select: event => {
140
+ select: (event) => {
100
141
  const { nodes, edges } = event;
101
142
  if (!_.isEmpty(nodes)) {
102
143
  const nodeId = _.head(nodes);
@@ -110,7 +151,7 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
110
151
  );
111
152
  }
112
153
  },
113
- hoverNode: event => {
154
+ hoverNode: (event) => {
114
155
  const { node } = event;
115
156
  if (node !== currentId) {
116
157
  const style = _.path("canvas.frame.style")(network);
@@ -122,18 +163,30 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
122
163
  const style = _.path("canvas.frame.style")(network);
123
164
  style.setProperty("cursor", "default");
124
165
  setNetwork(network);
125
- }
166
+ },
126
167
  };
127
168
  return (
128
169
  <>
129
- {!_.isEmpty(relationsGraph) && (
130
- <Graph
131
- graph={{ nodes, edges }}
132
- options={options}
133
- events={events}
134
- style={{ height: "640px" }}
135
- getNetwork={setNetwork}
136
- />
170
+ {!_.isEmpty(limitedRelationsGraph?.nodes) && (
171
+ <>
172
+ <RelationGraphDepth
173
+ onClick={(_e) => setDepth(maxDepth)}
174
+ onChange={(event) => setDepth(parseInt(event.target.value))}
175
+ depth={depth}
176
+ maxDepth={maxDepth}
177
+ />
178
+
179
+ <Graph
180
+ graph={{
181
+ nodes: getVisNodes(limitedRelationsGraph),
182
+ edges: getVisEdges(limitedRelationsGraph),
183
+ }}
184
+ options={options}
185
+ events={events}
186
+ style={{ height: "640px" }}
187
+ getNetwork={setNetwork}
188
+ />
189
+ </>
137
190
  )}
138
191
  </>
139
192
  );
@@ -142,9 +195,16 @@ export const RelationGraph = ({ navigate, currentId, relationsGraph }) => {
142
195
  RelationGraph.propTypes = {
143
196
  navigate: PropTypes.func,
144
197
  currentId: PropTypes.string,
145
- relationsGraph: PropTypes.object
198
+ relationsGraph: PropTypes.object,
199
+ initialDepth: PropTypes.number,
146
200
  };
147
201
 
148
- export const mapStateToProps = ({ relationsGraph }) => ({ relationsGraph });
202
+ export const mapStateToProps = ({
203
+ relationsGraph,
204
+ conceptGraphInitialDepth,
205
+ }) => ({
206
+ initialDepth: conceptGraphInitialDepth,
207
+ relationsGraph,
208
+ });
149
209
 
150
210
  export default connect(mapStateToProps)(RelationGraph);
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Button } from "semantic-ui-react";
4
+ import { FormattedMessage } from "react-intl";
5
+
6
+ export const RelationGraphDepth = ({ onClick, onChange, depth, maxDepth }) => {
7
+ return (
8
+ <div className="graph">
9
+ <label htmlFor="depth">
10
+ <FormattedMessage id="relationGraph.depth" /> [1-{maxDepth}]
11
+ </label>
12
+ <input
13
+ id="depth"
14
+ aria-label="depth"
15
+ type="number"
16
+ min="1"
17
+ max={maxDepth}
18
+ value={depth === Infinity ? 0 : depth}
19
+ onChange={onChange}
20
+ ></input>
21
+ <Button disabled={depth === maxDepth} onClick={onClick}>
22
+ <FormattedMessage id="relationGraph.all" />
23
+ </Button>
24
+ </div>
25
+ );
26
+ };
27
+
28
+ RelationGraphDepth.propTypes = {
29
+ onClick: PropTypes.func,
30
+ onChange: PropTypes.func,
31
+ depth: PropTypes.number,
32
+ maxDepth: PropTypes.number,
33
+ };
34
+
35
+ export default RelationGraphDepth;
@@ -1,93 +1,264 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
- import { shallow } from "enzyme";
3
+ import { mount } from "enzyme";
3
4
  import { intl } from "@truedat/test/intl-stub";
5
+ import userEvent from "@testing-library/user-event";
6
+ import { render } from "@truedat/test/render";
7
+ import { waitFor } from "@testing-library/react";
4
8
  import { RelationGraph } from "../RelationGraph";
5
9
 
6
10
  jest.spyOn(React, "useContext").mockImplementation(() => intl);
7
11
 
12
+ const renderOpts = {
13
+ messages: {
14
+ en: {
15
+ "relationGraph.all": "All",
16
+ "relationGraph.depth": "Depth",
17
+ },
18
+ },
19
+ };
20
+
8
21
  describe("<RelationGraph />", () => {
22
+ beforeEach(() => {
23
+ // Avoid `attachTo: document.body` Warning
24
+ const div = document.createElement("div");
25
+ div.setAttribute("id", "container");
26
+ document.body.appendChild(div);
27
+ });
28
+
29
+ afterEach(() => {
30
+ const div = document.getElementById("container");
31
+ if (div) {
32
+ document.body.removeChild(div);
33
+ }
34
+ });
35
+
9
36
  const nodes = [
10
37
  {
11
38
  id: "business_concept:92",
12
- label: "My Awesome Concept"
39
+ name: "My Awesome Concept",
13
40
  },
14
41
  {
15
42
  id: "business_concept:5",
16
- label: "New Concept 4"
43
+ name: "New Concept 4",
17
44
  },
18
45
  {
19
46
  id: "business_concept:18",
20
- label: "Float Concept"
21
- }
47
+ name: "Float Concept",
48
+ },
22
49
  ];
23
50
 
24
51
  const edges = [
25
52
  {
26
- from: "business_concept:92",
53
+ id: 1,
54
+ source_id: "business_concept:92",
55
+ target_id: "business_concept:5",
27
56
  tags: [
28
57
  {
29
58
  id: 3,
30
59
  value: {
31
60
  target_type: "business_concept",
32
- type: "bc_padre"
33
- }
34
- }
61
+ type: "bc_padre",
62
+ },
63
+ },
35
64
  ],
36
- to: "business_concept:5"
37
65
  },
38
66
  {
39
- from: "business_concept:5",
67
+ id: 2,
68
+ source_id: "business_concept:5",
69
+ target_id: "business_concept:18",
40
70
  tags: [
41
71
  {
42
72
  id: 3,
43
73
  value: {
44
74
  target_type: "business_concept",
45
- type: "bc_padre"
46
- }
47
- }
75
+ type: "bc_padre",
76
+ },
77
+ },
48
78
  ],
49
- to: "business_concept:18"
50
79
  },
51
80
  {
52
- from: "business_concept:18",
81
+ id: 3,
82
+ source_id: "business_concept:18",
83
+ target_id: "business_concept:5",
53
84
  tags: [
54
85
  {
55
86
  id: 3,
56
87
  value: {
57
88
  target_type: "business_concept",
58
- type: "bc_padre"
59
- }
60
- }
89
+ type: "bc_padre",
90
+ },
91
+ },
61
92
  ],
62
- to: "business_concept:5"
63
- }
93
+ },
64
94
  ];
65
95
 
66
96
  const relationsGraph = {
67
97
  nodes,
68
- edges
98
+ edges,
69
99
  };
70
100
 
71
101
  const defaultProps = {
72
- currentId: "1",
102
+ currentId: "business_concept:92",
73
103
  navigate: jest.fn(),
74
- relationsGraph: {}
104
+ relationsGraph: {},
75
105
  };
76
106
 
77
- it("matches the latest snapshot", () => {
107
+ it("renders Graph on non-empty relationsGraph", () => {
78
108
  const props = { ...defaultProps, relationsGraph };
79
- const wrapper = shallow(<RelationGraph {...props} />);
80
- expect(wrapper).toMatchSnapshot();
109
+
110
+ const wrapper = mount(<RelationGraph {...props} />, {
111
+ attachTo: document.getElementById("container"),
112
+ });
113
+
114
+ const graphComponent = wrapper.find("Graph");
115
+ expect(graphComponent.length).toBe(1);
116
+ const limitedGraph = graphComponent.prop("graph");
117
+
118
+ expect(
119
+ _.isUndefined(
120
+ _.find({ id: nodes[0].id, label: nodes[0].name }, limitedGraph.nodes)
121
+ ) &&
122
+ _.isUndefined(
123
+ _.find({ id: nodes[1].id, label: nodes[1].name }, limitedGraph.nodes)
124
+ ) &&
125
+ _.isUndefined(
126
+ _.find({ id: nodes[2].id, label: nodes[2].name }, limitedGraph.nodes)
127
+ )
128
+ ).toBeFalsy();
129
+
130
+ expect(
131
+ _.isUndefined(
132
+ _.find(
133
+ {
134
+ from: edges[0].source_id,
135
+ to: edges[0].target_id,
136
+ label: `source.${edges[0].tags[0].value.type}`,
137
+ },
138
+ limitedGraph.edges
139
+ )
140
+ ) &&
141
+ _.isUndefined(
142
+ _.find(
143
+ {
144
+ from: edges[1].source_id,
145
+ to: edges[1].target_id,
146
+ label: `source.${edges[1].tags[1].value.type}`,
147
+ },
148
+ limitedGraph.edges
149
+ )
150
+ ) &&
151
+ _.isUndefined(
152
+ _.find(
153
+ {
154
+ from: edges[2].source_id,
155
+ to: edges[2].target_id,
156
+ label: `source.${edges[2].tags[2].value.type}`,
157
+ },
158
+ limitedGraph.edges
159
+ )
160
+ )
161
+ ).toBeFalsy();
81
162
  });
82
163
 
83
- it("doest not render Graph when relationsGraph is empty", () => {
84
- const wrapper = shallow(<RelationGraph {...defaultProps} />);
164
+ it("does not render Graph on empty relationsGraph", () => {
165
+ const wrapper = mount(<RelationGraph {...defaultProps} />, {
166
+ attachTo: document.getElementById("container"),
167
+ });
85
168
  expect(wrapper.find("Graph").length).toBe(0);
86
169
  });
87
170
 
88
- it("render Graph when relationsGraph is not empty", () => {
171
+ it("sets initial depth", () => {
172
+ const initialDepth = 1;
173
+ const props = { ...defaultProps, relationsGraph, initialDepth };
174
+ const wrapper = mount(<RelationGraph {...props} />, {
175
+ attachTo: document.getElementById("container"),
176
+ });
177
+ const input = wrapper.find("input");
178
+ expect(input.prop("value")).toBe(initialDepth);
179
+ });
180
+
181
+ it("no initial depth defaults to max graph depth", () => {
89
182
  const props = { ...defaultProps, relationsGraph };
90
- const wrapper = shallow(<RelationGraph {...props} />);
91
- expect(wrapper.find("Graph").length).toBe(1);
183
+ const wrapper = mount(<RelationGraph {...props} />, {
184
+ attachTo: document.getElementById("container"),
185
+ });
186
+ const input = wrapper.find("input");
187
+ expect(input.prop("value")).toBe(3);
188
+ });
189
+
190
+ it("button sets max depth", async () => {
191
+ const initialDepth = 1;
192
+ const props = { ...defaultProps, relationsGraph, initialDepth };
193
+ const { getByRole, getByLabelText } = render(
194
+ <RelationGraph {...props} />,
195
+ renderOpts
196
+ );
197
+
198
+ userEvent.click(await getByRole("button"));
199
+ await waitFor(() => {
200
+ expect(parseInt(getByLabelText("depth").value)).toBe(3);
201
+ });
202
+ });
203
+
204
+ it("limits graph", async () => {
205
+ const props = { ...defaultProps, relationsGraph };
206
+ const wrapper = await mount(<RelationGraph {...props} />, {
207
+ attachTo: document.getElementById("container"),
208
+ });
209
+ const input = wrapper.find("input");
210
+ input.simulate("change", { target: { value: 2 } });
211
+ const limitedGraph = wrapper.find("Graph").prop("graph");
212
+
213
+ expect(
214
+ _.isUndefined(
215
+ _.find({ id: nodes[0].id, label: nodes[0].name }, limitedGraph.nodes)
216
+ ) &&
217
+ _.isUndefined(
218
+ _.find({ id: nodes[1].id, label: nodes[1].name }, limitedGraph.nodes)
219
+ )
220
+ ).toBeFalsy();
221
+
222
+ expect(
223
+ _.isUndefined(
224
+ _.find({ id: nodes[2].id, label: nodes[2].name }, limitedGraph.nodes)
225
+ )
226
+ ).toBeTruthy();
227
+
228
+ expect(
229
+ _.isUndefined(
230
+ _.find(
231
+ {
232
+ from: edges[0].source_id,
233
+ to: edges[0].target_id,
234
+ label: `source.${edges[0].tags[0].value.type}`,
235
+ },
236
+ limitedGraph.edges
237
+ )
238
+ )
239
+ ).toBeFalsy();
240
+
241
+ expect(
242
+ _.isUndefined(
243
+ _.find(
244
+ {
245
+ from: edges[1].source_id,
246
+ to: edges[1].target_id,
247
+ label: `source.${edges[1].tags[0].value.type}`,
248
+ },
249
+ limitedGraph.edges
250
+ )
251
+ ) &&
252
+ _.isUndefined(
253
+ _.find(
254
+ {
255
+ from: edges[2].source_id,
256
+ to: edges[2].target_id,
257
+ label: `source.${edges[2].tags[0].value.type}`,
258
+ },
259
+ limitedGraph.edges
260
+ )
261
+ )
262
+ ).toBeTruthy();
92
263
  });
93
264
  });
@@ -8,6 +8,8 @@ export default {
8
8
  "relation.actions.delete.confirmation.content":
9
9
  "The relation will be deleted. Are you sure?",
10
10
  "ingest.relatedTo.dataStructure": "Related to data structure",
11
+ "relationGraph.all": "All",
12
+ "relationGraph.depth": "Depth",
11
13
  "relationTags.header": "Relation Types",
12
14
  "relationTags.subheader": "In this section you can manage Relation Types",
13
15
  "relationTag.actions.delete.confirmation.header": "Delete Relation Type",
@@ -4,12 +4,13 @@ export default {
4
4
  "links.actions.create": "Añadir vinculación",
5
5
  "relations.actions.create": "Añadir relación",
6
6
  "relations.empty": "No se han encontrado relaciones",
7
-
8
7
  "relations.relationType": "Tipos de relaciones",
9
8
  "relation.actions.delete.confirmation.header": "Borrar Relación",
10
9
  "relation.actions.delete.confirmation.content":
11
10
  "La relación será eliminada. ¿Estás seguro?",
12
11
  "ingest.relatedTo.dataStructure": "Relacionado con estructura",
12
+ "relationGraph.all": "Todos",
13
+ "relationGraph.depth": "Profundidad",
13
14
  "relationTags.header": "Tipos de Relaciones",
14
15
  "relationTags.subheader": "Aquí podrás gestionar los tipos de relaciones",
15
16
  "relationTag.actions.delete.confirmation.header": "Borrar Tipo de Relación",
@@ -0,0 +1,60 @@
1
+ import _ from "lodash/fp";
2
+
3
+ const getNeighbours = (relationsGraph, start) => {
4
+ return _.concat(
5
+ // Up
6
+ _.flow(
7
+ _.filter((edge) => edge.target_id === start.id),
8
+ _.flatMap((edge) => _.find({ id: edge.source_id }, relationsGraph.nodes))
9
+ )(relationsGraph.edges),
10
+ // Down
11
+ _.flow(
12
+ _.filter((edge) => edge.source_id === start.id),
13
+ _.flatMap((edge) => _.find({ id: edge.target_id }, relationsGraph.nodes))
14
+ )(relationsGraph.edges)
15
+ );
16
+ };
17
+
18
+ export const getEdges = (relationsGraph, nodes) => {
19
+ const nodeIds = Object.keys(nodes);
20
+ return _.filter(
21
+ (edge) =>
22
+ nodeIds.includes(edge.source_id) && nodeIds.includes(edge.target_id),
23
+ relationsGraph.edges
24
+ );
25
+ };
26
+
27
+ export const dfs = (relationsGraph, start, visited, maxDepth) => {
28
+ if (Object.keys(visited).includes(start.id) || maxDepth === 0) return {};
29
+
30
+ return _.reduce(
31
+ (acc, neighbour) => ({
32
+ ...acc,
33
+ ...dfs(
34
+ relationsGraph,
35
+ neighbour,
36
+ { ...visited, [start.id]: start },
37
+ maxDepth - 1
38
+ ),
39
+ }),
40
+ { [start.id]: start }
41
+ )(getNeighbours(relationsGraph, start));
42
+ };
43
+
44
+ export const dfsMaxDepth = (relationsGraph, start, visited, currDepth) => {
45
+ if (Object.keys(visited).includes(start.id)) return currDepth;
46
+
47
+ return _.reduce(
48
+ (currMax, neighbour) =>
49
+ Math.max(
50
+ currMax,
51
+ dfsMaxDepth(
52
+ relationsGraph,
53
+ neighbour,
54
+ { ...visited, [start.id]: start },
55
+ currDepth + 1
56
+ )
57
+ ),
58
+ 0
59
+ )(getNeighbours(relationsGraph, start));
60
+ };
@@ -0,0 +1,34 @@
1
+
2
+ .graph{
3
+
4
+ div&{
5
+ position: absolute;
6
+ right: 0;
7
+ z-index: 1;
8
+ padding-right: 1rem;
9
+
10
+ input[type=number] {
11
+ vertical-align: top;
12
+ line-height: 34px;
13
+ width: 105px;
14
+ font-size: 20px;
15
+ text-align: center;
16
+ }
17
+
18
+ label {
19
+ position: absolute;
20
+ top: -20px;
21
+ }
22
+ .ui.button {
23
+ vertical-align: top;
24
+ width: 80px;
25
+ border-radius: 0 4px 4px 0;
26
+ border: 1px solid grey;
27
+ border-left: 0;
28
+ font-size: 14px;
29
+ }
30
+
31
+ }
32
+
33
+
34
+ }
@@ -1,107 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`<RelationGraph /> matches the latest snapshot 1`] = `
4
- <Fragment>
5
- <Graph
6
- events={
7
- Object {
8
- "blurNode": [Function],
9
- "hoverNode": [Function],
10
- "select": [Function],
11
- }
12
- }
13
- getNetwork={[Function]}
14
- graph={
15
- Object {
16
- "edges": Array [
17
- Object {
18
- "from": undefined,
19
- "label": "source.bc_padre",
20
- "to": undefined,
21
- },
22
- Object {
23
- "from": undefined,
24
- "label": "source.bc_padre",
25
- "to": undefined,
26
- },
27
- Object {
28
- "from": undefined,
29
- "label": "source.bc_padre",
30
- "to": undefined,
31
- },
32
- ],
33
- "nodes": Array [
34
- Object {
35
- "color": null,
36
- "font": null,
37
- "id": "business_concept:92",
38
- "label": undefined,
39
- },
40
- Object {
41
- "color": null,
42
- "font": null,
43
- "id": "business_concept:5",
44
- "label": undefined,
45
- },
46
- Object {
47
- "color": null,
48
- "font": null,
49
- "id": "business_concept:18",
50
- "label": undefined,
51
- },
52
- ],
53
- }
54
- }
55
- options={
56
- Object {
57
- "autoResize": true,
58
- "edges": Object {
59
- "color": "#000000",
60
- "font": Object {
61
- "face": "Lato, Helvetica Neue, Arial, Helvetica, sans-serif",
62
- },
63
- "smooth": true,
64
- },
65
- "height": "99%",
66
- "interaction": Object {
67
- "hover": true,
68
- },
69
- "layout": Object {
70
- "hierarchical": Object {
71
- "direction": "UD",
72
- "edgeMinimization": false,
73
- "levelSeparation": 170,
74
- "sortMethod": "directed",
75
- },
76
- },
77
- "nodes": Object {
78
- "color": Object {
79
- "background": "white",
80
- "border": "grey",
81
- },
82
- "font": Object {
83
- "face": "Lato, Helvetica Neue, Arial, Helvetica, sans-serif",
84
- },
85
- "margin": 10,
86
- "shape": "box",
87
- "widthConstraint": Object {
88
- "maximum": 200,
89
- "minimum": 100,
90
- },
91
- },
92
- "physics": Object {
93
- "hierarchicalRepulsion": Object {
94
- "nodeDistance": 250,
95
- },
96
- },
97
- "width": "99%",
98
- }
99
- }
100
- style={
101
- Object {
102
- "height": "640px",
103
- }
104
- }
105
- />
106
- </Fragment>
107
- `;