Skip to content

Commit 083bf3f

Browse files
authored
fix/COMPASS-9935 collapse/expand nested (#169)
1 parent 3fa2422 commit 083bf3f

File tree

5 files changed

+162
-32
lines changed

5 files changed

+162
-32
lines changed

src/components/diagram.stories.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Meta, StoryObj } from '@storybook/react';
22

33
import { Diagram } from '@/components/diagram';
4-
import { EMPLOYEE_TERRITORIES_NODE, EMPLOYEES_NODE, ORDERS_NODE } from '@/mocks/datasets/nodes';
4+
import { CUSTOMERS_NODE, EMPLOYEE_TERRITORIES_NODE, EMPLOYEES_NODE, ORDERS_NODE } from '@/mocks/datasets/nodes';
55
import {
66
EMPLOYEES_TO_EMPLOYEE_TERRITORIES_EDGE,
77
EMPLOYEES_TO_EMPLOYEES_EDGE,
@@ -58,23 +58,6 @@ export const DiagramWithConnectableNodes: Story = {
5858
},
5959
};
6060

61-
let idAccumulator: string[];
62-
let lastDepth = 0;
63-
// Used to build a string array id based on field depth.
64-
function idFromDepthAccumulator(name: string, depth?: number) {
65-
if (!depth) {
66-
idAccumulator = [name];
67-
} else if (depth > lastDepth) {
68-
idAccumulator.push(name);
69-
} else if (depth === lastDepth) {
70-
idAccumulator[idAccumulator.length - 1] = name;
71-
} else {
72-
idAccumulator = idAccumulator.slice(0, depth);
73-
idAccumulator[depth] = name;
74-
}
75-
lastDepth = depth ?? 0;
76-
return [...idAccumulator];
77-
}
7861
export const DiagramWithEditInteractions: Story = {
7962
decorators: [DiagramEditableInteractionsDecorator],
8063
args: {
@@ -93,7 +76,6 @@ export const DiagramWithEditInteractions: Story = {
9376
fields: [
9477
...ORDERS_NODE.fields.map(field => ({
9578
...field,
96-
id: idFromDepthAccumulator(field.name, field.depth),
9779
selectable: true,
9880
editable: true,
9981
})),
@@ -108,7 +90,16 @@ export const DiagramWithEditInteractions: Story = {
10890
fields: [
10991
...EMPLOYEES_NODE.fields.map(field => ({
11092
...field,
111-
id: idFromDepthAccumulator(field.name, field.depth),
93+
selectable: true,
94+
editable: true,
95+
})),
96+
],
97+
},
98+
{
99+
...CUSTOMERS_NODE,
100+
fields: [
101+
...CUSTOMERS_NODE.fields.map(field => ({
102+
...field,
112103
selectable: true,
113104
editable: true,
114105
})),

src/mocks/datasets/nodes.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,56 @@ export const EMPLOYEE_TERRITORIES_NODE: NodeProps = {
4646
{ name: 'employeeTerritory', type: 'string', glyphs: ['key'], id: ['employeeTerritory'] },
4747
],
4848
};
49+
50+
export const CUSTOMERS_NODE: NodeProps = {
51+
id: 'customers',
52+
type: 'table',
53+
position: {
54+
x: 500,
55+
y: 100,
56+
},
57+
title: 'customers',
58+
fields: [
59+
{
60+
name: 'customerId',
61+
type: 'string',
62+
glyphs: ['key'],
63+
id: ['customerId'],
64+
},
65+
{
66+
name: 'contact',
67+
type: 'object',
68+
id: ['contact'],
69+
},
70+
{
71+
name: 'address',
72+
type: 'object',
73+
id: ['contact', 'address'],
74+
depth: 1,
75+
},
76+
{
77+
name: 'city',
78+
type: 'string',
79+
id: ['contact', 'address', 'city'],
80+
depth: 2,
81+
},
82+
{
83+
name: 'street',
84+
type: 'string',
85+
id: ['contact', 'address', 'street'],
86+
depth: 2,
87+
},
88+
{
89+
name: 'phone',
90+
type: 'string',
91+
id: ['contact', 'phone'],
92+
depth: 1,
93+
},
94+
{
95+
name: 'email',
96+
type: 'string',
97+
id: ['contact', 'email'],
98+
depth: 1,
99+
},
100+
],
101+
};

src/mocks/decorators/diagram-editable-interactions.decorator.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,16 @@ export const useEditableNodes = (initialNodes: NodeProps[]) => {
257257

258258
const onFieldExpandToggle = useCallback((_evt: ReactMouseEvent, nodeId: string, fieldId: FieldId) => {
259259
setNodes(nodes =>
260-
nodes.map(node =>
261-
node.id === nodeId
260+
nodes.map(node => {
261+
return node.id === nodeId
262262
? {
263263
...node,
264264
fields: node.fields.map(field => {
265-
return field.id === fieldId ? { ...field, expanded: !field.expanded } : field;
265+
return field.id === fieldId ? { ...field, expanded: field.expanded === false } : field;
266266
}),
267267
}
268-
: node,
269-
),
268+
: node;
269+
}),
270270
);
271271
}, []);
272272

src/utilities/convert-nodes.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,93 @@ describe('convert-nodes', () => {
310310
},
311311
});
312312
});
313+
it('Should resolve expansion and assign expandability - multiple level', () => {
314+
const fields = [
315+
{ id: ['level0Expanded'], name: 'level0Expanded', expanded: true },
316+
{ id: ['level0Expanded', 'level1Expanded'], name: 'level1Expanded', expanded: true, depth: 1 },
317+
{ id: ['level0Expanded', 'level1Expanded', 'child1'], name: 'visibleChild', depth: 2 },
318+
{ id: ['level0Expanded', 'level1Collapsed'], name: 'level1Collapsed', expanded: false, depth: 1 },
319+
{ id: ['level0Expanded', 'level1Collapsed', 'child1'], name: 'invisibleChild', depth: 2 },
320+
{ id: ['level0Collapsed'], name: 'level0Collapsed', expanded: false },
321+
{ id: ['level0Collapsed', 'level1Expanded'], name: 'level1Expanded', expanded: true, depth: 1 },
322+
{ id: ['level0Collapsed', 'level1Expanded', 'child1'], name: 'anotherInvisibleChild', depth: 2 },
323+
];
324+
const externalNode = {
325+
id: 'node-1',
326+
type: 'table' as const,
327+
position: { x: 100, y: 200 },
328+
title: 'some-title',
329+
fields,
330+
};
331+
332+
const result = convertToInternalNode(externalNode);
333+
expect(result).toEqual({
334+
id: 'node-1',
335+
type: 'table',
336+
position: { x: 100, y: 200 },
337+
connectable: false,
338+
data: {
339+
title: 'some-title',
340+
fields: [
341+
{ id: ['level0Expanded'], name: 'level0Expanded', expanded: true, hasChildren: true, isVisible: true },
342+
{
343+
id: ['level0Expanded', 'level1Expanded'],
344+
name: 'level1Expanded',
345+
expanded: true,
346+
depth: 1,
347+
hasChildren: true,
348+
isVisible: true,
349+
},
350+
{
351+
id: ['level0Expanded', 'level1Expanded', 'child1'],
352+
name: 'visibleChild',
353+
depth: 2,
354+
hasChildren: false,
355+
isVisible: true,
356+
},
357+
{
358+
id: ['level0Expanded', 'level1Collapsed'],
359+
name: 'level1Collapsed',
360+
expanded: false,
361+
depth: 1,
362+
hasChildren: true,
363+
isVisible: true,
364+
},
365+
{
366+
id: ['level0Expanded', 'level1Collapsed', 'child1'],
367+
name: 'invisibleChild',
368+
depth: 2,
369+
hasChildren: false,
370+
isVisible: false,
371+
},
372+
{
373+
id: ['level0Collapsed'],
374+
name: 'level0Collapsed',
375+
expanded: false,
376+
hasChildren: true,
377+
isVisible: true,
378+
},
379+
{
380+
id: ['level0Collapsed', 'level1Expanded'],
381+
name: 'level1Expanded',
382+
expanded: true,
383+
depth: 1,
384+
hasChildren: true,
385+
isVisible: false,
386+
},
387+
{
388+
id: ['level0Collapsed', 'level1Expanded', 'child1'],
389+
name: 'anotherInvisibleChild',
390+
depth: 2,
391+
hasChildren: false,
392+
isVisible: false,
393+
},
394+
],
395+
borderVariant: undefined,
396+
disabled: undefined,
397+
},
398+
});
399+
});
313400
});
314401

315402
describe('convertToInternalNodes', () => {

src/utilities/convert-nodes.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,14 @@ function hasChildren(field: NodeField, index: number, fields: NodeField[]): bool
3030
// We also annotate each field with whether it is expandable (has children)
3131
// This is more reliable than checking the type of the field, since the object could be hidden in arrays, or simply have no children
3232
function getInternalFields(fields: NodeField[]): InternalNodeField[] {
33-
let isParentCollapsed = false;
34-
let parentDepth = 1;
33+
const parentsWithVisibleChildren = new Set();
3534
const fieldsWithExpandStatus = fields.map((field, index) => {
3635
const fieldDepth = field.depth ?? 0;
37-
const noLongerAChild = fieldDepth <= parentDepth;
38-
const isVisible = !isParentCollapsed || noLongerAChild;
39-
if (noLongerAChild) {
40-
parentDepth = fieldDepth;
41-
isParentCollapsed = field.expanded === false;
36+
const parentField = field.id?.slice(0, -1);
37+
const isVisible = fieldDepth === 0 || parentsWithVisibleChildren.has(JSON.stringify(parentField));
38+
const isExpanded = field.expanded ?? true;
39+
if (isVisible && isExpanded) {
40+
parentsWithVisibleChildren.add(JSON.stringify(field.id));
4241
}
4342
return {
4443
...field,

0 commit comments

Comments
 (0)