@truedat/bg 8.4.7 → 8.4.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.
- package/package.json +7 -4
- package/src/concepts/relations/components/ConceptRelations.js +98 -77
- package/src/concepts/relations/components/SliderWithFilter.js +198 -0
- package/src/concepts/relations/components/__tests__/ConceptRelations.spec.js +159 -15
- package/src/concepts/relations/components/__tests__/SliderWithFilter.spec.js +134 -0
- package/src/concepts/relations/components/__tests__/__snapshots__/ConceptRelations.spec.js.snap +86 -52
- package/src/concepts/relations/components/__tests__/__snapshots__/SliderWithFilter.spec.js.snap +55 -0
- package/src/concepts/relations/styles/sliderWithFilter.less +202 -0
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/bg",
|
|
3
|
-
"version": "8.4.
|
|
3
|
+
"version": "8.4.8",
|
|
4
4
|
"description": "Truedat Web Business Glossary",
|
|
5
|
-
"sideEffects":
|
|
5
|
+
"sideEffects": [
|
|
6
|
+
"**/*.css",
|
|
7
|
+
"**/*.less"
|
|
8
|
+
],
|
|
6
9
|
"module": "src/index.js",
|
|
7
10
|
"files": [
|
|
8
11
|
"src",
|
|
@@ -51,7 +54,7 @@
|
|
|
51
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
52
55
|
"@testing-library/react": "^16.3.0",
|
|
53
56
|
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@truedat/test": "8.4.
|
|
57
|
+
"@truedat/test": "8.4.8",
|
|
55
58
|
"identity-obj-proxy": "^3.0.0",
|
|
56
59
|
"jest": "^29.7.0",
|
|
57
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -84,5 +87,5 @@
|
|
|
84
87
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
85
88
|
"swr": "^2.3.3"
|
|
86
89
|
},
|
|
87
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "7829e377f78c3cfc66e449f5dc31a8b03c0f5f00"
|
|
88
91
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
|
-
import
|
|
2
|
+
import debounce from "lodash/debounce";
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useState,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
3
9
|
import PropTypes from "prop-types";
|
|
4
10
|
import { connect } from "react-redux";
|
|
5
11
|
import {
|
|
6
12
|
Button,
|
|
7
|
-
Dropdown,
|
|
8
13
|
Segment,
|
|
9
14
|
Grid,
|
|
10
15
|
Icon,
|
|
@@ -23,12 +28,15 @@ import {
|
|
|
23
28
|
graphDistinctTags,
|
|
24
29
|
EMPTY,
|
|
25
30
|
} from "@truedat/lm/services/relationGraphTraversal";
|
|
31
|
+
import { buildColorMap } from "@truedat/lm/services/edgeColorPalette";
|
|
26
32
|
import { RelationGraphDepth } from "@truedat/lm/components";
|
|
27
33
|
import { config } from "@truedat/core/truedatConfig";
|
|
28
34
|
import { getConceptToConceptRelations } from "../selectors/getConceptRelations";
|
|
29
35
|
|
|
30
36
|
import ConceptRelationActions from "./ConceptRelationActions";
|
|
31
37
|
import ConceptRelationRow from "./ConceptRelationRow";
|
|
38
|
+
import SliderWithFilter from "./SliderWithFilter";
|
|
39
|
+
|
|
32
40
|
|
|
33
41
|
export const ConceptRelationsHeaders = () => (
|
|
34
42
|
<Table.Header>
|
|
@@ -84,7 +92,7 @@ export const ConceptRelations = ({
|
|
|
84
92
|
hasAppliedConfig.current = true;
|
|
85
93
|
}, [conceptRelations, location]);
|
|
86
94
|
|
|
87
|
-
const navigateToConcept = ({ resource_id }
|
|
95
|
+
const navigateToConcept = ({ resource_id }) => {
|
|
88
96
|
navigate(
|
|
89
97
|
linkTo.CONCEPT_LINKS_CONCEPTS({
|
|
90
98
|
business_concept_id: resource_id,
|
|
@@ -93,20 +101,31 @@ export const ConceptRelations = ({
|
|
|
93
101
|
);
|
|
94
102
|
};
|
|
95
103
|
|
|
96
|
-
const
|
|
97
|
-
setAllowedTags(value);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const [depth, setDepth] = useState(initialDepth);
|
|
101
|
-
const [maxDepth, setMaxDepth] = useState(0);
|
|
104
|
+
const [selectedDepth, setSelectedDepth] = useState(initialDepth);
|
|
102
105
|
const [currentId, setCurrentId] = useState();
|
|
103
106
|
const [tagOptions, setTagsOptions] = useState([]);
|
|
104
|
-
const [limitedRelationGraph, setLimitedRelationGraph] =
|
|
105
|
-
useState(relationsGraph);
|
|
106
107
|
const [traversalTags, setAllowedTags] = useState([]);
|
|
108
|
+
const [localTraversalTags, setLocalTraversalTags] = useState([]);
|
|
109
|
+
const traversalTagsDebounceMs = Number.isFinite(
|
|
110
|
+
Number(config?.RelatedConceptsTagsDebounceMs)
|
|
111
|
+
)
|
|
112
|
+
? Number(config?.RelatedConceptsTagsDebounceMs)
|
|
113
|
+
: 500;
|
|
114
|
+
const debouncedSetAllowedTags = useMemo(
|
|
115
|
+
() => debounce((value) => setAllowedTags(value), traversalTagsDebounceMs),
|
|
116
|
+
[traversalTagsDebounceMs]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
useEffect(
|
|
120
|
+
() => () => {
|
|
121
|
+
debouncedSetAllowedTags.cancel();
|
|
122
|
+
},
|
|
123
|
+
[debouncedSetAllowedTags]
|
|
124
|
+
);
|
|
107
125
|
|
|
108
|
-
const
|
|
109
|
-
|
|
126
|
+
const onTraversalTagsChange = (_event, { value }) => {
|
|
127
|
+
setLocalTraversalTags(value);
|
|
128
|
+
debouncedSetAllowedTags(value);
|
|
110
129
|
};
|
|
111
130
|
|
|
112
131
|
useEffect(() => {
|
|
@@ -115,37 +134,46 @@ export const ConceptRelations = ({
|
|
|
115
134
|
}, [concept]);
|
|
116
135
|
|
|
117
136
|
useEffect(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
relationsGraph,
|
|
121
|
-
currentId,
|
|
122
|
-
traversalTags
|
|
123
|
-
);
|
|
137
|
+
setSelectedDepth(initialDepth);
|
|
138
|
+
}, [currentId, initialDepth]);
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_.isEmpty(traversalTags)
|
|
128
|
-
? Math.min(initialDepth, newMaxDepth)
|
|
129
|
-
: newMaxDepth
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}, [relationsGraph, currentId, traversalTags, initialDepth]);
|
|
140
|
+
const maxDepth = useMemo(() => {
|
|
141
|
+
if (_.isEmpty(relationsGraph) || !currentId) return 0;
|
|
133
142
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
const limitedGraph = pruneGraph(
|
|
140
|
-
relationsGraph,
|
|
141
|
-
currentId,
|
|
142
|
-
depth,
|
|
143
|
-
traversalTags
|
|
144
|
-
);
|
|
143
|
+
return findMaxDepth(relationsGraph, currentId, traversalTags);
|
|
144
|
+
}, [relationsGraph, currentId, traversalTags]);
|
|
145
|
+
|
|
146
|
+
const depth = useMemo(() => {
|
|
147
|
+
if (_.isEmpty(relationsGraph) || !currentId) return 0;
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
return Number.isFinite(selectedDepth)
|
|
150
|
+
? Math.min(selectedDepth, maxDepth)
|
|
151
|
+
: maxDepth;
|
|
152
|
+
}, [relationsGraph, currentId, selectedDepth, maxDepth]);
|
|
153
|
+
|
|
154
|
+
const limitedRelationGraph = useMemo(() => {
|
|
155
|
+
if (_.isEmpty(relationsGraph) || !currentId) return { nodes: [], edges: [] };
|
|
156
|
+
|
|
157
|
+
const result = pruneGraph(
|
|
158
|
+
relationsGraph,
|
|
159
|
+
currentId,
|
|
160
|
+
depth,
|
|
161
|
+
traversalTags
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (!result) return { nodes: [], edges: [] };
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
...result,
|
|
168
|
+
nodes: _.values(_.getOr({}, "nodes")(result)),
|
|
169
|
+
edges: _.getOr([], "edges")(result),
|
|
170
|
+
};
|
|
171
|
+
}, [relationsGraph, currentId, depth, traversalTags]);
|
|
172
|
+
|
|
173
|
+
const colorMap = useMemo(
|
|
174
|
+
() => buildColorMap(relationsGraph?.edges),
|
|
175
|
+
[relationsGraph]
|
|
176
|
+
);
|
|
149
177
|
|
|
150
178
|
useEffect(() => {
|
|
151
179
|
const tagsToOptions = (tags) =>
|
|
@@ -156,6 +184,7 @@ export const ConceptRelations = ({
|
|
|
156
184
|
defaultMessage: value.type,
|
|
157
185
|
}),
|
|
158
186
|
value: id,
|
|
187
|
+
type: value.type,
|
|
159
188
|
}),
|
|
160
189
|
[
|
|
161
190
|
{
|
|
@@ -170,33 +199,18 @@ export const ConceptRelations = ({
|
|
|
170
199
|
_.flow(graphDistinctTags, tagsToOptions, setTagsOptions)(relationsGraph);
|
|
171
200
|
}, [relationsGraph, formatMessage]);
|
|
172
201
|
|
|
202
|
+
const showGraph = graphRender && !_.isEmpty(currentId);
|
|
203
|
+
const stableGraphKey = useMemo(
|
|
204
|
+
() =>
|
|
205
|
+
`graph-${currentId}-${depth}-${_.sortBy(_.identity, traversalTags).join(",")}`,
|
|
206
|
+
[currentId, depth, traversalTags]
|
|
207
|
+
);
|
|
208
|
+
|
|
173
209
|
return (
|
|
174
|
-
<Segment
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<label>
|
|
179
|
-
{formatMessage({
|
|
180
|
-
id: "relations.tags.label",
|
|
181
|
-
})}
|
|
182
|
-
</label>
|
|
183
|
-
</b>
|
|
184
|
-
</div>
|
|
185
|
-
<div>
|
|
186
|
-
<Dropdown
|
|
187
|
-
className="traversal-tags"
|
|
188
|
-
placeholder={formatMessage({
|
|
189
|
-
id: `relations.tags.placeholder`,
|
|
190
|
-
})}
|
|
191
|
-
multiple
|
|
192
|
-
search
|
|
193
|
-
selection
|
|
194
|
-
options={tagOptions}
|
|
195
|
-
onChange={onTraversalTagsChange}
|
|
196
|
-
clearable
|
|
197
|
-
/>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
210
|
+
<Segment
|
|
211
|
+
attached="bottom"
|
|
212
|
+
className={`concept-relations-segment${showGraph ? " relation-graph-segment" : ""}`}
|
|
213
|
+
>
|
|
200
214
|
<div className="implementation-actions">
|
|
201
215
|
{_.negate(_.isEmpty)(conceptRelations) ? (
|
|
202
216
|
<Button.Group>
|
|
@@ -226,21 +240,28 @@ export const ConceptRelations = ({
|
|
|
226
240
|
</div>
|
|
227
241
|
{conceptRelations ? (
|
|
228
242
|
<>
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
{showGraph ? (
|
|
244
|
+
<div className="relation-graph-wrapper">
|
|
245
|
+
<SliderWithFilter
|
|
246
|
+
tagOptions={tagOptions}
|
|
247
|
+
traversalTags={localTraversalTags}
|
|
248
|
+
onTraversalTagsChange={onTraversalTagsChange}
|
|
249
|
+
colorMap={colorMap}
|
|
250
|
+
>
|
|
251
|
+
<RelationGraphDepth
|
|
252
|
+
onChange={(newDepth) => setSelectedDepth(parseInt(newDepth, 10))}
|
|
253
|
+
depth={depth}
|
|
254
|
+
maxDepth={maxDepth}
|
|
255
|
+
/>
|
|
256
|
+
</SliderWithFilter>
|
|
237
257
|
<RelationGraph
|
|
238
|
-
key={
|
|
258
|
+
key={stableGraphKey}
|
|
239
259
|
navigate={navigateToConcept}
|
|
240
260
|
currentId={currentId}
|
|
241
261
|
relationsGraph={limitedRelationGraph}
|
|
262
|
+
colorMap={colorMap}
|
|
242
263
|
/>
|
|
243
|
-
|
|
264
|
+
</div>
|
|
244
265
|
) : (
|
|
245
266
|
<Table selectable>
|
|
246
267
|
<Table.Body>
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { useIntl } from "react-intl";
|
|
4
|
+
|
|
5
|
+
const FunnelIcon = () => (
|
|
6
|
+
<svg
|
|
7
|
+
width="14"
|
|
8
|
+
height="14"
|
|
9
|
+
viewBox="0 0 24 24"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="#ff5c00"
|
|
12
|
+
strokeWidth="2.5"
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
>
|
|
16
|
+
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const XIcon = () => (
|
|
21
|
+
<svg
|
|
22
|
+
width="14"
|
|
23
|
+
height="14"
|
|
24
|
+
viewBox="0 0 24 24"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="#999"
|
|
27
|
+
strokeWidth="2.5"
|
|
28
|
+
strokeLinecap="round"
|
|
29
|
+
strokeLinejoin="round"
|
|
30
|
+
>
|
|
31
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
32
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const SquareIcon = ({ color = "#333" }) => (
|
|
37
|
+
<svg
|
|
38
|
+
width="16"
|
|
39
|
+
height="16"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke={color}
|
|
43
|
+
strokeWidth="2"
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
>
|
|
47
|
+
<rect x="3" y="3" width="18" height="18" rx="2" />
|
|
48
|
+
</svg>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const SquareCheckIcon = ({ color = "#f87201" }) => (
|
|
52
|
+
<svg
|
|
53
|
+
width="16"
|
|
54
|
+
height="16"
|
|
55
|
+
viewBox="0 0 24 24"
|
|
56
|
+
fill="none"
|
|
57
|
+
stroke="#ffffff"
|
|
58
|
+
strokeWidth="2"
|
|
59
|
+
strokeLinecap="round"
|
|
60
|
+
strokeLinejoin="round"
|
|
61
|
+
>
|
|
62
|
+
<rect x="3" y="3" width="18" height="18" rx="2" fill={color} stroke={color} />
|
|
63
|
+
<polyline points="9 12 11 14 15 10" />
|
|
64
|
+
</svg>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const Badge = ({ count }) => (
|
|
68
|
+
<span className="slider-with-filter__badge">
|
|
69
|
+
<span className="slider-with-filter__badge-text">{count}</span>
|
|
70
|
+
</span>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const TagChip = ({ text, selected, onClick, color }) => (
|
|
74
|
+
<span
|
|
75
|
+
className="slider-with-filter__chip"
|
|
76
|
+
role="option"
|
|
77
|
+
aria-selected={selected}
|
|
78
|
+
onClick={onClick}
|
|
79
|
+
title={text}
|
|
80
|
+
>
|
|
81
|
+
{selected ? <SquareCheckIcon color={color} /> : <SquareIcon color={color} />}
|
|
82
|
+
<span
|
|
83
|
+
className="slider-with-filter__chip-text"
|
|
84
|
+
style={color ? { "--td-chip-color": color } : undefined}
|
|
85
|
+
>
|
|
86
|
+
{text}
|
|
87
|
+
</span>
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const SliderWithFilter = ({
|
|
92
|
+
tagOptions = [],
|
|
93
|
+
traversalTags = [],
|
|
94
|
+
onTraversalTagsChange,
|
|
95
|
+
colorMap,
|
|
96
|
+
children,
|
|
97
|
+
}) => {
|
|
98
|
+
const { formatMessage } = useIntl();
|
|
99
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
100
|
+
|
|
101
|
+
const hasApplied = traversalTags.length > 0;
|
|
102
|
+
|
|
103
|
+
const handleToggleOpen = useCallback(() => setIsOpen((o) => !o), []);
|
|
104
|
+
|
|
105
|
+
const handleClearFilters = useCallback(
|
|
106
|
+
(e) => {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
onTraversalTagsChange?.(null, { value: [] });
|
|
109
|
+
},
|
|
110
|
+
[onTraversalTagsChange]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const handleToggleTag = useCallback(
|
|
114
|
+
(tagValue) => {
|
|
115
|
+
const next = traversalTags.includes(tagValue)
|
|
116
|
+
? traversalTags.filter((t) => t !== tagValue)
|
|
117
|
+
: [...traversalTags, tagValue];
|
|
118
|
+
onTraversalTagsChange?.(null, { value: next });
|
|
119
|
+
},
|
|
120
|
+
[traversalTags, onTraversalTagsChange]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div className="slider-with-filter__panel">
|
|
125
|
+
<div className="slider-with-filter__header-area">
|
|
126
|
+
<div
|
|
127
|
+
className="slider-with-filter__tab"
|
|
128
|
+
onClick={handleToggleOpen}
|
|
129
|
+
title={formatMessage({ id: "relations.tags.filter" })}
|
|
130
|
+
>
|
|
131
|
+
<span className="slider-with-filter__filtro-label">
|
|
132
|
+
{formatMessage({ id: "relations.tags.filter.label" })}
|
|
133
|
+
</span>
|
|
134
|
+
<span className="slider-with-filter__tab-actions">
|
|
135
|
+
<FunnelIcon />
|
|
136
|
+
{hasApplied ? <Badge count={traversalTags.length} /> : null}
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{isOpen && tagOptions.length > 0 && (
|
|
141
|
+
<div className="slider-with-filter__chips-bar">
|
|
142
|
+
<div className="slider-with-filter__chips-list">
|
|
143
|
+
{tagOptions.map((opt) => (
|
|
144
|
+
<TagChip
|
|
145
|
+
key={opt.value}
|
|
146
|
+
text={opt.text}
|
|
147
|
+
selected={traversalTags.includes(opt.value)}
|
|
148
|
+
color={opt.type && colorMap ? colorMap.get(opt.type) : undefined}
|
|
149
|
+
onClick={(e) => {
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
handleToggleTag(opt.value);
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div className="slider-with-filter__slider-area">
|
|
162
|
+
{children}
|
|
163
|
+
<div className="slider-with-filter__prof-pill">
|
|
164
|
+
<span className="slider-with-filter__prof-label">
|
|
165
|
+
{formatMessage({ id: "relations.depth.label" })}
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{hasApplied && (
|
|
171
|
+
<span
|
|
172
|
+
className="slider-with-filter__clear"
|
|
173
|
+
onClick={handleClearFilters}
|
|
174
|
+
>
|
|
175
|
+
<XIcon />
|
|
176
|
+
<span className="slider-with-filter__clear-label">
|
|
177
|
+
{formatMessage({ id: "relations.tags.clear" })}
|
|
178
|
+
</span>
|
|
179
|
+
</span>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
SliderWithFilter.propTypes = {
|
|
186
|
+
tagOptions: PropTypes.arrayOf(
|
|
187
|
+
PropTypes.shape({
|
|
188
|
+
text: PropTypes.string,
|
|
189
|
+
value: PropTypes.any,
|
|
190
|
+
})
|
|
191
|
+
),
|
|
192
|
+
traversalTags: PropTypes.array,
|
|
193
|
+
onTraversalTagsChange: PropTypes.func,
|
|
194
|
+
colorMap: PropTypes.instanceOf(Map),
|
|
195
|
+
children: PropTypes.node,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export default SliderWithFilter;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { act, fireEvent, waitFor } from "@testing-library/react";
|
|
1
2
|
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
3
|
import { ConceptRelations } from "../ConceptRelations";
|
|
3
4
|
import { setConfig } from "@truedat/core/truedatConfig";
|
|
4
5
|
|
|
5
6
|
const mockUseLocation = jest.fn(() => ({ pathname: "/bar" }));
|
|
6
7
|
const mockUseNavigate = jest.fn(() => jest.fn());
|
|
8
|
+
let lastRelationGraphProps;
|
|
7
9
|
|
|
8
10
|
jest.mock("react-router", () => ({
|
|
9
11
|
...jest.requireActual("react-router"),
|
|
@@ -12,9 +14,30 @@ jest.mock("react-router", () => ({
|
|
|
12
14
|
}));
|
|
13
15
|
|
|
14
16
|
jest.mock("@truedat/lm/components", () => ({
|
|
15
|
-
RelationGraph: () =>
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
RelationGraph: (props) => {
|
|
18
|
+
lastRelationGraphProps = props;
|
|
19
|
+
const nodeIds = (props.relationsGraph?.nodes || [])
|
|
20
|
+
.map(({ id }) => id)
|
|
21
|
+
.join(",");
|
|
22
|
+
const edgeIds = (props.relationsGraph?.edges || [])
|
|
23
|
+
.map(({ id }) => id)
|
|
24
|
+
.join(",");
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div data-testid="relation-graph">
|
|
28
|
+
<span data-testid="relation-graph-node-ids">{nodeIds}</span>
|
|
29
|
+
<span data-testid="relation-graph-edge-ids">{edgeIds}</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
RelationGraphDepth: ({ onChange, depth, maxDepth }) => (
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
data-testid="relation-graph-depth"
|
|
37
|
+
onClick={() => onChange(1)}
|
|
38
|
+
>
|
|
39
|
+
{`${depth}/${maxDepth}`}
|
|
40
|
+
</button>
|
|
18
41
|
),
|
|
19
42
|
}));
|
|
20
43
|
|
|
@@ -22,6 +45,7 @@ describe("<ConceptRelations />", () => {
|
|
|
22
45
|
beforeEach(() => {
|
|
23
46
|
setConfig({});
|
|
24
47
|
mockUseLocation.mockReturnValue({ pathname: "/bar" });
|
|
48
|
+
lastRelationGraphProps = undefined;
|
|
25
49
|
});
|
|
26
50
|
|
|
27
51
|
afterEach(() => {
|
|
@@ -52,6 +76,28 @@ describe("<ConceptRelations />", () => {
|
|
|
52
76
|
visible: true,
|
|
53
77
|
};
|
|
54
78
|
|
|
79
|
+
const graphWithDepthAndTags = {
|
|
80
|
+
nodes: [
|
|
81
|
+
{ id: "business_concept:1", name: "Root" },
|
|
82
|
+
{ id: "business_concept:2", name: "Second" },
|
|
83
|
+
{ id: "business_concept:3", name: "Third" },
|
|
84
|
+
],
|
|
85
|
+
edges: [
|
|
86
|
+
{
|
|
87
|
+
id: "root_to_second",
|
|
88
|
+
source_id: "business_concept:1",
|
|
89
|
+
target_id: "business_concept:2",
|
|
90
|
+
tags: [{ id: "foo", value: { type: "foo" } }],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "second_to_third",
|
|
94
|
+
source_id: "business_concept:2",
|
|
95
|
+
target_id: "business_concept:3",
|
|
96
|
+
tags: [{ id: "bar", value: { type: "bar" } }],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
|
|
55
101
|
it("matches the latest snapshot", async () => {
|
|
56
102
|
const props = {
|
|
57
103
|
...baseProps,
|
|
@@ -66,6 +112,42 @@ describe("<ConceptRelations />", () => {
|
|
|
66
112
|
expect(rendered.container).toMatchSnapshot();
|
|
67
113
|
});
|
|
68
114
|
|
|
115
|
+
it("matches snapshot with list view and relations", async () => {
|
|
116
|
+
setConfig({ RelatedConceptsActiveRenderMode: "list" });
|
|
117
|
+
|
|
118
|
+
const props = {
|
|
119
|
+
...baseProps,
|
|
120
|
+
conceptRelations: [
|
|
121
|
+
{
|
|
122
|
+
id: 1,
|
|
123
|
+
source_id: 1,
|
|
124
|
+
target_id: 2,
|
|
125
|
+
tags: [{ id: 3, value: { type: "related_to" } }],
|
|
126
|
+
context: {
|
|
127
|
+
source: { id: 1, name: "Source Concept" },
|
|
128
|
+
target: { id: 2, name: "Target Concept" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 2,
|
|
133
|
+
source_id: 1,
|
|
134
|
+
target_id: 3,
|
|
135
|
+
tags: [],
|
|
136
|
+
context: {
|
|
137
|
+
source: { id: 1, name: "Source Concept" },
|
|
138
|
+
target: { id: 3, name: "Another Target" },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const rendered = render(<ConceptRelations {...props} />, {
|
|
145
|
+
state: baseState,
|
|
146
|
+
});
|
|
147
|
+
await waitForLoad(rendered);
|
|
148
|
+
expect(rendered.container).toMatchSnapshot();
|
|
149
|
+
});
|
|
150
|
+
|
|
69
151
|
it("activates graph button when RelatedConceptsActiveRenderMode is graph", async () => {
|
|
70
152
|
setConfig({ RelatedConceptsActiveRenderMode: "graph" });
|
|
71
153
|
|
|
@@ -75,12 +157,18 @@ describe("<ConceptRelations />", () => {
|
|
|
75
157
|
await waitForLoad(rendered);
|
|
76
158
|
|
|
77
159
|
expect(
|
|
78
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
160
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
79
161
|
).toHaveClass("active");
|
|
80
162
|
expect(
|
|
81
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
163
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
82
164
|
).not.toHaveClass("active");
|
|
165
|
+
expect(
|
|
166
|
+
rendered.container.querySelector(".ui.bottom.attached.segment"),
|
|
167
|
+
).toHaveClass("concept-relations-segment");
|
|
83
168
|
expect(rendered.getByTestId("relation-graph")).toBeInTheDocument();
|
|
169
|
+
expect(
|
|
170
|
+
rendered.container.querySelector(".ui.bottom.attached.segment"),
|
|
171
|
+
).toHaveClass("relation-graph-segment");
|
|
84
172
|
});
|
|
85
173
|
|
|
86
174
|
it("activates list button when RelatedConceptsActiveRenderMode is list", async () => {
|
|
@@ -92,12 +180,21 @@ describe("<ConceptRelations />", () => {
|
|
|
92
180
|
await waitForLoad(rendered);
|
|
93
181
|
|
|
94
182
|
expect(
|
|
95
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
183
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
96
184
|
).toHaveClass("active");
|
|
97
185
|
expect(
|
|
98
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
186
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
99
187
|
).not.toHaveClass("active");
|
|
100
188
|
expect(rendered.queryByTestId("relation-graph")).not.toBeInTheDocument();
|
|
189
|
+
expect(
|
|
190
|
+
rendered.container.querySelector(".ui.bottom.attached.segment"),
|
|
191
|
+
).toHaveClass("concept-relations-segment");
|
|
192
|
+
expect(
|
|
193
|
+
rendered.container.querySelector(".ui.bottom.attached.segment"),
|
|
194
|
+
).not.toHaveClass("relation-graph-segment");
|
|
195
|
+
expect(
|
|
196
|
+
rendered.container.querySelector(".slider-with-filter__panel"),
|
|
197
|
+
).not.toBeInTheDocument();
|
|
101
198
|
});
|
|
102
199
|
|
|
103
200
|
it("shows graph view by default when RelatedConceptsActiveRenderMode is not set", async () => {
|
|
@@ -109,10 +206,10 @@ describe("<ConceptRelations />", () => {
|
|
|
109
206
|
await waitForLoad(rendered);
|
|
110
207
|
|
|
111
208
|
expect(
|
|
112
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
209
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
113
210
|
).toHaveClass("active");
|
|
114
211
|
expect(
|
|
115
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
212
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
116
213
|
).not.toHaveClass("active");
|
|
117
214
|
expect(rendered.getByTestId("relation-graph")).toBeInTheDocument();
|
|
118
215
|
});
|
|
@@ -130,10 +227,10 @@ describe("<ConceptRelations />", () => {
|
|
|
130
227
|
await waitForLoad(rendered);
|
|
131
228
|
|
|
132
229
|
expect(
|
|
133
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
230
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
134
231
|
).toHaveClass("active");
|
|
135
232
|
expect(
|
|
136
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
233
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
137
234
|
).not.toHaveClass("active");
|
|
138
235
|
expect(rendered.queryByTestId("relation-graph")).not.toBeInTheDocument();
|
|
139
236
|
});
|
|
@@ -151,10 +248,10 @@ describe("<ConceptRelations />", () => {
|
|
|
151
248
|
await waitForLoad(rendered);
|
|
152
249
|
|
|
153
250
|
expect(
|
|
154
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
251
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
155
252
|
).toHaveClass("active");
|
|
156
253
|
expect(
|
|
157
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
254
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
158
255
|
).not.toHaveClass("active");
|
|
159
256
|
expect(rendered.getByTestId("relation-graph")).toBeInTheDocument();
|
|
160
257
|
});
|
|
@@ -173,14 +270,61 @@ describe("<ConceptRelations />", () => {
|
|
|
173
270
|
await waitForLoad(rendered);
|
|
174
271
|
|
|
175
272
|
expect(
|
|
176
|
-
rendered.container.querySelector('button[data-tooltip*="maps"]')
|
|
273
|
+
rendered.container.querySelector('button[data-tooltip*="maps"]'),
|
|
177
274
|
).not.toBeInTheDocument();
|
|
178
275
|
|
|
179
276
|
rendered.rerender(<ConceptRelations {...baseProps} />);
|
|
180
277
|
await waitForLoad(rendered);
|
|
181
278
|
|
|
182
279
|
expect(
|
|
183
|
-
rendered.container.querySelector('button[data-tooltip*="list"]')
|
|
280
|
+
rendered.container.querySelector('button[data-tooltip*="list"]'),
|
|
184
281
|
).toHaveClass("active");
|
|
185
282
|
});
|
|
283
|
+
|
|
284
|
+
it("keeps the selected depth when clearing relation filters", async () => {
|
|
285
|
+
setConfig({ RelatedConceptsTagsDebounceMs: 0 });
|
|
286
|
+
|
|
287
|
+
const rendered = render(
|
|
288
|
+
<ConceptRelations
|
|
289
|
+
{...baseProps}
|
|
290
|
+
relationsGraph={graphWithDepthAndTags}
|
|
291
|
+
/>,
|
|
292
|
+
{
|
|
293
|
+
state: baseState,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
await waitForLoad(rendered);
|
|
298
|
+
|
|
299
|
+
fireEvent.click(rendered.getByTestId("relation-graph-depth"));
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(lastRelationGraphProps.relationsGraph.nodes).toHaveLength(2);
|
|
303
|
+
expect(lastRelationGraphProps.relationsGraph.edges).toHaveLength(1);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
fireEvent.click(rendered.getByText("relations.tags.filter.label"));
|
|
307
|
+
fireEvent.click(rendered.getByText("foo"));
|
|
308
|
+
act(() => {
|
|
309
|
+
jest.runOnlyPendingTimers();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
await waitFor(() => {
|
|
313
|
+
expect(lastRelationGraphProps.relationsGraph.nodes).toHaveLength(2);
|
|
314
|
+
expect(lastRelationGraphProps.relationsGraph.edges).toHaveLength(1);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
fireEvent.click(rendered.getByText("relations.tags.clear"));
|
|
318
|
+
act(() => {
|
|
319
|
+
jest.runOnlyPendingTimers();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await waitFor(() => {
|
|
323
|
+
expect(lastRelationGraphProps.relationsGraph.nodes).toHaveLength(2);
|
|
324
|
+
expect(lastRelationGraphProps.relationsGraph.edges).toHaveLength(1);
|
|
325
|
+
expect(
|
|
326
|
+
lastRelationGraphProps.relationsGraph.nodes.map(({ id }) => id),
|
|
327
|
+
).toEqual(["business_concept:1", "business_concept:2"]);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
186
330
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { fireEvent, screen } from "@testing-library/react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import SliderWithFilter from "../SliderWithFilter";
|
|
4
|
+
|
|
5
|
+
const tagOptions = [
|
|
6
|
+
{ text: "Type A", value: "tag-a", type: "type_a" },
|
|
7
|
+
{ text: "Type B", value: "tag-b", type: "type_b" },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const defaultProps = {
|
|
11
|
+
tagOptions,
|
|
12
|
+
traversalTags: [],
|
|
13
|
+
onTraversalTagsChange: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe("<SliderWithFilter />", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders correctly", () => {
|
|
22
|
+
const { container } = render(<SliderWithFilter {...defaultProps} />);
|
|
23
|
+
|
|
24
|
+
expect(container).toMatchSnapshot();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("renders the filter button", () => {
|
|
28
|
+
render(<SliderWithFilter {...defaultProps} />);
|
|
29
|
+
|
|
30
|
+
expect(screen.getByText("relations.tags.filter.label")).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("does not show badge when no filters are applied", () => {
|
|
34
|
+
render(<SliderWithFilter {...defaultProps} />);
|
|
35
|
+
|
|
36
|
+
expect(screen.queryByText("0")).not.toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("shows badge with count when filters are applied", () => {
|
|
40
|
+
render(
|
|
41
|
+
<SliderWithFilter {...defaultProps} traversalTags={["tag-a", "tag-b"]} />
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(screen.getByText("2")).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("does not show chips when closed", () => {
|
|
48
|
+
render(<SliderWithFilter {...defaultProps} />);
|
|
49
|
+
|
|
50
|
+
expect(screen.queryByText("Type A")).not.toBeInTheDocument();
|
|
51
|
+
expect(screen.queryByText("Type B")).not.toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("shows chips when filter button is clicked", () => {
|
|
55
|
+
render(<SliderWithFilter {...defaultProps} />);
|
|
56
|
+
|
|
57
|
+
fireEvent.click(screen.getByText("relations.tags.filter.label"));
|
|
58
|
+
|
|
59
|
+
expect(screen.getByText("Type A")).toBeInTheDocument();
|
|
60
|
+
expect(screen.getByText("Type B")).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("calls onTraversalTagsChange with selected tag when chip is clicked", () => {
|
|
64
|
+
const onTraversalTagsChange = jest.fn();
|
|
65
|
+
render(
|
|
66
|
+
<SliderWithFilter
|
|
67
|
+
{...defaultProps}
|
|
68
|
+
onTraversalTagsChange={onTraversalTagsChange}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
fireEvent.click(screen.getByText("relations.tags.filter.label"));
|
|
73
|
+
fireEvent.click(screen.getByText("Type A"));
|
|
74
|
+
|
|
75
|
+
expect(onTraversalTagsChange).toHaveBeenCalledWith(null, {
|
|
76
|
+
value: ["tag-a"],
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("calls onTraversalTagsChange removing tag when selected chip is clicked", () => {
|
|
81
|
+
const onTraversalTagsChange = jest.fn();
|
|
82
|
+
render(
|
|
83
|
+
<SliderWithFilter
|
|
84
|
+
{...defaultProps}
|
|
85
|
+
traversalTags={["tag-a"]}
|
|
86
|
+
onTraversalTagsChange={onTraversalTagsChange}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
fireEvent.click(screen.getByText("relations.tags.filter.label"));
|
|
91
|
+
fireEvent.click(screen.getByText("Type A"));
|
|
92
|
+
|
|
93
|
+
expect(onTraversalTagsChange).toHaveBeenCalledWith(null, { value: [] });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("shows clear button when filters are applied", () => {
|
|
97
|
+
render(
|
|
98
|
+
<SliderWithFilter {...defaultProps} traversalTags={["tag-a"]} />
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(screen.getByText("relations.tags.clear")).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("does not show clear button when no filters are applied", () => {
|
|
105
|
+
render(<SliderWithFilter {...defaultProps} />);
|
|
106
|
+
|
|
107
|
+
expect(screen.queryByText("relations.tags.clear")).not.toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("calls onTraversalTagsChange with empty array when clear button is clicked", () => {
|
|
111
|
+
const onTraversalTagsChange = jest.fn();
|
|
112
|
+
render(
|
|
113
|
+
<SliderWithFilter
|
|
114
|
+
{...defaultProps}
|
|
115
|
+
traversalTags={["tag-a"]}
|
|
116
|
+
onTraversalTagsChange={onTraversalTagsChange}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
fireEvent.click(screen.getByText("relations.tags.clear"));
|
|
121
|
+
|
|
122
|
+
expect(onTraversalTagsChange).toHaveBeenCalledWith(null, { value: [] });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("renders children in the slider area", () => {
|
|
126
|
+
render(
|
|
127
|
+
<SliderWithFilter {...defaultProps}>
|
|
128
|
+
<div data-testid="depth-slider">slider</div>
|
|
129
|
+
</SliderWithFilter>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(screen.getByTestId("depth-slider")).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
});
|
package/src/concepts/relations/components/__tests__/__snapshots__/ConceptRelations.spec.js.snap
CHANGED
|
@@ -1,71 +1,105 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`<ConceptRelations /> matches
|
|
3
|
+
exports[`<ConceptRelations /> matches snapshot with list view and relations 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<div
|
|
6
|
-
class="ui bottom attached segment"
|
|
6
|
+
class="ui bottom attached segment concept-relations-segment"
|
|
7
7
|
>
|
|
8
8
|
<div
|
|
9
|
-
class="
|
|
9
|
+
class="implementation-actions"
|
|
10
10
|
>
|
|
11
|
-
<div
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</div>
|
|
18
|
-
<div>
|
|
19
|
-
<div
|
|
20
|
-
aria-expanded="false"
|
|
21
|
-
class="ui multiple search selection dropdown traversal-tags"
|
|
22
|
-
role="combobox"
|
|
11
|
+
<div
|
|
12
|
+
class="ui buttons"
|
|
13
|
+
>
|
|
14
|
+
<button
|
|
15
|
+
class="ui icon button"
|
|
16
|
+
data-tooltip="relations.actions.maps.tooltip"
|
|
23
17
|
>
|
|
24
|
-
<
|
|
25
|
-
aria-
|
|
26
|
-
|
|
27
|
-
class="search"
|
|
28
|
-
tabindex="0"
|
|
29
|
-
type="text"
|
|
30
|
-
value=""
|
|
31
|
-
/>
|
|
32
|
-
<span
|
|
33
|
-
class="sizer"
|
|
18
|
+
<i
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
class="sitemap icon"
|
|
34
21
|
/>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
relations.tags.placeholder
|
|
42
|
-
</div>
|
|
22
|
+
</button>
|
|
23
|
+
<button
|
|
24
|
+
class="ui active icon button"
|
|
25
|
+
data-tooltip="relations.actions.list.tooltip"
|
|
26
|
+
>
|
|
43
27
|
<i
|
|
44
28
|
aria-hidden="true"
|
|
45
|
-
class="
|
|
29
|
+
class="list ul icon"
|
|
46
30
|
/>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<table
|
|
35
|
+
class="ui selectable table"
|
|
36
|
+
>
|
|
37
|
+
<tbody
|
|
38
|
+
class=""
|
|
39
|
+
>
|
|
40
|
+
<tr
|
|
41
|
+
class=""
|
|
42
|
+
>
|
|
43
|
+
<td
|
|
44
|
+
class=""
|
|
45
|
+
>
|
|
46
|
+
Source Concept
|
|
47
|
+
</td>
|
|
48
|
+
<td
|
|
49
|
+
class="center aligned"
|
|
51
50
|
>
|
|
52
51
|
<div
|
|
53
|
-
|
|
54
|
-
aria-selected="true"
|
|
55
|
-
class="selected item"
|
|
56
|
-
role="option"
|
|
57
|
-
style="pointer-events: all;"
|
|
52
|
+
class="ui label"
|
|
58
53
|
>
|
|
59
|
-
|
|
60
|
-
class="text"
|
|
61
|
-
>
|
|
62
|
-
_empty
|
|
63
|
-
</span>
|
|
54
|
+
related_to
|
|
64
55
|
</div>
|
|
65
|
-
</
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
</td>
|
|
57
|
+
<td
|
|
58
|
+
class="center aligned"
|
|
59
|
+
>
|
|
60
|
+
Target Concept
|
|
61
|
+
</td>
|
|
62
|
+
<td
|
|
63
|
+
class="center aligned"
|
|
64
|
+
/>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr
|
|
67
|
+
class=""
|
|
68
|
+
>
|
|
69
|
+
<td
|
|
70
|
+
class=""
|
|
71
|
+
>
|
|
72
|
+
Source Concept
|
|
73
|
+
</td>
|
|
74
|
+
<td
|
|
75
|
+
class="center aligned"
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
class="ui label"
|
|
79
|
+
>
|
|
80
|
+
-
|
|
81
|
+
</div>
|
|
82
|
+
</td>
|
|
83
|
+
<td
|
|
84
|
+
class="center aligned"
|
|
85
|
+
>
|
|
86
|
+
Another Target
|
|
87
|
+
</td>
|
|
88
|
+
<td
|
|
89
|
+
class="center aligned"
|
|
90
|
+
/>
|
|
91
|
+
</tr>
|
|
92
|
+
</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
exports[`<ConceptRelations /> matches the latest snapshot 1`] = `
|
|
99
|
+
<div>
|
|
100
|
+
<div
|
|
101
|
+
class="ui bottom attached segment concept-relations-segment"
|
|
102
|
+
>
|
|
69
103
|
<div
|
|
70
104
|
class="implementation-actions"
|
|
71
105
|
/>
|
package/src/concepts/relations/components/__tests__/__snapshots__/SliderWithFilter.spec.js.snap
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<SliderWithFilter /> renders correctly 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="slider-with-filter__panel"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
class="slider-with-filter__header-area"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="slider-with-filter__tab"
|
|
13
|
+
title="relations.tags.filter"
|
|
14
|
+
>
|
|
15
|
+
<span
|
|
16
|
+
class="slider-with-filter__filtro-label"
|
|
17
|
+
>
|
|
18
|
+
relations.tags.filter.label
|
|
19
|
+
</span>
|
|
20
|
+
<span
|
|
21
|
+
class="slider-with-filter__tab-actions"
|
|
22
|
+
>
|
|
23
|
+
<svg
|
|
24
|
+
fill="none"
|
|
25
|
+
height="14"
|
|
26
|
+
stroke="#ff5c00"
|
|
27
|
+
stroke-linecap="round"
|
|
28
|
+
stroke-linejoin="round"
|
|
29
|
+
stroke-width="2.5"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
width="14"
|
|
32
|
+
>
|
|
33
|
+
<polygon
|
|
34
|
+
points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div
|
|
41
|
+
class="slider-with-filter__slider-area"
|
|
42
|
+
>
|
|
43
|
+
<div
|
|
44
|
+
class="slider-with-filter__prof-pill"
|
|
45
|
+
>
|
|
46
|
+
<span
|
|
47
|
+
class="slider-with-filter__prof-label"
|
|
48
|
+
>
|
|
49
|
+
relations.depth.label
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
@slider-width: 104px;
|
|
2
|
+
@tab-offset: 8px;
|
|
3
|
+
@tab-width: 88px;
|
|
4
|
+
@tab-top: 13px;
|
|
5
|
+
@tab-height: 24px;
|
|
6
|
+
@orange-primary: #ed5c17;
|
|
7
|
+
@orange-bg: #FFF7F2;
|
|
8
|
+
@orange-border: #FFE4D1;
|
|
9
|
+
@font-stack: 'Funnel Sans', Inter, sans-serif;
|
|
10
|
+
|
|
11
|
+
.relation-graph-wrapper {
|
|
12
|
+
.slider-with-filter__panel {
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 44px;
|
|
15
|
+
right: 8px;
|
|
16
|
+
z-index: 2;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.slider-with-filter {
|
|
21
|
+
&__panel {
|
|
22
|
+
width: @slider-width;
|
|
23
|
+
min-height: 220px;
|
|
24
|
+
background: #ffffff9a;
|
|
25
|
+
backdrop-filter: blur(4px);
|
|
26
|
+
-webkit-backdrop-filter: blur(4px);
|
|
27
|
+
border-radius: 12px;
|
|
28
|
+
border: 1px solid #F3F4F6;
|
|
29
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.03), 0 16px 40px rgba(0, 0, 0, 0.07);
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
overflow: visible;
|
|
33
|
+
position: relative;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&__header-area {
|
|
37
|
+
position: relative;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 44px;
|
|
40
|
+
overflow: visible;
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&__tab {
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: @tab-top;
|
|
47
|
+
right: @tab-offset;
|
|
48
|
+
width: @tab-width;
|
|
49
|
+
height: @tab-height;
|
|
50
|
+
background: @orange-bg;
|
|
51
|
+
border: 1px solid @orange-border;
|
|
52
|
+
border-radius: 9999px;
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
gap: 8px;
|
|
57
|
+
padding: 0 8px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
box-sizing: border-box;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__tab-actions {
|
|
65
|
+
display: inline-flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: flex-end;
|
|
68
|
+
gap: 2px;
|
|
69
|
+
min-width: 20px;
|
|
70
|
+
margin-left: auto;
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&__chips-bar {
|
|
75
|
+
position: absolute;
|
|
76
|
+
top: (@tab-top + (@tab-height / 2));
|
|
77
|
+
right: (@tab-width + @tab-offset + 6px);
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
align-items: flex-start;
|
|
81
|
+
gap: 6px;
|
|
82
|
+
padding: 8px;
|
|
83
|
+
background: #ffffffe0;
|
|
84
|
+
backdrop-filter: blur(4px);
|
|
85
|
+
-webkit-backdrop-filter: blur(4px);
|
|
86
|
+
border: 1px solid @orange-border;
|
|
87
|
+
border-radius: 12px;
|
|
88
|
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
transform-origin: right center;
|
|
91
|
+
transform: translateY(-50%);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&__chips-list {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 6px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&__filtro-label {
|
|
101
|
+
font-family: @font-stack;
|
|
102
|
+
font-size: 10px;
|
|
103
|
+
font-weight: 700;
|
|
104
|
+
letter-spacing: 0.3px;
|
|
105
|
+
color: @orange-primary;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
&__badge {
|
|
110
|
+
display: inline-flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
justify-content: flex-end;
|
|
113
|
+
min-width: 14px;
|
|
114
|
+
margin-left: -4px;
|
|
115
|
+
flex-shrink: 0;
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
&__badge-text {
|
|
120
|
+
font-family: @font-stack;
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
font-weight: bold;
|
|
123
|
+
color: #111111;
|
|
124
|
+
line-height: 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&__chip {
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 4px;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
flex-shrink: 0;
|
|
133
|
+
padding: 0 4px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
&__clear {
|
|
137
|
+
position: absolute;
|
|
138
|
+
left: 50%;
|
|
139
|
+
bottom: -36px;
|
|
140
|
+
transform: translateX(-50%);
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 4px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
flex-shrink: 0;
|
|
146
|
+
opacity: 0.72;
|
|
147
|
+
padding: 4px 8px;
|
|
148
|
+
background: #fff;
|
|
149
|
+
border: 1px solid @orange-border;
|
|
150
|
+
border-radius: 9999px;
|
|
151
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
|
|
152
|
+
white-space: nowrap;
|
|
153
|
+
z-index: 1;
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
opacity: 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&__clear-label {
|
|
161
|
+
font-family: @font-stack;
|
|
162
|
+
font-size: 10px;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
color: #999;
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&__chip-text {
|
|
169
|
+
font-family: @font-stack;
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
color: var(--td-chip-color, #666);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&__prof-pill {
|
|
176
|
+
width: 100%;
|
|
177
|
+
height: 18px;
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
flex-shrink: 0;
|
|
182
|
+
margin-top: auto;
|
|
183
|
+
margin-bottom: 5px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
&__prof-label {
|
|
187
|
+
font-family: @font-stack;
|
|
188
|
+
font-size: 10px;
|
|
189
|
+
font-weight: 700;
|
|
190
|
+
color: @orange-primary;
|
|
191
|
+
line-height: 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
&__slider-area {
|
|
195
|
+
flex: 1;
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: flex-start;
|
|
200
|
+
padding: 14px 4px 8px;
|
|
201
|
+
}
|
|
202
|
+
}
|