Skip to content

Commit 4c1863f

Browse files
committed
feat: field sidebar COMPASS-9659
1 parent bfd0ab4 commit 4c1863f

File tree

8 files changed

+347
-342
lines changed

8 files changed

+347
-342
lines changed

package-lock.json

Lines changed: 172 additions & 220 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-data-modeling/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"@mongodb-js/compass-user-data": "^0.9.0",
6464
"@mongodb-js/compass-utils": "^0.9.10",
6565
"@mongodb-js/compass-workspaces": "^0.51.0",
66-
"@mongodb-js/diagramming": "^1.3.3",
66+
"@mongodb-js/diagramming": "^1.4.0",
6767
"bson": "^6.10.4",
6868
"compass-preferences-model": "^2.50.0",
6969
"html-to-image": "1.11.11",

packages/compass-data-modeling/src/components/diagram-editor.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
type DiagramState,
1616
selectCurrentModelFromState,
1717
createNewRelationship,
18+
selectField,
1819
} from '../store/diagram';
1920
import {
2021
Banner,
@@ -34,7 +35,7 @@ import {
3435
type EdgeProps,
3536
useDiagram,
3637
} from '@mongodb-js/diagramming';
37-
import type { StaticModel } from '../services/data-model-storage';
38+
import type { FieldPath, StaticModel } from '../services/data-model-storage';
3839
import DiagramEditorToolbar from './diagram-editor-toolbar';
3940
import ExportDiagramModal from './export-diagram-modal';
4041
import { DATA_MODELING_DRAWER_ID } from './drawer/diagram-editor-side-panel';
@@ -110,6 +111,7 @@ const DiagramContent: React.FunctionComponent<{
110111
onMoveCollection: (ns: string, newPosition: [number, number]) => void;
111112
onCollectionSelect: (namespace: string) => void;
112113
onRelationshipSelect: (rId: string) => void;
114+
onFieldSelect: (namespace: string, fieldPath: FieldPath) => void;
113115
onDiagramBackgroundClicked: () => void;
114116
selectedItems: SelectedItems;
115117
onCreateNewRelationship: (source: string, target: string) => void;
@@ -121,6 +123,7 @@ const DiagramContent: React.FunctionComponent<{
121123
onMoveCollection,
122124
onCollectionSelect,
123125
onRelationshipSelect,
126+
onFieldSelect,
124127
onDiagramBackgroundClicked,
125128
onCreateNewRelationship,
126129
onRelationshipDrawn,
@@ -207,6 +210,7 @@ const DiagramContent: React.FunctionComponent<{
207210
// dragging
208211
nodeDragThreshold={5}
209212
onNodeClick={(_evt, node) => {
213+
console.log('onNodeClick', node);
210214
if (node.type !== 'collection') {
211215
return;
212216
}
@@ -218,6 +222,12 @@ const DiagramContent: React.FunctionComponent<{
218222
onRelationshipSelect(edge.id);
219223
openDrawer(DATA_MODELING_DRAWER_ID);
220224
}}
225+
onFieldClick={(_evt, { id: fieldPath, nodeId: namespace }) => {
226+
_evt.stopPropagation(); // TODO: should this be handled by the diagramming package?
227+
if (!Array.isArray(fieldPath)) return; // TODO: could be avoided with generics in the diagramming package
228+
onFieldSelect(namespace, fieldPath);
229+
openDrawer(DATA_MODELING_DRAWER_ID);
230+
}}
221231
fitViewOptions={{
222232
maxZoom: 1,
223233
minZoom: 0.25,
@@ -247,6 +257,7 @@ const ConnectedDiagramContent = connect(
247257
onMoveCollection: moveCollection,
248258
onCollectionSelect: selectCollection,
249259
onRelationshipSelect: selectRelationship,
260+
onFieldSelect: selectField,
250261
onDiagramBackgroundClicked: selectBackground,
251262
onCreateNewRelationship: createNewRelationship,
252263
}

packages/compass-data-modeling/src/components/drawer/collection-drawer-content.tsx

Lines changed: 11 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@ import React, { useMemo } from 'react';
22
import { connect } from 'react-redux';
33
import toNS from 'mongodb-ns';
44
import type { Relationship } from '../../services/data-model-storage';
5-
import {
6-
Badge,
7-
Button,
8-
IconButton,
9-
css,
10-
palette,
11-
spacing,
12-
TextInput,
13-
Icon,
14-
TextArea,
15-
} from '@mongodb-js/compass-components';
5+
import { TextInput, TextArea } from '@mongodb-js/compass-components';
166
import {
177
createNewRelationship,
188
deleteRelationship,
@@ -28,6 +18,7 @@ import {
2818
DMFormFieldContainer,
2919
} from './drawer-section-components';
3020
import { useChangeOnBlur } from './use-change-on-blur';
21+
import { RelationshipsSection } from './relationships-section';
3122

3223
type CollectionDrawerContentProps = {
3324
namespace: string;
@@ -41,39 +32,6 @@ type CollectionDrawerContentProps = {
4132
onRenameCollection: (fromNS: string, toNS: string) => void;
4233
};
4334

44-
const titleBtnStyles = css({
45-
marginLeft: 'auto',
46-
maxHeight: 20, // To match accordion line height
47-
});
48-
49-
const emptyRelationshipMessageStyles = css({
50-
color: palette.gray.dark1,
51-
});
52-
53-
const relationshipsListStyles = css({
54-
display: 'flex',
55-
flexDirection: 'column',
56-
gap: spacing[200],
57-
});
58-
59-
const relationshipItemStyles = css({
60-
display: 'flex',
61-
alignItems: 'center',
62-
});
63-
64-
const relationshipNameStyles = css({
65-
flexGrow: 1,
66-
overflow: 'hidden',
67-
textOverflow: 'ellipsis',
68-
whiteSpace: 'nowrap',
69-
minWidth: 0,
70-
paddingRight: spacing[200],
71-
});
72-
73-
const relationshipContentStyles = css({
74-
marginTop: spacing[400],
75-
});
76-
7735
export function getIsCollectionNameValid(
7836
collectionName: string,
7937
namespaces: string[],
@@ -161,72 +119,15 @@ const CollectionDrawerContent: React.FunctionComponent<
161119
</DMFormFieldContainer>
162120
</DMDrawerSection>
163121

164-
<DMDrawerSection
165-
label={
166-
<>
167-
Relationships&nbsp;
168-
<Badge>{relationships.length}</Badge>
169-
<Button
170-
className={titleBtnStyles}
171-
size="xsmall"
172-
onClick={() => {
173-
onCreateNewRelationshipClick(namespace);
174-
}}
175-
>
176-
Add relationship
177-
</Button>
178-
</>
179-
}
180-
>
181-
<div className={relationshipContentStyles}>
182-
{!relationships.length ? (
183-
<div className={emptyRelationshipMessageStyles}>
184-
This collection does not have any relationships yet.
185-
</div>
186-
) : (
187-
<ul className={relationshipsListStyles}>
188-
{relationships.map((r) => {
189-
const relationshipLabel = getDefaultRelationshipName(
190-
r.relationship
191-
);
192-
193-
return (
194-
<li
195-
key={r.id}
196-
data-relationship-id={r.id}
197-
className={relationshipItemStyles}
198-
>
199-
<span
200-
className={relationshipNameStyles}
201-
title={relationshipLabel}
202-
>
203-
{relationshipLabel}
204-
</span>
205-
<IconButton
206-
aria-label="Edit relationship"
207-
title="Edit relationship"
208-
onClick={() => {
209-
onEditRelationshipClick(r.id);
210-
}}
211-
>
212-
<Icon glyph="Edit" />
213-
</IconButton>
214-
<IconButton
215-
aria-label="Delete relationship"
216-
title="Delete relationship"
217-
onClick={() => {
218-
onDeleteRelationshipClick(r.id);
219-
}}
220-
>
221-
<Icon glyph="Trash" />
222-
</IconButton>
223-
</li>
224-
);
225-
})}
226-
</ul>
227-
)}
228-
</div>
229-
</DMDrawerSection>
122+
<RelationshipsSection
123+
relationships={relationships}
124+
getRelationshipLabel={getDefaultRelationshipName}
125+
onCreateNewRelationshipClick={() => {
126+
onCreateNewRelationshipClick(namespace);
127+
}}
128+
onEditRelationshipClick={onEditRelationshipClick}
129+
onDeleteRelationshipClick={onDeleteRelationshipClick}
130+
/>
230131

231132
<DMDrawerSection label="Notes">
232133
<DMFormFieldContainer>

packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.tsx

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import {
1212
deleteCollection,
1313
deleteRelationship,
1414
selectCurrentModelFromState,
15+
type SelectedItems,
1516
} from '../../store/diagram';
1617
import { getDefaultRelationshipName } from '../../utils';
18+
import FieldDrawerContent from './field-drawer-content';
19+
import type { FieldPath } from '../../services/data-model-storage';
1720

1821
export const DATA_MODELING_DRAWER_ID = 'data-modeling-drawer';
1922

@@ -32,19 +35,17 @@ const drawerTitleTextStyles = css({
3235
const drawerTitleActionGroupStyles = css({});
3336

3437
type DiagramEditorSidePanelProps = {
35-
selectedItems: {
36-
id: string;
37-
type: 'relationship' | 'collection';
38-
label: string;
39-
} | null;
38+
selectedItems: (SelectedItems & { label: string }) | null;
4039
onDeleteCollection: (ns: string) => void;
4140
onDeleteRelationship: (rId: string) => void;
41+
onDeleteField: (ns: string, fieldPath: FieldPath) => void;
4242
};
4343

4444
function DiagramEditorSidePanel({
4545
selectedItems,
4646
onDeleteCollection,
4747
onDeleteRelationship,
48+
onDeleteField,
4849
}: DiagramEditorSidePanelProps) {
4950
const { content, label, actions, handleAction } = useMemo(() => {
5051
if (selectedItems?.type === 'collection') {
@@ -91,8 +92,31 @@ function DiagramEditorSidePanel({
9192
};
9293
}
9394

95+
if (selectedItems?.type === 'field') {
96+
return {
97+
label: selectedItems.label,
98+
content: (
99+
<FieldDrawerContent
100+
key={`${selectedItems.namespace}.${JSON.stringify(
101+
selectedItems.fieldPath
102+
)}`}
103+
namespace={selectedItems.namespace}
104+
fieldPath={selectedItems.fieldPath}
105+
></FieldDrawerContent>
106+
),
107+
actions: [
108+
{ action: 'delete', label: 'Delete', icon: 'Trash' as const },
109+
],
110+
handleAction: (actionName: string) => {
111+
if (actionName === 'delete') {
112+
onDeleteField(selectedItems.namespace, selectedItems.fieldPath);
113+
}
114+
},
115+
};
116+
}
117+
94118
return { content: null };
95-
}, [selectedItems, onDeleteCollection, onDeleteRelationship]);
119+
}, [selectedItems, onDeleteCollection, onDeleteRelationship, onDeleteField]);
96120

97121
if (!content) {
98122
return null;
@@ -184,9 +208,30 @@ export default connect(
184208
},
185209
};
186210
}
211+
212+
if (selected.type === 'field') {
213+
// const collection = model.collections.find((collection) => (collection.ns === selected.namespace));
214+
// const field =
215+
216+
// if (!field) {
217+
// // TODO(COMPASS-9680): When the selected field doesn't exist we don't
218+
// // show any selection.
219+
// return {
220+
// selectedItems: null,
221+
// };
222+
// }
223+
224+
return {
225+
selectedItems: {
226+
...selected,
227+
label: selected.fieldPath.join('.'),
228+
},
229+
};
230+
}
187231
},
188232
{
189233
onDeleteCollection: deleteCollection,
190234
onDeleteRelationship: deleteRelationship,
235+
onDeleteField: () => {}, // TODO onDeleteField,
191236
}
192237
)(DiagramEditorSidePanel);

packages/compass-data-modeling/src/services/data-model-storage.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { z } from '@mongodb-js/compass-user-data';
2+
import { JSON_SCHEMA } from 'js-yaml';
23
import type { MongoDBJSONSchema } from 'mongodb-schema';
34

5+
export const FieldPathSchema = z.array(z.string());
6+
7+
export type FieldPath = z.output<typeof FieldPathSchema>;
8+
9+
export const FieldSchema = z.custom<MongoDBJSONSchema>();
10+
11+
export type FieldSchema = z.output<typeof FieldSchema>;
12+
413
export const RelationshipSideSchema = z.object({
514
ns: z.string().nullable(),
615
cardinality: z.number(),
@@ -80,6 +89,44 @@ const EditSchemaVariants = z.discriminatedUnion('type', [
8089
ns: z.string(),
8190
note: z.string(),
8291
}),
92+
// Field operations
93+
z.object({
94+
type: z.literal('RenameField'),
95+
ns: z.string(),
96+
from: FieldPathSchema,
97+
to: FieldPathSchema,
98+
}),
99+
z.object({
100+
type: z.literal('RemoveField'),
101+
ns: z.string(),
102+
field: FieldPathSchema,
103+
}),
104+
z.object({
105+
type: z.literal('ChangeFieldType'),
106+
ns: z.string(),
107+
field: FieldPathSchema,
108+
from: FieldSchema,
109+
to: FieldSchema,
110+
}),
111+
z.object({
112+
type: z.literal('AddField'),
113+
ns: z.string(),
114+
field: FieldPathSchema,
115+
jsonSchema: FieldSchema,
116+
}),
117+
z.object({
118+
type: z.literal('DuplicateField'),
119+
ns: z.string(),
120+
field: FieldPathSchema,
121+
}),
122+
z.object({
123+
type: z.literal('MoveField'),
124+
sourceNS: z.string(),
125+
targetNS: z.string(),
126+
targetField: FieldPathSchema,
127+
field: FieldPathSchema,
128+
jsonSchema: z.custom<MongoDBJSONSchema>(),
129+
}),
83130
]);
84131

85132
export const EditSchema = z.intersection(EditSchemaBase, EditSchemaVariants);

0 commit comments

Comments
 (0)