Skip to content

Commit e8db58d

Browse files
committed
feat(data-modeling): add add field button to object in diagram COMPASS-9742
1 parent 0d862f2 commit e8db58d

File tree

14 files changed

+555
-36
lines changed

14 files changed

+555
-36
lines changed

packages/compass-components/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ export { VisuallyHidden } from '@react-aria/visually-hidden';
9292

9393
export { openToast, closeToast, ToastArea } from './hooks/use-toast';
9494

95-
export { breakpoints, spacing } from '@leafygreen-ui/tokens';
95+
export {
96+
breakpoints,
97+
spacing,
98+
transitionDuration,
99+
} from '@leafygreen-ui/tokens';
96100
import IndexIcon from './components/index-icon';
97101

98102
export { default as FormFieldContainer } from './components/form-field-container';

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { connect } from 'react-redux';
99
import type { DataModelingState } from '../store/reducer';
1010
import {
1111
addNewFieldToCollection,
12+
onAddNestedField,
1213
moveCollection,
1314
selectCollection,
1415
selectRelationship,
@@ -113,6 +114,7 @@ const DiagramContent: React.FunctionComponent<{
113114
editErrors?: string[];
114115
newCollection?: string;
115116
onAddNewFieldToCollection: (ns: string) => void;
117+
onAddNestedField: (ns: string, parentFieldPath: string[]) => void;
116118
onMoveCollection: (ns: string, newPosition: [number, number]) => void;
117119
onCollectionSelect: (namespace: string) => void;
118120
onRelationshipSelect: (rId: string) => void;
@@ -133,6 +135,7 @@ const DiagramContent: React.FunctionComponent<{
133135
isInRelationshipDrawingMode,
134136
newCollection,
135137
onAddNewFieldToCollection,
138+
onAddNestedField,
136139
onMoveCollection,
137140
onCollectionSelect,
138141
onRelationshipSelect,
@@ -183,6 +186,8 @@ const DiagramContent: React.FunctionComponent<{
183186
: undefined,
184187
onClickAddNewFieldToCollection: () =>
185188
onAddNewFieldToCollection(coll.ns),
189+
onClickAddNestedField: (parentFieldPath: string[]) =>
190+
onAddNestedField(coll.ns, parentFieldPath),
186191
selected,
187192
isInRelationshipDrawingMode,
188193
});
@@ -193,6 +198,7 @@ const DiagramContent: React.FunctionComponent<{
193198
model?.relationships,
194199
selectedItems,
195200
isInRelationshipDrawingMode,
201+
onAddNestedField,
196202
]);
197203

198204
// Fit to view on initial mount
@@ -309,6 +315,7 @@ const ConnectedDiagramContent = connect(
309315
},
310316
{
311317
onAddNewFieldToCollection: addNewFieldToCollection,
318+
onAddNestedField: onAddNestedField,
312319
onMoveCollection: moveCollection,
313320
onCollectionSelect: selectCollection,
314321
onRelationshipSelect: selectRelationship,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useCallback } from 'react';
2+
import {
3+
css,
4+
cx,
5+
focusRing,
6+
palette,
7+
spacing,
8+
transitionDuration,
9+
transparentize,
10+
useDarkMode,
11+
} from '@mongodb-js/compass-components';
12+
13+
import PlusWithSquare from '../icons/plus-with-square';
14+
15+
const objectTypeContainerStyles = css({
16+
display: 'flex',
17+
justifyContent: 'flex-end',
18+
alignItems: 'center',
19+
});
20+
21+
const iconButtonHoverStyles = css({
22+
color: palette.gray.dark1,
23+
24+
'&::before': {
25+
content: '""',
26+
transition: `${transitionDuration.default}ms all ease-in-out`,
27+
position: 'absolute',
28+
top: 0,
29+
bottom: 0,
30+
left: 0,
31+
right: 0,
32+
borderRadius: '100%',
33+
transform: 'scale(0.8)',
34+
},
35+
36+
[`&:active:before,
37+
&:hover:before,
38+
&:focus:before,
39+
&[data-hover='true']:before,
40+
&[data-focus='true']:before`]: {
41+
transform: 'scale(1)',
42+
},
43+
44+
[`&:active,
45+
&:hover,
46+
&[data-hover='true'],
47+
&:focus-visible,
48+
&[data-focus='true']`]: {
49+
color: palette.black,
50+
51+
'&::before': {
52+
backgroundColor: transparentize(0.9, palette.gray.dark2),
53+
},
54+
},
55+
});
56+
57+
const iconButtonHoverDarkModeStyles = css({
58+
color: palette.gray.light1,
59+
60+
[`&:active,
61+
&:hover,
62+
&[data-hover='true'],
63+
&:focus-visible,
64+
&[data-focus='true']`]: {
65+
color: palette.gray.light3,
66+
67+
'&::before': {
68+
backgroundColor: transparentize(0.9, palette.gray.light2),
69+
},
70+
},
71+
});
72+
73+
const addNestedFieldStyles = css(iconButtonHoverStyles, focusRing, {
74+
background: 'none',
75+
border: 'none',
76+
padding: spacing[100],
77+
margin: 0,
78+
marginLeft: spacing[100],
79+
cursor: 'pointer',
80+
color: 'inherit',
81+
display: 'flex',
82+
});
83+
84+
type ObjectFieldTypeProps = {
85+
onClickAddNestedField: (event: React.MouseEvent<HTMLButtonElement>) => void;
86+
['data-testid']: string;
87+
};
88+
89+
const ObjectFieldType: React.FunctionComponent<ObjectFieldTypeProps> = ({
90+
'data-testid': dataTestId,
91+
onClickAddNestedField: _onClickAddNestedField,
92+
}) => {
93+
const darkMode = useDarkMode();
94+
95+
const onClickAddNestedField = useCallback(
96+
(event: React.MouseEvent<HTMLButtonElement>) => {
97+
// Don't click on the field element.
98+
event.stopPropagation();
99+
_onClickAddNestedField(event);
100+
},
101+
[_onClickAddNestedField]
102+
);
103+
104+
return (
105+
<div className={objectTypeContainerStyles}>
106+
{'{}'}
107+
<button
108+
className={cx(
109+
addNestedFieldStyles,
110+
darkMode && iconButtonHoverDarkModeStyles
111+
)}
112+
data-testid={dataTestId}
113+
onClick={onClickAddNestedField}
114+
aria-label="Add new field"
115+
title="Add Field"
116+
>
117+
<PlusWithSquare />
118+
</button>
119+
</div>
120+
);
121+
};
122+
123+
export { ObjectFieldType };

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ describe('DiagramEditorSidePanel', function () {
171171
expect(nameInput).to.be.visible;
172172
expect(nameInput).to.have.value('alias');
173173

174-
const selectedTypes = getMultiComboboxValues('lg-combobox-datatype');
174+
const selectedTypes = getMultiComboboxValues('field-type-combobox');
175175
expect(selectedTypes).to.have.lengthOf(2);
176176
expect(selectedTypes).to.include('string');
177177
expect(selectedTypes).to.include('int');
@@ -190,7 +190,7 @@ describe('DiagramEditorSidePanel', function () {
190190
expect(nameInput).to.be.visible;
191191
expect(nameInput).to.have.value('_id');
192192

193-
const selectedTypes = getMultiComboboxValues('lg-combobox-datatype');
193+
const selectedTypes = getMultiComboboxValues('field-type-combobox');
194194
expect(selectedTypes).to.have.lengthOf(1);
195195
expect(selectedTypes).to.include('string');
196196
});
@@ -286,9 +286,7 @@ describe('DiagramEditorSidePanel', function () {
286286
expect(screen.getByTitle('routes.airline.name')).to.be.visible;
287287

288288
// before - string
289-
const selectedTypesBefore = getMultiComboboxValues(
290-
'lg-combobox-datatype'
291-
);
289+
const selectedTypesBefore = getMultiComboboxValues('field-type-combobox');
292290
expect(selectedTypesBefore).to.have.members(['string']);
293291

294292
// add int and bool and remove string
@@ -317,9 +315,7 @@ describe('DiagramEditorSidePanel', function () {
317315
expect(screen.getByTitle('routes.airline.name')).to.be.visible;
318316

319317
// before - string
320-
const selectedTypesBefore = getMultiComboboxValues(
321-
'lg-combobox-datatype'
322-
);
318+
const selectedTypesBefore = getMultiComboboxValues('field-type-combobox');
323319
expect(selectedTypesBefore).to.have.members(['string']);
324320

325321
// remove string without adding anything else

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ const FieldDrawerContent: React.FunctionComponent<FieldDrawerContentProps> = ({
172172

173173
<DMFormFieldContainer>
174174
<Combobox
175-
data-testid="lg-combobox-datatype"
175+
data-testid="field-type-combobox"
176176
label="Datatype"
177177
aria-label="Datatype"
178178
disabled={isReadOnly}

packages/compass-data-modeling/src/store/diagram.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,38 @@ export function addNewFieldToCollection(
525525
};
526526
}
527527

528+
export function onAddNestedField(
529+
ns: string,
530+
parentFieldPath: string[]
531+
): DataModelingThunkAction<void, ApplyEditAction | ApplyEditFailedAction> {
532+
return (dispatch, getState) => {
533+
const modelState = selectCurrentModelFromState(getState());
534+
535+
const collection = modelState.collections.find((c) => c.ns === ns);
536+
if (!collection) {
537+
throw new Error('Collection to add field to not found');
538+
}
539+
540+
const edit: Omit<
541+
Extract<Edit, { type: 'AddField' }>,
542+
'id' | 'timestamp'
543+
> = {
544+
type: 'AddField',
545+
ns,
546+
// Use the first unique field name we can use.
547+
field: [
548+
...parentFieldPath,
549+
getNewUnusedFieldName(collection.jsonSchema, parentFieldPath),
550+
],
551+
jsonSchema: {
552+
bsonType: 'string',
553+
},
554+
};
555+
556+
return dispatch(applyEdit(edit));
557+
};
558+
}
559+
528560
export function moveCollection(
529561
ns: string,
530562
newPosition: [number, number]

0 commit comments

Comments
 (0)