From e8db58d8e43df9ddf813aca481fea94eb78bed85 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Tue, 9 Sep 2025 17:22:13 -0400 Subject: [PATCH 01/13] feat(data-modeling): add add field button to object in diagram COMPASS-9742 --- packages/compass-components/src/index.ts | 6 +- .../src/components/diagram-editor.tsx | 7 + .../components/diagram/object-field-type.tsx | 123 +++++++++++++++ .../drawer/diagram-editor-side-panel.spec.tsx | 12 +- .../drawer/field-drawer-content.tsx | 2 +- .../src/store/diagram.ts | 32 ++++ .../src/utils/nodes-and-edges.spec.tsx | 142 +++++++++++++++--- .../src/utils/nodes-and-edges.tsx | 70 ++++++++- .../src/utils/schema.spec.ts | 24 +++ .../compass-data-modeling/src/utils/schema.ts | 20 ++- .../helpers/commands/index.ts | 1 + .../commands/set-multi-combo-box-value.ts | 46 ++++++ .../compass-e2e-tests/helpers/selectors.ts | 12 ++ .../tests/data-modeling-tab.test.ts | 94 ++++++++++++ 14 files changed, 555 insertions(+), 36 deletions(-) create mode 100644 packages/compass-data-modeling/src/components/diagram/object-field-type.tsx create mode 100644 packages/compass-e2e-tests/helpers/commands/set-multi-combo-box-value.ts diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 7e4431ab956..75449fe2c20 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -92,7 +92,11 @@ export { VisuallyHidden } from '@react-aria/visually-hidden'; export { openToast, closeToast, ToastArea } from './hooks/use-toast'; -export { breakpoints, spacing } from '@leafygreen-ui/tokens'; +export { + breakpoints, + spacing, + transitionDuration, +} from '@leafygreen-ui/tokens'; import IndexIcon from './components/index-icon'; export { default as FormFieldContainer } from './components/form-field-container'; diff --git a/packages/compass-data-modeling/src/components/diagram-editor.tsx b/packages/compass-data-modeling/src/components/diagram-editor.tsx index 6f9bb483649..8de646153ea 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.tsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import type { DataModelingState } from '../store/reducer'; import { addNewFieldToCollection, + onAddNestedField, moveCollection, selectCollection, selectRelationship, @@ -113,6 +114,7 @@ const DiagramContent: React.FunctionComponent<{ editErrors?: string[]; newCollection?: string; onAddNewFieldToCollection: (ns: string) => void; + onAddNestedField: (ns: string, parentFieldPath: string[]) => void; onMoveCollection: (ns: string, newPosition: [number, number]) => void; onCollectionSelect: (namespace: string) => void; onRelationshipSelect: (rId: string) => void; @@ -133,6 +135,7 @@ const DiagramContent: React.FunctionComponent<{ isInRelationshipDrawingMode, newCollection, onAddNewFieldToCollection, + onAddNestedField, onMoveCollection, onCollectionSelect, onRelationshipSelect, @@ -183,6 +186,8 @@ const DiagramContent: React.FunctionComponent<{ : undefined, onClickAddNewFieldToCollection: () => onAddNewFieldToCollection(coll.ns), + onClickAddNestedField: (parentFieldPath: string[]) => + onAddNestedField(coll.ns, parentFieldPath), selected, isInRelationshipDrawingMode, }); @@ -193,6 +198,7 @@ const DiagramContent: React.FunctionComponent<{ model?.relationships, selectedItems, isInRelationshipDrawingMode, + onAddNestedField, ]); // Fit to view on initial mount @@ -309,6 +315,7 @@ const ConnectedDiagramContent = connect( }, { onAddNewFieldToCollection: addNewFieldToCollection, + onAddNestedField: onAddNestedField, onMoveCollection: moveCollection, onCollectionSelect: selectCollection, onRelationshipSelect: selectRelationship, diff --git a/packages/compass-data-modeling/src/components/diagram/object-field-type.tsx b/packages/compass-data-modeling/src/components/diagram/object-field-type.tsx new file mode 100644 index 00000000000..01042f89ba0 --- /dev/null +++ b/packages/compass-data-modeling/src/components/diagram/object-field-type.tsx @@ -0,0 +1,123 @@ +import React, { useCallback } from 'react'; +import { + css, + cx, + focusRing, + palette, + spacing, + transitionDuration, + transparentize, + useDarkMode, +} from '@mongodb-js/compass-components'; + +import PlusWithSquare from '../icons/plus-with-square'; + +const objectTypeContainerStyles = css({ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', +}); + +const iconButtonHoverStyles = css({ + color: palette.gray.dark1, + + '&::before': { + content: '""', + transition: `${transitionDuration.default}ms all ease-in-out`, + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + borderRadius: '100%', + transform: 'scale(0.8)', + }, + + [`&:active:before, + &:hover:before, + &:focus:before, + &[data-hover='true']:before, + &[data-focus='true']:before`]: { + transform: 'scale(1)', + }, + + [`&:active, + &:hover, + &[data-hover='true'], + &:focus-visible, + &[data-focus='true']`]: { + color: palette.black, + + '&::before': { + backgroundColor: transparentize(0.9, palette.gray.dark2), + }, + }, +}); + +const iconButtonHoverDarkModeStyles = css({ + color: palette.gray.light1, + + [`&:active, + &:hover, + &[data-hover='true'], + &:focus-visible, + &[data-focus='true']`]: { + color: palette.gray.light3, + + '&::before': { + backgroundColor: transparentize(0.9, palette.gray.light2), + }, + }, +}); + +const addNestedFieldStyles = css(iconButtonHoverStyles, focusRing, { + background: 'none', + border: 'none', + padding: spacing[100], + margin: 0, + marginLeft: spacing[100], + cursor: 'pointer', + color: 'inherit', + display: 'flex', +}); + +type ObjectFieldTypeProps = { + onClickAddNestedField: (event: React.MouseEvent) => void; + ['data-testid']: string; +}; + +const ObjectFieldType: React.FunctionComponent = ({ + 'data-testid': dataTestId, + onClickAddNestedField: _onClickAddNestedField, +}) => { + const darkMode = useDarkMode(); + + const onClickAddNestedField = useCallback( + (event: React.MouseEvent) => { + // Don't click on the field element. + event.stopPropagation(); + _onClickAddNestedField(event); + }, + [_onClickAddNestedField] + ); + + return ( +
+ {'{}'} + +
+ ); +}; + +export { ObjectFieldType }; diff --git a/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx index 13d6fa16228..8d8e4069c0f 100644 --- a/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx +++ b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx @@ -171,7 +171,7 @@ describe('DiagramEditorSidePanel', function () { expect(nameInput).to.be.visible; expect(nameInput).to.have.value('alias'); - const selectedTypes = getMultiComboboxValues('lg-combobox-datatype'); + const selectedTypes = getMultiComboboxValues('field-type-combobox'); expect(selectedTypes).to.have.lengthOf(2); expect(selectedTypes).to.include('string'); expect(selectedTypes).to.include('int'); @@ -190,7 +190,7 @@ describe('DiagramEditorSidePanel', function () { expect(nameInput).to.be.visible; expect(nameInput).to.have.value('_id'); - const selectedTypes = getMultiComboboxValues('lg-combobox-datatype'); + const selectedTypes = getMultiComboboxValues('field-type-combobox'); expect(selectedTypes).to.have.lengthOf(1); expect(selectedTypes).to.include('string'); }); @@ -286,9 +286,7 @@ describe('DiagramEditorSidePanel', function () { expect(screen.getByTitle('routes.airline.name')).to.be.visible; // before - string - const selectedTypesBefore = getMultiComboboxValues( - 'lg-combobox-datatype' - ); + const selectedTypesBefore = getMultiComboboxValues('field-type-combobox'); expect(selectedTypesBefore).to.have.members(['string']); // add int and bool and remove string @@ -317,9 +315,7 @@ describe('DiagramEditorSidePanel', function () { expect(screen.getByTitle('routes.airline.name')).to.be.visible; // before - string - const selectedTypesBefore = getMultiComboboxValues( - 'lg-combobox-datatype' - ); + const selectedTypesBefore = getMultiComboboxValues('field-type-combobox'); expect(selectedTypesBefore).to.have.members(['string']); // remove string without adding anything else diff --git a/packages/compass-data-modeling/src/components/drawer/field-drawer-content.tsx b/packages/compass-data-modeling/src/components/drawer/field-drawer-content.tsx index 226e6937d2b..1af0d1f9a1f 100644 --- a/packages/compass-data-modeling/src/components/drawer/field-drawer-content.tsx +++ b/packages/compass-data-modeling/src/components/drawer/field-drawer-content.tsx @@ -172,7 +172,7 @@ const FieldDrawerContent: React.FunctionComponent = ({ { + return (dispatch, getState) => { + const modelState = selectCurrentModelFromState(getState()); + + const collection = modelState.collections.find((c) => c.ns === ns); + if (!collection) { + throw new Error('Collection to add field to not found'); + } + + const edit: Omit< + Extract, + 'id' | 'timestamp' + > = { + type: 'AddField', + ns, + // Use the first unique field name we can use. + field: [ + ...parentFieldPath, + getNewUnusedFieldName(collection.jsonSchema, parentFieldPath), + ], + jsonSchema: { + bsonType: 'string', + }, + }; + + return dispatch(applyEdit(edit)); + }; +} + export function moveCollection( ns: string, newPosition: [number, number] diff --git a/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx b/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx index 17803c39acb..5013aa49718 100644 --- a/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx +++ b/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx @@ -6,26 +6,121 @@ import { render, userEvent, } from '@mongodb-js/testing-library-compass'; -import { getFieldsFromSchema } from './nodes-and-edges'; +import type { NodeProps } from '@mongodb-js/diagramming'; -describe('getFieldsFromSchema', function () { - const validateMixedType = async ( - type: React.ReactNode, - expectedTooltip: RegExp - ) => { - render(<>{type}); - const mixed = screen.getByText('(mixed)'); - expect(mixed).to.be.visible; - expect(screen.queryByText(expectedTooltip)).to.not.exist; - userEvent.hover(mixed); - await waitFor(() => { - expect(screen.getByText(expectedTooltip)).to.be.visible; +import { + getFieldsFromSchema, + getBaseFieldsFromSchema, +} from './nodes-and-edges'; + +const validateMixedType = async ( + type: React.ReactNode, + expectedTooltip: RegExp +) => { + render(<>{type}); + const mixed = screen.getByText('(mixed)'); + expect(mixed).to.be.visible; + expect(screen.queryByText(expectedTooltip)).to.not.exist; + userEvent.hover(mixed); + await waitFor(() => { + expect(screen.getByText(expectedTooltip)).to.be.visible; + }); +}; + +function withoutObjectReactType(fields: NodeProps['fields']) { + return fields.map((f) => ({ + ...f, + type: + // eslint-disable-next-line @typescript-eslint/no-explicit-any + typeof f.type === 'object' && + (f?.type as any)?.$$typeof === Symbol.for('react.element') + ? 'object' + : f.type, + })); +} + +describe('getBaseFieldsFromSchema', function () { + describe('flat schema', function () { + it('return empty array for empty schema', function () { + const result = getBaseFieldsFromSchema({ jsonSchema: {} }); + expect(result).to.deep.equal([]); }); - }; + it('returns fields for a simple schema', function () { + const result = getBaseFieldsFromSchema({ + jsonSchema: { + bsonType: 'object', + properties: { + name: { bsonType: 'string' }, + age: { bsonType: 'int' }, + }, + }, + }); + expect(result).to.deep.equal([ + { + name: 'name', + id: ['name'], + depth: 0, + }, + { + name: 'age', + id: ['age'], + depth: 0, + }, + ]); + }); + + it('returns fields for an array of mixed (including objects)', function () { + const result = getBaseFieldsFromSchema({ + jsonSchema: { + bsonType: 'object', + properties: { + todos: { + bsonType: 'array', + items: { + anyOf: [ + { + bsonType: 'object', + properties: { + title: { bsonType: 'string' }, + completed: { bsonType: 'boolean' }, + }, + }, + { bsonType: 'string' }, + ], + }, + }, + }, + }, + }); + expect(result).to.deep.equal([ + { + name: 'todos', + id: ['todos'], + depth: 0, + }, + { + name: 'title', + id: ['todos', 'title'], + depth: 1, + }, + { + name: 'completed', + id: ['todos', 'completed'], + depth: 1, + }, + ]); + }); + }); +}); + +describe('getFieldsFromSchema', function () { describe('flat schema', function () { it('return empty array for empty schema', function () { - const result = getFieldsFromSchema({ jsonSchema: {} }); + const result = getFieldsFromSchema({ + jsonSchema: {}, + onClickAddNestedField: () => {}, + }); expect(result).to.deep.equal([]); }); @@ -38,6 +133,7 @@ describe('getFieldsFromSchema', function () { age: { bsonType: 'int' }, }, }, + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { @@ -71,6 +167,7 @@ describe('getFieldsFromSchema', function () { age: { bsonType: ['int', 'string'] }, }, }, + onClickAddNestedField: () => {}, }); expect(result[0]).to.deep.include({ name: 'age', @@ -95,6 +192,7 @@ describe('getFieldsFromSchema', function () { }, }, highlightedFields: [['age']], + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { @@ -141,6 +239,7 @@ describe('getFieldsFromSchema', function () { }, }, highlightedFields: [['age'], ['profession']], + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { @@ -198,8 +297,9 @@ describe('getFieldsFromSchema', function () { }, }, }, + onClickAddNestedField: () => {}, }); - expect(result).to.deep.equal([ + expect(withoutObjectReactType(result)).to.deep.equal([ { name: 'person', id: ['person'], @@ -274,8 +374,9 @@ describe('getFieldsFromSchema', function () { }, }, highlightedFields: [['person', 'address', 'street']], + onClickAddNestedField: () => {}, }); - expect(result).to.deep.equal([ + expect(withoutObjectReactType(result)).to.deep.equal([ { name: 'person', id: ['person'], @@ -360,8 +461,9 @@ describe('getFieldsFromSchema', function () { ['person', 'address', 'street'], ['person', 'billingAddress', 'city'], ], + onClickAddNestedField: () => {}, }); - expect(result).to.deep.equal([ + expect(withoutObjectReactType(result)).to.deep.equal([ { name: 'person', id: ['person'], @@ -456,6 +558,7 @@ describe('getFieldsFromSchema', function () { }, }, }, + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { @@ -488,6 +591,7 @@ describe('getFieldsFromSchema', function () { }, }, }, + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { @@ -542,6 +646,7 @@ describe('getFieldsFromSchema', function () { }, }, }, + onClickAddNestedField: () => {}, }); expect(result).to.have.lengthOf(3); expect(result[0]).to.deep.include({ @@ -598,6 +703,7 @@ describe('getFieldsFromSchema', function () { }, }, }, + onClickAddNestedField: () => {}, }); expect(result).to.deep.equal([ { diff --git a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx index fb49b7f4977..53f8c6170eb 100644 --- a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx +++ b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx @@ -17,6 +17,7 @@ import type { import { traverseSchema } from './schema-traversal'; import { areFieldPathsEqual } from './utils'; import PlusWithSquare from '../components/icons/plus-with-square'; +import { ObjectFieldType } from '../components/diagram/object-field-type'; function getBsonTypeName(bsonType: string) { switch (bsonType) { @@ -37,12 +38,30 @@ const mixedTypeTooltipContentStyles = css({ textAlign: 'left', }); -function getFieldTypeDisplay(bsonTypes: string[]) { +function getFieldTypeDisplay({ + bsonTypes, + onClickAddNestedField, + typeDisplayTestId, +}: { + bsonTypes: string[]; + onClickAddNestedField: () => void; + typeDisplayTestId: string; +}) { if (bsonTypes.length === 0) { return 'unknown'; } if (bsonTypes.length === 1) { + if (bsonTypes[0] === 'object') { + // Custom renderer for object types to include the "add field" button. + return ( + + ); + } + return getBsonTypeName(bsonTypes[0]); } @@ -88,10 +107,12 @@ export const getFieldsFromSchema = ({ jsonSchema, highlightedFields = [], selectedField, + onClickAddNestedField, }: { jsonSchema: MongoDBJSONSchema; highlightedFields?: FieldPath[]; selectedField?: FieldPath; + onClickAddNestedField: (parentFieldPath: string[]) => void; }): NodeProps['fields'] => { if (!jsonSchema || !jsonSchema.properties) { return []; @@ -104,14 +125,20 @@ export const getFieldsFromSchema = ({ fields.push({ name: fieldPath[fieldPath.length - 1], id: fieldPath, - type: getFieldTypeDisplay(fieldTypes), + type: getFieldTypeDisplay({ + bsonTypes: fieldTypes, + typeDisplayTestId: `data-model-field-type-${fieldPath.join('-')}`, // Could have duplications, that's okay for test ids. + onClickAddNestedField: () => onClickAddNestedField(fieldPath), + }), depth: fieldPath.length - 1, glyphs: fieldTypes.length === 1 && fieldTypes[0] === 'objectId' ? ['key'] : [], selectable: true, - selected: areFieldPathsEqual(fieldPath, selectedField ?? []), + selected: + !!selectedField?.length && + areFieldPathsEqual(fieldPath, selectedField), variant: highlightedFields.length && highlightedFields.some((highlightedField) => @@ -126,6 +153,33 @@ export const getFieldsFromSchema = ({ return fields; }; +/** + * Create the base field list to be used for positioning and measuring in node layouts. + */ +export const getBaseFieldsFromSchema = ({ + jsonSchema, +}: { + jsonSchema: MongoDBJSONSchema; +}): NodeProps['fields'] => { + if (!jsonSchema || !jsonSchema.properties) { + return []; + } + const fields: NodeProps['fields'] = []; + + traverseSchema({ + jsonSchema, + visitor: ({ fieldPath }) => { + fields.push({ + name: fieldPath[fieldPath.length - 1], + id: fieldPath, + depth: fieldPath.length - 1, + }); + }, + }); + + return fields; +}; + /** * Create a base node to be used for positioning and measuring in node layouts. */ @@ -143,7 +197,7 @@ export function collectionToBaseNodeForLayout({ x: displayPosition[0], y: displayPosition[1], }, - fields: getFieldsFromSchema({ jsonSchema }), + fields: getBaseFieldsFromSchema({ jsonSchema }), }; } @@ -156,6 +210,7 @@ type CollectionWithRenderOptions = Pick< selected: boolean; isInRelationshipDrawingMode: boolean; onClickAddNewFieldToCollection: () => void; + onClickAddNestedField: (parentFieldPath: string[]) => void; }; export function collectionToDiagramNode({ @@ -167,6 +222,7 @@ export function collectionToDiagramNode({ selected, isInRelationshipDrawingMode, onClickAddNewFieldToCollection, + onClickAddNestedField, }: CollectionWithRenderOptions): NodeProps { return { id: ns, @@ -177,9 +233,10 @@ export function collectionToDiagramNode({ }, title: toNS(ns).collection, fields: getFieldsFromSchema({ - jsonSchema: jsonSchema, - highlightedFields: highlightedFields[ns] ?? undefined, + jsonSchema, + highlightedFields: highlightedFields[ns], selectedField, + onClickAddNestedField, }), selected, connectable: isInRelationshipDrawingMode, @@ -188,6 +245,7 @@ export function collectionToDiagramNode({ ) => { event.stopPropagation(); onClickAddNewFieldToCollection(); diff --git a/packages/compass-data-modeling/src/utils/schema.spec.ts b/packages/compass-data-modeling/src/utils/schema.spec.ts index e6a923b1f92..2ed09eb8bcd 100644 --- a/packages/compass-data-modeling/src/utils/schema.spec.ts +++ b/packages/compass-data-modeling/src/utils/schema.spec.ts @@ -34,6 +34,30 @@ describe('schema diagram utils', function () { const newFieldName = getNewUnusedFieldName(jsonSchema); expect(newFieldName).to.equal('field-3'); }); + + it('should return a new unused field name in a nested object', function () { + const jsonSchema = { + bsonType: 'object', + properties: { + a: { + bsonType: 'object', + properties: { + 'field-1': { + bsonType: 'string', + }, + 'field-2': { + bsonType: 'string', + }, + }, + }, + b: { + bsonType: 'string', + }, + }, + }; + const newFieldName = getNewUnusedFieldName(jsonSchema, ['a']); + expect(newFieldName).to.equal('field-3'); + }); }); describe('#addFieldToJSONSchema', function () { diff --git a/packages/compass-data-modeling/src/utils/schema.ts b/packages/compass-data-modeling/src/utils/schema.ts index 71e6332663f..ad535c2e462 100644 --- a/packages/compass-data-modeling/src/utils/schema.ts +++ b/packages/compass-data-modeling/src/utils/schema.ts @@ -1,7 +1,23 @@ import type { MongoDBJSONSchema } from 'mongodb-schema'; -export function getNewUnusedFieldName(jsonSchema: MongoDBJSONSchema): string { - const existingFieldNames = new Set(Object.keys(jsonSchema.properties || {})); +export function getNewUnusedFieldName( + jsonSchema: MongoDBJSONSchema, + parentFieldPath: string[] = [] +): string { + const fieldPathToTraverse = [...parentFieldPath]; + let parentJSONSchema: MongoDBJSONSchema | undefined = jsonSchema; + while (fieldPathToTraverse.length > 0) { + const currentField = fieldPathToTraverse.shift(); + if (!currentField) { + throw new Error('Invalid field path to get new field name'); + } + parentJSONSchema = parentJSONSchema?.properties?.[currentField]; + } + + const existingFieldNames = new Set( + Object.keys(parentJSONSchema?.properties || {}) + ); + let i = 1; let fieldName = `field-${i}`; diff --git a/packages/compass-e2e-tests/helpers/commands/index.ts b/packages/compass-e2e-tests/helpers/commands/index.ts index 3d08f092c1e..b4dff872dd0 100644 --- a/packages/compass-e2e-tests/helpers/commands/index.ts +++ b/packages/compass-e2e-tests/helpers/commands/index.ts @@ -53,6 +53,7 @@ export * from './drop-database-from-sidebar'; export * from './toggle-aggregation-side-panel'; export * from './add-wizard'; export * from './set-combo-box-value'; +export * from './set-multi-combo-box-value'; export * from './wait-for-export-to-finish'; export * from './create-index'; export * from './drop-index'; diff --git a/packages/compass-e2e-tests/helpers/commands/set-multi-combo-box-value.ts b/packages/compass-e2e-tests/helpers/commands/set-multi-combo-box-value.ts new file mode 100644 index 00000000000..39b44447029 --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/set-multi-combo-box-value.ts @@ -0,0 +1,46 @@ +import type { CompassBrowser } from '../compass-browser'; + +export async function setMultiComboBoxValue( + browser: CompassBrowser, + comboboxSelector: string, + comboboxInputSelector: string, + comboboxValues: string[] +): Promise { + await browser.$(comboboxSelector).waitForDisplayed(); + + // Clear existing values. + const existingDataTypeRemoveButtonSelector = `${comboboxSelector} [data-testid="chip-dismiss-button"]`; + + while (await browser.$(existingDataTypeRemoveButtonSelector).isDisplayed()) { + await browser.clickVisible(existingDataTypeRemoveButtonSelector); + } + + // Focus the combobox. + await browser.clickVisible(comboboxInputSelector); + const inputElement = browser.$(comboboxInputSelector); + await browser.waitUntil(async () => { + const isFocused = await inputElement.isFocused(); + if (isFocused === true) { + return true; + } else { + // Try to click again. + await inputElement.click(); + return false; + } + }); + + const controlledMenuId: string = await inputElement.getAttribute( + 'aria-controls' + ); + const comboboxListSelectorElement = browser.$( + `[id="${controlledMenuId}"][role="listbox"]` + ); + await comboboxListSelectorElement.waitForDisplayed(); + + for (const value of comboboxValues) { + await browser.setValueVisible(comboboxInputSelector, value); + await browser.keys(['Enter']); + } + await browser.keys(['Escape']); + await comboboxListSelectorElement.waitForDisplayed({ reverse: true }); +} diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index da07b9a5be4..3d9dae03a67 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1451,6 +1451,15 @@ export const DataModelPreviewCollection = (collectionId: string) => `${DataModelPreview} [data-id="${collectionId}"]`; // TODO(COMPASS-9719): add once we upgrade reactflow again in diagramming: [aria-roleDescription="node"] export const DataModelPreviewRelationship = (relationshipId: string) => `${DataModelPreview} [data-id="${relationshipId}"]`; // TODO(COMPASS-9719): add once we upgrade reactflow again in diagramming: [aria-roleDescription="edge"] +export const DataModelCollectionAddFieldBtn = (collectionId: string) => + `${DataModelPreview} [data-id="${collectionId}"] [data-testid="data-model-collection-add-field"]`; // TODO(COMPASS-9719): add once we upgrade reactflow again in diagramming: [aria-roleDescription="node"] +export const DataModelAddNestedFieldBtn = ( + collectionId: string, + fieldPath: string[] +) => + `${DataModelPreview} [data-id="${collectionId}"] [data-testid="data-model-field-type-${fieldPath.join( + '-' + )}"]`; export const DataModelApplyEditor = `${DataModelEditor} [data-testid="apply-editor"]`; export const DataModelEditorApplyButton = `${DataModelApplyEditor} [data-testid="apply-button"]`; export const DataModelUndoButton = 'button[aria-label="Undo"]'; @@ -1478,6 +1487,9 @@ export const DataModelsListItemActions = (diagramName: string) => export const DataModelsListItemDeleteButton = `[data-action="delete"]`; export const DataModelAddRelationshipBtn = 'aria/Add Relationship'; export const DataModelAddCollectionBtn = 'aria/Add Collection'; +export const DataModelFieldTypeCombobox = '[data-testid="field-type-combobox"]'; +export const DataModelFieldTypeComboboxInput = + '[data-testid="field-type-combobox"] [role="combobox"] input'; export const DataModelNameInputLabel = '//label[text()="Name"]'; export const DataModelNameInput = 'input[data-testid="data-model-collection-drawer-name-input"]'; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 91e51119cfc..0addbe9f0e1 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -834,5 +834,99 @@ describe('Data Modeling tab', function () { await browser.clickVisible(Selectors.DataModelUndoButton); await getDiagramNodes(browser, 2); }); + + it('allows field editing', async function () { + const dataModelName = 'Test Edit Collection'; + await setupDiagram(browser, { + diagramName: dataModelName, + connectionName: DEFAULT_CONNECTION_NAME_1, + databaseName: 'test', + }); + + const dataModelEditor = browser.$(Selectors.DataModelEditor); + await dataModelEditor.waitForDisplayed(); + + let collectionText = await browser + .$(Selectors.DataModelPreviewCollection('test.testCollection-one')) + .getText(); + expect(collectionText).to.not.include('field-1'); + + // Add two fields to the collection. + await browser.clickVisible( + Selectors.DataModelCollectionAddFieldBtn('test.testCollection-one') + ); + await browser.clickVisible( + Selectors.DataModelCollectionAddFieldBtn('test.testCollection-one') + ); + + // Verify they both exist. + await browser.waitUntil(async () => { + collectionText = await browser + .$(Selectors.DataModelPreviewCollection('test.testCollection-one')) + .getText(); + return ( + collectionText.includes('field-1') && + collectionText.includes('field-2') + ); + }); + + // Rename the field (field-2 is selected). + await browser.setValueVisible( + browser.$(Selectors.DataModelNameInput), + 'renamedField' + ); + await browser.$(Selectors.SideDrawer).click(); // Unfocus the input. + + // Ensure the name is updated in the diagram. + await browser.waitUntil(async () => { + collectionText = await browser + .$(Selectors.DataModelPreviewCollection('test.testCollection-one')) + .getText(); + const previousNameExists = collectionText.includes('field-2'); + const renamedFieldExists = collectionText.includes('renamedField'); + + return !previousNameExists && renamedFieldExists; + }); + + // Change the field type to object. + await browser.setMultiComboBoxValue( + Selectors.DataModelFieldTypeCombobox, + Selectors.DataModelFieldTypeComboboxInput, + ['object'] + ); + await browser.$(Selectors.SideDrawer).click(); // Unfocus the input. + + // Drag the node into view to ensure the add nested field button is visible. + await dragNode( + browser, + Selectors.DataModelPreviewCollection('test.testCollection-one'), + { x: -200, y: -200 } + ); + await browser.waitForAnimations(dataModelEditor); + + // Add two new fields to the new object. + await browser.clickVisible( + Selectors.DataModelAddNestedFieldBtn('test.testCollection-one', [ + 'renamedField', + ]) + ); + await browser.clickVisible( + Selectors.DataModelAddNestedFieldBtn('test.testCollection-one', [ + 'renamedField', + ]) + ); + + // Ensure the new fields are in the diagram. + await browser.waitUntil(async () => { + collectionText = await browser + .$(Selectors.DataModelPreviewCollection('test.testCollection-one')) + .getText(); + + return ( + collectionText.split('field-1').length === 3 && + collectionText.includes('field-2') + ); + }); + }); }); }); From d23a8f15d10e59db0fe2e69e36a831e720aa7ad2 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 11 Sep 2025 09:25:13 -0400 Subject: [PATCH 02/13] fixup: e2e test drag node for clickability --- .../tests/data-modeling-tab.test.ts | 125 ++++++++++++++++-- 1 file changed, 112 insertions(+), 13 deletions(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 0addbe9f0e1..950cd1c4015 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -234,15 +234,118 @@ async function dragNode( y: Math.round(startPosition.y + 15), // we're aiming for the header area (top of the node) }) .down({ button: 0 }) // Left mouse button - .move({ duration: 1000, origin: 'pointer', ...pointerActionMoveParams }) - .pause(1000) - .move({ duration: 1000, origin: 'pointer', ...pointerActionMoveParams }) + .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) + .pause(250) + .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) .up({ button: 0 }) // Release the left mouse button .perform(); await browser.waitForAnimations(node); return startPosition; } +/** + * Drags the diagram view to show the specified element until it's clickable and clicks. + * This is useful when we're trying to interact with an element that may be partially outside + * of the view. Note that there isn't any check that a node is covering the space that will be + * dragged, so it can end up dragging nodes unintentionally. + **/ +async function dragDiagramToShowAndClick( + browser: CompassBrowser, + selector: string +) { + const targetElement = browser.$(selector); + + let elementPosition = await targetElement.getLocation(); + let elementSize = await targetElement.getSize(); + + const DRAG_INCREMENT = 50; + + let diagramBackgroundPosition = await browser + .$(Selectors.DataModelPreview) + .getLocation(); + + function getDistanceToElementCenter() { + return ( + Math.floor( + elementPosition.x + + elementSize.width / 2 - + (diagramBackgroundPosition.x + 1) + ), + Math.floor( + elementPosition.y + + elementSize.height / 2 - + (diagramBackgroundPosition.y + 1) + ) + ); + } + + async function attemptClick() { + try { + // In the diagram the buttons on nodes will return true for `isClickable` and `isDisplayed` + // withinViewport even when it errors on click. So we have to attempt a click to be sure. + await targetElement.click(); + } catch { + return false; + } + + return true; + } + + while ( + !(await attemptClick()) && + getDistanceToElementCenter() > DRAG_INCREMENT * 4 + ) { + elementPosition = await targetElement.getLocation(); + elementSize = await targetElement.getSize(); + + diagramBackgroundPosition = await browser + .$(Selectors.DataModelPreview) + .getLocation(); + + // Start a bit away from the origin 5 to give space for the drag to happen. + const baseX = Math.round(diagramBackgroundPosition.x + DRAG_INCREMENT + 5); + const baseY = Math.round(diagramBackgroundPosition.y + DRAG_INCREMENT + 5); + + const moveX = + Math.abs(diagramBackgroundPosition.x - elementPosition.x) > + DRAG_INCREMENT * 2 + ? Math.round( + DRAG_INCREMENT * + (diagramBackgroundPosition.x > elementPosition.x ? 1 : -1) + ) + : 0; + const moveY = + Math.abs(diagramBackgroundPosition.y - elementPosition.y) > + DRAG_INCREMENT * 2 + ? Math.round( + DRAG_INCREMENT * + (diagramBackgroundPosition.y > elementPosition.y ? 1 : -1) + ) + : 0; + + await browser + .action('pointer') + .move({ + x: baseX, + y: baseY, + }) + .down({ button: 0 }) // Left mouse button. + .pause(250) + .move({ + duration: 250, + origin: 'pointer', + x: moveX, + y: moveY, + }) + .up({ button: 0 }) // Release the left mouse button. + .perform(); + + await browser.waitForAnimations(Selectors.DataModelPreview); + } + + return elementPosition; +} + describe('Data Modeling tab', function () { let compass: Compass; let browser: CompassBrowser; @@ -851,10 +954,13 @@ describe('Data Modeling tab', function () { .getText(); expect(collectionText).to.not.include('field-1'); - // Add two fields to the collection. - await browser.clickVisible( + // Drag the node into view to ensure the add nested field button is visible. + await dragDiagramToShowAndClick( + browser, Selectors.DataModelCollectionAddFieldBtn('test.testCollection-one') ); + + // Add two fields to the collection. await browser.clickVisible( Selectors.DataModelCollectionAddFieldBtn('test.testCollection-one') ); @@ -897,15 +1003,8 @@ describe('Data Modeling tab', function () { await browser.$(Selectors.SideDrawer).click(); // Unfocus the input. // Drag the node into view to ensure the add nested field button is visible. - await dragNode( + await dragDiagramToShowAndClick( browser, - Selectors.DataModelPreviewCollection('test.testCollection-one'), - { x: -200, y: -200 } - ); - await browser.waitForAnimations(dataModelEditor); - - // Add two new fields to the new object. - await browser.clickVisible( Selectors.DataModelAddNestedFieldBtn('test.testCollection-one', [ 'renamedField', ]) From d1f8d0afc5a7d435bfd9a7ba99a5f303b7f4bda0 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 11 Sep 2025 10:34:52 -0400 Subject: [PATCH 03/13] fixup: proper distance calculation --- .../src/utils/nodes-and-edges.spec.tsx | 7 +------ .../tests/data-modeling-tab.test.ts | 20 +++++++++---------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx b/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx index 5013aa49718..235554338eb 100644 --- a/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx +++ b/packages/compass-data-modeling/src/utils/nodes-and-edges.spec.tsx @@ -30,12 +30,7 @@ const validateMixedType = async ( function withoutObjectReactType(fields: NodeProps['fields']) { return fields.map((f) => ({ ...f, - type: - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof f.type === 'object' && - (f?.type as any)?.$$typeof === Symbol.for('react.element') - ? 'object' - : f.type, + type: React.isValidElement(f.type) ? 'object' : f.type, })); } diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 950cd1c4015..4beb2c24819 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -265,17 +265,15 @@ async function dragDiagramToShowAndClick( .getLocation(); function getDistanceToElementCenter() { - return ( + return Math.abs( Math.floor( - elementPosition.x + - elementSize.width / 2 - - (diagramBackgroundPosition.x + 1) - ), - Math.floor( - elementPosition.y + - elementSize.height / 2 - - (diagramBackgroundPosition.y + 1) - ) + elementPosition.x + elementSize.width / 2 - diagramBackgroundPosition.x + ) + + Math.floor( + elementPosition.y + + elementSize.height / 2 - + diagramBackgroundPosition.y + ) ); } @@ -938,7 +936,7 @@ describe('Data Modeling tab', function () { await getDiagramNodes(browser, 2); }); - it('allows field editing', async function () { + it.only('allows field editing', async function () { const dataModelName = 'Test Edit Collection'; await setupDiagram(browser, { diagramName: dataModelName, From 4059b7a1679210b027b047cb6499d7b2861c2eaa Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 11 Sep 2025 10:35:21 -0400 Subject: [PATCH 04/13] fixup: remove .only ahhhhhh --- packages/compass-e2e-tests/tests/data-modeling-tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 4beb2c24819..289e72207f3 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -936,7 +936,7 @@ describe('Data Modeling tab', function () { await getDiagramNodes(browser, 2); }); - it.only('allows field editing', async function () { + it('allows field editing', async function () { const dataModelName = 'Test Edit Collection'; await setupDiagram(browser, { diagramName: dataModelName, From de2d5fb306ffe86a05975ba259f81be1456f7189 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 17 Sep 2025 12:05:42 -0400 Subject: [PATCH 05/13] fixup: e2e test --- .../src/utils/nodes-and-edges.tsx | 33 +++++++++++-------- .../compass-data-modeling/src/utils/schema.ts | 4 +-- .../tests/data-modeling-tab.test.ts | 7 ++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx index 53f8c6170eb..87da8e0351f 100644 --- a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx +++ b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx @@ -6,7 +6,12 @@ import { InlineDefinition, css, } from '@mongodb-js/compass-components'; -import type { NodeProps, EdgeProps, BaseNode } from '@mongodb-js/diagramming'; +import type { + NodeField, + NodeProps, + EdgeProps, + BaseNode, +} from '@mongodb-js/diagramming'; import type { MongoDBJSONSchema } from 'mongodb-schema'; import type { SelectedItems } from '../store/diagram'; import type { @@ -103,6 +108,14 @@ export const getHighlightedFields = ( return selection; }; +const getBaseNodeField = (fieldPath: string[]): NodeField => { + return { + name: fieldPath[fieldPath.length - 1], + id: fieldPath, + depth: fieldPath.length - 1, + }; +}; + export const getFieldsFromSchema = ({ jsonSchema, highlightedFields = [], @@ -113,24 +126,22 @@ export const getFieldsFromSchema = ({ highlightedFields?: FieldPath[]; selectedField?: FieldPath; onClickAddNestedField: (parentFieldPath: string[]) => void; -}): NodeProps['fields'] => { +}): NodeField[] => { if (!jsonSchema || !jsonSchema.properties) { return []; } - const fields: NodeProps['fields'] = []; + const fields: NodeField[] = []; traverseSchema({ jsonSchema, visitor: ({ fieldPath, fieldTypes }) => { fields.push({ - name: fieldPath[fieldPath.length - 1], - id: fieldPath, + ...getBaseNodeField(fieldPath), type: getFieldTypeDisplay({ bsonTypes: fieldTypes, typeDisplayTestId: `data-model-field-type-${fieldPath.join('-')}`, // Could have duplications, that's okay for test ids. onClickAddNestedField: () => onClickAddNestedField(fieldPath), }), - depth: fieldPath.length - 1, glyphs: fieldTypes.length === 1 && fieldTypes[0] === 'objectId' ? ['key'] @@ -160,20 +171,16 @@ export const getBaseFieldsFromSchema = ({ jsonSchema, }: { jsonSchema: MongoDBJSONSchema; -}): NodeProps['fields'] => { +}): NodeField[] => { if (!jsonSchema || !jsonSchema.properties) { return []; } - const fields: NodeProps['fields'] = []; + const fields: NodeField[] = []; traverseSchema({ jsonSchema, visitor: ({ fieldPath }) => { - fields.push({ - name: fieldPath[fieldPath.length - 1], - id: fieldPath, - depth: fieldPath.length - 1, - }); + fields.push(getBaseNodeField(fieldPath)); }, }); diff --git a/packages/compass-data-modeling/src/utils/schema.ts b/packages/compass-data-modeling/src/utils/schema.ts index ad535c2e462..db545a13b36 100644 --- a/packages/compass-data-modeling/src/utils/schema.ts +++ b/packages/compass-data-modeling/src/utils/schema.ts @@ -4,10 +4,8 @@ export function getNewUnusedFieldName( jsonSchema: MongoDBJSONSchema, parentFieldPath: string[] = [] ): string { - const fieldPathToTraverse = [...parentFieldPath]; let parentJSONSchema: MongoDBJSONSchema | undefined = jsonSchema; - while (fieldPathToTraverse.length > 0) { - const currentField = fieldPathToTraverse.shift(); + for (const currentField of parentFieldPath) { if (!currentField) { throw new Error('Invalid field path to get new field name'); } diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 289e72207f3..1a9a27ad83a 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -234,9 +234,10 @@ async function dragNode( y: Math.round(startPosition.y + 15), // we're aiming for the header area (top of the node) }) .down({ button: 0 }) // Left mouse button - .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) .pause(250) .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) + .pause(250) + // .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) .up({ button: 0 }) // Release the left mouse button .perform(); await browser.waitForAnimations(node); @@ -258,12 +259,13 @@ async function dragDiagramToShowAndClick( let elementPosition = await targetElement.getLocation(); let elementSize = await targetElement.getSize(); - const DRAG_INCREMENT = 50; + const DRAG_INCREMENT = 100; let diagramBackgroundPosition = await browser .$(Selectors.DataModelPreview) .getLocation(); + // Distance from the origin of the diagram to the center of the element. function getDistanceToElementCenter() { return Math.abs( Math.floor( @@ -289,6 +291,7 @@ async function dragDiagramToShowAndClick( return true; } + // Drag in increments as the diagram can be large, and we can't drag off of the page in one go. while ( !(await attemptClick()) && getDistanceToElementCenter() > DRAG_INCREMENT * 4 From 0622b936a21e5ef9aa48d80777b4fe07e5d01639 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Wed, 17 Sep 2025 14:50:30 -0400 Subject: [PATCH 06/13] fixup: drag when drawer opens as well --- .../tests/data-modeling-tab.test.ts | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 1a9a27ad83a..93875044ae6 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -257,7 +257,6 @@ async function dragDiagramToShowAndClick( const targetElement = browser.$(selector); let elementPosition = await targetElement.getLocation(); - let elementSize = await targetElement.getSize(); const DRAG_INCREMENT = 100; @@ -265,20 +264,6 @@ async function dragDiagramToShowAndClick( .$(Selectors.DataModelPreview) .getLocation(); - // Distance from the origin of the diagram to the center of the element. - function getDistanceToElementCenter() { - return Math.abs( - Math.floor( - elementPosition.x + elementSize.width / 2 - diagramBackgroundPosition.x - ) + - Math.floor( - elementPosition.y + - elementSize.height / 2 - - diagramBackgroundPosition.y - ) - ); - } - async function attemptClick() { try { // In the diagram the buttons on nodes will return true for `isClickable` and `isDisplayed` @@ -291,13 +276,11 @@ async function dragDiagramToShowAndClick( return true; } + let dragAndClickAttempts = 0; + // Drag in increments as the diagram can be large, and we can't drag off of the page in one go. - while ( - !(await attemptClick()) && - getDistanceToElementCenter() > DRAG_INCREMENT * 4 - ) { + while (!(await attemptClick())) { elementPosition = await targetElement.getLocation(); - elementSize = await targetElement.getSize(); diagramBackgroundPosition = await browser .$(Selectors.DataModelPreview) @@ -342,6 +325,13 @@ async function dragDiagramToShowAndClick( .perform(); await browser.waitForAnimations(Selectors.DataModelPreview); + + dragAndClickAttempts--; + if (dragAndClickAttempts > 20) { + throw new Error( + `Could not drag the diagram to show and click the element with selector ${selector}. Attempted to reposition and click ${dragAndClickAttempts} times.` + ); + } } return elementPosition; @@ -962,7 +952,8 @@ describe('Data Modeling tab', function () { ); // Add two fields to the collection. - await browser.clickVisible( + await dragDiagramToShowAndClick( + browser, Selectors.DataModelCollectionAddFieldBtn('test.testCollection-one') ); From a38fe21126772ed40ef4c15bd3a90d5de06c71a1 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Thu, 18 Sep 2025 12:23:13 -0400 Subject: [PATCH 07/13] fixup: bump diagramming and e2e test fix --- package-lock.json | 355 ++++++++++++++++-- packages/compass-data-modeling/package.json | 2 +- .../src/utils/nodes-and-edges.tsx | 2 + .../compass-e2e-tests/helpers/selectors.ts | 7 + .../tests/data-modeling-tab.test.ts | 19 +- 5 files changed, 333 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a10f197185..e44919bc6cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9848,26 +9848,6 @@ "url": "https://opencollective.com/node-fetch" } }, - "node_modules/@mongodb-js/diagramming": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.5.1.tgz", - "integrity": "sha512-lyF8VIh+hwFEmou980K4gB9f+PegMaXgFlgQijur4oRZlsIrlmvQ4Gg5r0C/SqVyMn7MQIDiADgZr+NJJ8sd6Q==", - "license": "MIT", - "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^14.3.0", - "@leafygreen-ui/leafygreen-provider": "^5.0.2", - "@leafygreen-ui/palette": "^5.0.0", - "@leafygreen-ui/tokens": "^3.2.1", - "@leafygreen-ui/typography": "^22.1.0", - "@xyflow/react": "12.5.1", - "d3-path": "^3.1.0", - "elkjs": "^0.10.0", - "react": "17.0.2", - "react-dom": "17.0.2" - } - }, "node_modules/@mongodb-js/dl-center": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/dl-center/-/dl-center-1.3.0.tgz", @@ -48625,7 +48605,7 @@ "@mongodb-js/compass-user-data": "^0.9.2", "@mongodb-js/compass-utils": "^0.9.12", "@mongodb-js/compass-workspaces": "^0.53.1", - "@mongodb-js/diagramming": "^1.5.1", + "@mongodb-js/diagramming": "^1.6.0", "bson": "^6.10.4", "compass-preferences-model": "^2.52.1", "html-to-image": "1.11.11", @@ -48660,6 +48640,173 @@ "xvfb-maybe": "^0.2.1" } }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/emotion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.2.tgz", + "integrity": "sha512-pnAWIg7iFZUtpUkhhFATyx+p2qzySKmWrqGNp409+v6EgkfbpQkZIr+52fDzU4Xzc8PgGT+yBg9CTw6W4yzumg==", + "license": "Apache-2.0", + "dependencies": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/hooks": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.3.tgz", + "integrity": "sha512-M3fKuSiz+mEmIi1d8dK5pPbwbELbJFJsonRH+iTHM3vJPTo6w+bCws1g/sYiP7UryogZLogM9/i8NL6nejhRog==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/tokens": "^3.2.4", + "lodash": "^4.17.21" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/hooks/node_modules/@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/icon": { + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.5.0.tgz", + "integrity": "sha512-dZIrwNxTJrDp12OSuk8TU5dzrWSK38zyyL8dZcdXpo3VtWaWnzwZDK6tr08dt+Tgj7Dj2Qchg7+iwotHHbv3Lg==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.2", + "lodash": "^4.17.21" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/leafygreen-provider": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.4.tgz", + "integrity": "sha512-VDlmjTiIqlITVhq4VKUDq8FLySWnHkTxSV2n1sxOanLNPuatOXjxsPmCkPUBXhmQKk/fBf4yQnDKOwJvkyzE6Q==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/hooks": "^9.1.3", + "@leafygreen-ui/lib": "^15.3.0", + "react-transition-group": "^4.4.5" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/leafygreen-provider/node_modules/@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/palette": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.2.tgz", + "integrity": "sha512-+PrfGeJSv4goxm/vKpfJJDOP7t/uElj+14K8jiIyu3qR3TcFRIZ5h1VMvICTUgqvRc8W+xIZYQwsLa2XCu2lvw==", + "license": "Apache-2.0" + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/polymorphic": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-3.0.4.tgz", + "integrity": "sha512-PRpEkgJgke5jEaM4Q3heRzfsLc0HKnW4p7YiXlQxIe0qQcUOYSHiUeBBqh4UwgX7oMz699ZDM/sLC4hp/aNl7Q==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/lib": "^15.3.0", + "lodash": "^4.17.21" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/polymorphic/node_modules/@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.4.tgz", + "integrity": "sha512-Bd11x/ext/vVozd/HL+AD8LbL71Z6B6VbtQ/+qLqoX8qHMsJt7VWL0CmmGs5NVHh3v5sAlfT5DYbB9uhwVM8Qw==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.2", + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/palette": "^5.0.2", + "polished": "^4.2.2" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens/node_modules/@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/typography": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-22.1.2.tgz", + "integrity": "sha512-sULk4TVstU2Zk1l5RZFkkgP7BomxjDX4P3/aFs/2L9kVHYxGBvtykZb3SALPNUSP1L9+U1KDe5qmSckqO2hbmA==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.2", + "@leafygreen-ui/icon": "^14.5.0", + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/palette": "^5.0.2", + "@leafygreen-ui/polymorphic": "^3.0.4", + "@leafygreen-ui/tokens": "^3.2.4" + }, + "peerDependencies": { + "@leafygreen-ui/leafygreen-provider": "^5.0.4" + } + }, + "packages/compass-data-modeling/node_modules/@leafygreen-ui/typography/node_modules/@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "packages/compass-data-modeling/node_modules/@mongodb-js/diagramming": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.6.0.tgz", + "integrity": "sha512-/8Q9kBkXcj16wotHKszGJkoy6wK8lDiMaIcL2Nv2FeXdZP/EfirfhaxTzMYJw7jHM9TaYD6Wl4YNzqdnlrYhTA==", + "license": "MIT", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@leafygreen-ui/icon": "^14.3.0", + "@leafygreen-ui/leafygreen-provider": "^5.0.2", + "@leafygreen-ui/palette": "^5.0.0", + "@leafygreen-ui/tokens": "^3.2.1", + "@leafygreen-ui/typography": "^22.1.0", + "@xyflow/react": "12.5.1", + "d3-path": "^3.1.0", + "elkjs": "^0.10.0", + "react": "17.0.2", + "react-dom": "17.0.2" + } + }, "packages/compass-data-modeling/node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -61969,7 +62116,7 @@ "@mongodb-js/compass-user-data": "^0.9.2", "@mongodb-js/compass-utils": "^0.9.12", "@mongodb-js/compass-workspaces": "^0.53.1", - "@mongodb-js/diagramming": "^1.5.1", + "@mongodb-js/diagramming": "^1.6.0", "@mongodb-js/eslint-config-compass": "^1.4.8", "@mongodb-js/mocha-config-compass": "^1.7.1", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -62002,6 +62149,151 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { + "@leafygreen-ui/emotion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.2.tgz", + "integrity": "sha512-pnAWIg7iFZUtpUkhhFATyx+p2qzySKmWrqGNp409+v6EgkfbpQkZIr+52fDzU4Xzc8PgGT+yBg9CTw6W4yzumg==", + "requires": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + }, + "@leafygreen-ui/hooks": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.3.tgz", + "integrity": "sha512-M3fKuSiz+mEmIi1d8dK5pPbwbELbJFJsonRH+iTHM3vJPTo6w+bCws1g/sYiP7UryogZLogM9/i8NL6nejhRog==", + "requires": { + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/tokens": "^3.2.4", + "lodash": "^4.17.21" + }, + "dependencies": { + "@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "requires": { + "lodash": "^4.17.21" + } + } + } + }, + "@leafygreen-ui/icon": { + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.5.0.tgz", + "integrity": "sha512-dZIrwNxTJrDp12OSuk8TU5dzrWSK38zyyL8dZcdXpo3VtWaWnzwZDK6tr08dt+Tgj7Dj2Qchg7+iwotHHbv3Lg==", + "requires": { + "@leafygreen-ui/emotion": "^5.0.2", + "lodash": "^4.17.21" + } + }, + "@leafygreen-ui/leafygreen-provider": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.4.tgz", + "integrity": "sha512-VDlmjTiIqlITVhq4VKUDq8FLySWnHkTxSV2n1sxOanLNPuatOXjxsPmCkPUBXhmQKk/fBf4yQnDKOwJvkyzE6Q==", + "requires": { + "@leafygreen-ui/hooks": "^9.1.3", + "@leafygreen-ui/lib": "^15.3.0", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "requires": { + "lodash": "^4.17.21" + } + } + } + }, + "@leafygreen-ui/palette": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.2.tgz", + "integrity": "sha512-+PrfGeJSv4goxm/vKpfJJDOP7t/uElj+14K8jiIyu3qR3TcFRIZ5h1VMvICTUgqvRc8W+xIZYQwsLa2XCu2lvw==" + }, + "@leafygreen-ui/polymorphic": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-3.0.4.tgz", + "integrity": "sha512-PRpEkgJgke5jEaM4Q3heRzfsLc0HKnW4p7YiXlQxIe0qQcUOYSHiUeBBqh4UwgX7oMz699ZDM/sLC4hp/aNl7Q==", + "requires": { + "@leafygreen-ui/lib": "^15.3.0", + "lodash": "^4.17.21" + }, + "dependencies": { + "@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "requires": { + "lodash": "^4.17.21" + } + } + } + }, + "@leafygreen-ui/tokens": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.4.tgz", + "integrity": "sha512-Bd11x/ext/vVozd/HL+AD8LbL71Z6B6VbtQ/+qLqoX8qHMsJt7VWL0CmmGs5NVHh3v5sAlfT5DYbB9uhwVM8Qw==", + "requires": { + "@leafygreen-ui/emotion": "^5.0.2", + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/palette": "^5.0.2", + "polished": "^4.2.2" + }, + "dependencies": { + "@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "requires": { + "lodash": "^4.17.21" + } + } + } + }, + "@leafygreen-ui/typography": { + "version": "22.1.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-22.1.2.tgz", + "integrity": "sha512-sULk4TVstU2Zk1l5RZFkkgP7BomxjDX4P3/aFs/2L9kVHYxGBvtykZb3SALPNUSP1L9+U1KDe5qmSckqO2hbmA==", + "requires": { + "@leafygreen-ui/emotion": "^5.0.2", + "@leafygreen-ui/icon": "^14.5.0", + "@leafygreen-ui/lib": "^15.3.0", + "@leafygreen-ui/palette": "^5.0.2", + "@leafygreen-ui/polymorphic": "^3.0.4", + "@leafygreen-ui/tokens": "^3.2.4" + }, + "dependencies": { + "@leafygreen-ui/lib": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", + "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", + "requires": { + "lodash": "^4.17.21" + } + } + } + }, + "@mongodb-js/diagramming": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.6.0.tgz", + "integrity": "sha512-/8Q9kBkXcj16wotHKszGJkoy6wK8lDiMaIcL2Nv2FeXdZP/EfirfhaxTzMYJw7jHM9TaYD6Wl4YNzqdnlrYhTA==", + "requires": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@leafygreen-ui/icon": "^14.3.0", + "@leafygreen-ui/leafygreen-provider": "^5.0.2", + "@leafygreen-ui/palette": "^5.0.0", + "@leafygreen-ui/tokens": "^3.2.1", + "@leafygreen-ui/typography": "^22.1.0", + "@xyflow/react": "12.5.1", + "d3-path": "^3.1.0", + "elkjs": "^0.10.0", + "react": "17.0.2", + "react-dom": "17.0.2" + } + }, "@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -64946,25 +65238,6 @@ } } }, - "@mongodb-js/diagramming": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.5.1.tgz", - "integrity": "sha512-lyF8VIh+hwFEmou980K4gB9f+PegMaXgFlgQijur4oRZlsIrlmvQ4Gg5r0C/SqVyMn7MQIDiADgZr+NJJ8sd6Q==", - "requires": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^13.1.2", - "@leafygreen-ui/leafygreen-provider": "^4.0.2", - "@leafygreen-ui/palette": "^4.1.3", - "@leafygreen-ui/tokens": "^2.11.3", - "@leafygreen-ui/typography": "^20.0.2", - "@xyflow/react": "12.5.1", - "d3-path": "^3.1.0", - "elkjs": "^0.10.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" - } - }, "@mongodb-js/dl-center": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/dl-center/-/dl-center-1.3.0.tgz", diff --git a/packages/compass-data-modeling/package.json b/packages/compass-data-modeling/package.json index 4b0b8f22ac5..b5b40d54238 100644 --- a/packages/compass-data-modeling/package.json +++ b/packages/compass-data-modeling/package.json @@ -63,7 +63,7 @@ "@mongodb-js/compass-user-data": "^0.9.2", "@mongodb-js/compass-utils": "^0.9.12", "@mongodb-js/compass-workspaces": "^0.53.1", - "@mongodb-js/diagramming": "^1.5.1", + "@mongodb-js/diagramming": "^1.6.0", "bson": "^6.10.4", "compass-preferences-model": "^2.52.1", "html-to-image": "1.11.11", diff --git a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx index 87da8e0351f..aad57d5dd6e 100644 --- a/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx +++ b/packages/compass-data-modeling/src/utils/nodes-and-edges.tsx @@ -5,6 +5,7 @@ import { IconButton, InlineDefinition, css, + spacing, } from '@mongodb-js/compass-components'; import type { NodeField, @@ -35,6 +36,7 @@ function getBsonTypeName(bsonType: string) { const addNewFieldStyles = css({ marginLeft: 'auto', + marginRight: spacing[100], }); const mixedTypeTooltipContentStyles = css({ diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 3d9dae03a67..2615a6cf244 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1460,6 +1460,13 @@ export const DataModelAddNestedFieldBtn = ( `${DataModelPreview} [data-id="${collectionId}"] [data-testid="data-model-field-type-${fieldPath.join( '-' )}"]`; +export const DataModelCollectionField = ( + collectionId: string, + fieldPath: string[] +) => + `${DataModelPreview} [data-id="${collectionId}"] [data-testid="selectable-field-${collectionId}-${fieldPath.join( + '.' + )}"]`; // selectable-field-test.testCollection-one-renamedField.field-2 export const DataModelApplyEditor = `${DataModelEditor} [data-testid="apply-editor"]`; export const DataModelEditorApplyButton = `${DataModelApplyEditor} [data-testid="apply-button"]`; export const DataModelUndoButton = 'button[aria-label="Undo"]'; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 93875044ae6..3665ee1cdcb 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -1008,16 +1008,15 @@ describe('Data Modeling tab', function () { ); // Ensure the new fields are in the diagram. - await browser.waitUntil(async () => { - collectionText = await browser - .$(Selectors.DataModelPreviewCollection('test.testCollection-one')) - .getText(); - - return ( - collectionText.split('field-1').length === 3 && - collectionText.includes('field-2') - ); - }); + const newFieldText = await browser + .$( + Selectors.DataModelCollectionField('test.testCollection-one', [ + 'renamedField', + 'field-2', + ]) + ) + .getText(); + expect(newFieldText).to.include('field-2'); }); }); }); From 60b1e0addd531bf32090fbc0cdbeeaf1ff839919 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 19 Sep 2025 12:52:05 -0400 Subject: [PATCH 08/13] fixup: fix dragging --- .../tests/data-modeling-tab.test.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 3665ee1cdcb..42e7443fb35 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -234,10 +234,9 @@ async function dragNode( y: Math.round(startPosition.y + 15), // we're aiming for the header area (top of the node) }) .down({ button: 0 }) // Left mouse button - .pause(250) - .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) - .pause(250) - // .move({ duration: 250, origin: 'pointer', ...pointerActionMoveParams }) + .move({ duration: 500, origin: 'pointer', ...pointerActionMoveParams }) + .pause(500) + .move({ duration: 500, origin: 'pointer', ...pointerActionMoveParams }) .up({ button: 0 }) // Release the left mouse button .perform(); await browser.waitForAnimations(node); @@ -314,6 +313,12 @@ async function dragDiagramToShowAndClick( y: baseY, }) .down({ button: 0 }) // Left mouse button. + .move({ + duration: 250, + origin: 'pointer', + x: moveX, + y: moveY, + }) .pause(250) .move({ duration: 250, @@ -1007,6 +1012,13 @@ describe('Data Modeling tab', function () { ]) ); + // Drag the node to show the new fields. + await dragNode( + browser, + Selectors.DataModelPreviewCollection('test.testCollection-one'), + { x: -100, y: -200 } + ); + // Ensure the new fields are in the diagram. const newFieldText = await browser .$( From 76714f95a42d68548cc8b88bc88c11ca1daaaf51 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 19 Sep 2025 13:25:56 -0400 Subject: [PATCH 09/13] fixup: what a drag --- .../tests/data-modeling-tab.test.ts | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 42e7443fb35..49d0fbd78fc 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -313,12 +313,6 @@ async function dragDiagramToShowAndClick( y: baseY, }) .down({ button: 0 }) // Left mouse button. - .move({ - duration: 250, - origin: 'pointer', - x: moveX, - y: moveY, - }) .pause(250) .move({ duration: 250, @@ -999,7 +993,13 @@ describe('Data Modeling tab', function () { ); await browser.$(Selectors.SideDrawer).click(); // Unfocus the input. - // Drag the node into view to ensure the add nested field button is visible. + // Drag the node to show add new field buttons and new fields. + await dragNode( + browser, + Selectors.DataModelPreviewCollection('test.testCollection-one'), + { x: -100, y: -200 } + ); + await dragDiagramToShowAndClick( browser, Selectors.DataModelAddNestedFieldBtn('test.testCollection-one', [ @@ -1012,13 +1012,6 @@ describe('Data Modeling tab', function () { ]) ); - // Drag the node to show the new fields. - await dragNode( - browser, - Selectors.DataModelPreviewCollection('test.testCollection-one'), - { x: -100, y: -200 } - ); - // Ensure the new fields are in the diagram. const newFieldText = await browser .$( From ef9bbdea9b31b478701a3f38076ceeebfc6fa86b Mon Sep 17 00:00:00 2001 From: Rhys Date: Fri, 19 Sep 2025 16:07:06 -0400 Subject: [PATCH 10/13] Update packages/compass-e2e-tests/tests/data-modeling-tab.test.ts --- packages/compass-e2e-tests/tests/data-modeling-tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index d320e2f566f..b5fe442c692 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -948,7 +948,7 @@ describe('Data Modeling tab', function () { await dragNode( browser, Selectors.DataModelPreviewCollection('test.testCollection-one'), - { x: -100, y: -100 } + { x: -100, y: 0 } ); // Add two fields to the collection. From d9734f9773a4ce6bdb77baf7eba868140a08b5f9 Mon Sep 17 00:00:00 2001 From: Rhys Date: Fri, 19 Sep 2025 18:05:42 -0400 Subject: [PATCH 11/13] Update packages/compass-e2e-tests/tests/data-modeling-tab.test.ts --- packages/compass-e2e-tests/tests/data-modeling-tab.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index b5fe442c692..d492c9275f5 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -1010,6 +1010,13 @@ describe('Data Modeling tab', function () { ]) ); + // Drag the node to show add new field buttons and new fields. + await dragNode( + browser, + Selectors.DataModelPreviewCollection('test.testCollection-one'), + { x: 0, y: -100 } + ); + // Ensure the new fields are in the diagram. const newFieldText = await browser .$( From a4a0e3ab4f3470086d6f2e0f5a42f3369d033d52 Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Fri, 19 Sep 2025 19:17:59 -0400 Subject: [PATCH 12/13] fixup: add wait for animations --- packages/compass-e2e-tests/tests/data-modeling-tab.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index d492c9275f5..47511207621 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -333,6 +333,8 @@ async function dragDiagramToShowAndClick( } } + await browser.waitForAnimations(Selectors.DataModelPreview); + return elementPosition; } From 9ec8c7c46f91983eba5e7cbd4d500d6104e5008c Mon Sep 17 00:00:00 2001 From: Rhys Howell Date: Sat, 20 Sep 2025 06:31:49 -0400 Subject: [PATCH 13/13] fixup: package-lock, drag click --- package-lock.json | 133 ------------------ .../tests/data-modeling-tab.test.ts | 3 +- 2 files changed, 2 insertions(+), 134 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40e2a0caa8d..b6a71e10d3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49178,18 +49178,6 @@ "lodash": "^4.17.21" } }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/hooks/node_modules/@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, "packages/compass-data-modeling/node_modules/@leafygreen-ui/icon": { "version": "14.5.0", "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.5.0.tgz", @@ -49211,18 +49199,6 @@ "react-transition-group": "^4.4.5" } }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/leafygreen-provider/node_modules/@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, "packages/compass-data-modeling/node_modules/@leafygreen-ui/palette": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.2.tgz", @@ -49239,42 +49215,6 @@ "lodash": "^4.17.21" } }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/polymorphic/node_modules/@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.4.tgz", - "integrity": "sha512-Bd11x/ext/vVozd/HL+AD8LbL71Z6B6VbtQ/+qLqoX8qHMsJt7VWL0CmmGs5NVHh3v5sAlfT5DYbB9uhwVM8Qw==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/emotion": "^5.0.2", - "@leafygreen-ui/lib": "^15.3.0", - "@leafygreen-ui/palette": "^5.0.2", - "polished": "^4.2.2" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens/node_modules/@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, "packages/compass-data-modeling/node_modules/@leafygreen-ui/typography": { "version": "22.1.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-22.1.2.tgz", @@ -49292,18 +49232,6 @@ "@leafygreen-ui/leafygreen-provider": "^5.0.4" } }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/typography/node_modules/@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, "packages/compass-data-modeling/node_modules/@mongodb-js/diagramming": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.6.0.tgz", @@ -63016,16 +62944,6 @@ "@leafygreen-ui/lib": "^15.3.0", "@leafygreen-ui/tokens": "^3.2.4", "lodash": "^4.17.21" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "requires": { - "lodash": "^4.17.21" - } - } } }, "@leafygreen-ui/icon": { @@ -63045,16 +62963,6 @@ "@leafygreen-ui/hooks": "^9.1.3", "@leafygreen-ui/lib": "^15.3.0", "react-transition-group": "^4.4.5" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "requires": { - "lodash": "^4.17.21" - } - } } }, "@leafygreen-ui/palette": { @@ -63069,37 +62977,6 @@ "requires": { "@leafygreen-ui/lib": "^15.3.0", "lodash": "^4.17.21" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "requires": { - "lodash": "^4.17.21" - } - } - } - }, - "@leafygreen-ui/tokens": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.4.tgz", - "integrity": "sha512-Bd11x/ext/vVozd/HL+AD8LbL71Z6B6VbtQ/+qLqoX8qHMsJt7VWL0CmmGs5NVHh3v5sAlfT5DYbB9uhwVM8Qw==", - "requires": { - "@leafygreen-ui/emotion": "^5.0.2", - "@leafygreen-ui/lib": "^15.3.0", - "@leafygreen-ui/palette": "^5.0.2", - "polished": "^4.2.2" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "requires": { - "lodash": "^4.17.21" - } - } } }, "@leafygreen-ui/typography": { @@ -63113,16 +62990,6 @@ "@leafygreen-ui/palette": "^5.0.2", "@leafygreen-ui/polymorphic": "^3.0.4", "@leafygreen-ui/tokens": "^3.2.4" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.3.0.tgz", - "integrity": "sha512-WuEd60jLO2u3J2MdMHglKTvqC2DmjA/JI6BpCIU35Gim3ruf6+DF1WIyOhhxibqTtnn/5vEY0oAJrd3728UYCQ==", - "requires": { - "lodash": "^4.17.21" - } - } } }, "@mongodb-js/diagramming": { diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 47511207621..3df34baac72 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -1006,7 +1006,8 @@ describe('Data Modeling tab', function () { 'renamedField', ]) ); - await browser.clickVisible( + await dragDiagramToShowAndClick( + browser, Selectors.DataModelAddNestedFieldBtn('test.testCollection-one', [ 'renamedField', ])