diff --git a/packages/compass-components/src/components/accordion.spec.tsx b/packages/compass-components/src/components/accordion.spec.tsx index 815f66564c1..878bc04d5f8 100644 --- a/packages/compass-components/src/components/accordion.spec.tsx +++ b/packages/compass-components/src/components/accordion.spec.tsx @@ -1,12 +1,7 @@ import React from 'react'; import { expect } from 'chai'; -import { - fireEvent, - render, - screen, - cleanup, -} from '@mongodb-js/testing-library-compass'; +import { userEvent, render, screen } from '@mongodb-js/testing-library-compass'; import { Accordion } from './accordion'; @@ -21,15 +16,26 @@ function renderAccordion( } describe('Accordion Component', function () { - afterEach(cleanup); - it('should open the accordion on click', function () { renderAccordion(); expect(screen.getByTestId('my-test-id')).to.exist; const button = screen.getByText('Accordion Test'); - fireEvent.click(button); + userEvent.click(button); + expect(screen.getByText('Hello World')).to.be.visible; + }); + + it('should close the accordion on click - default open', function () { + renderAccordion({ + defaultOpen: true, + }); + + expect(screen.getByTestId('my-test-id')).to.exist; + const button = screen.getByText('Accordion Test'); expect(screen.getByText('Hello World')).to.be.visible; + userEvent.click(button); + + expect(screen.queryByText('Hello World')).not.to.exist; }); it('should close the accordion after clicking to open then close', function () { @@ -37,9 +43,9 @@ describe('Accordion Component', function () { expect(screen.getByTestId('my-test-id')).to.exist; const button = screen.getByText('Accordion Test'); - fireEvent.click(button); + userEvent.click(button); expect(screen.getByText('Hello World')).to.be.visible; - fireEvent.click(button); + userEvent.click(button); expect(screen.queryByText('Hello World')).to.not.exist; }); diff --git a/packages/compass-components/src/components/accordion.tsx b/packages/compass-components/src/components/accordion.tsx index eb8f894e5c2..a5c7b13187c 100644 --- a/packages/compass-components/src/components/accordion.tsx +++ b/packages/compass-components/src/components/accordion.tsx @@ -50,18 +50,22 @@ const buttonHintStyles = css({ interface AccordionProps extends React.HTMLProps { text: string | React.ReactNode; hintText?: string; + textClassName?: string; open?: boolean; + defaultOpen?: boolean; setOpen?: (newValue: boolean) => void; } function Accordion({ text, hintText, + textClassName, open: _open, setOpen: _setOpen, + defaultOpen = false, ...props }: React.PropsWithChildren): React.ReactElement { const darkMode = useDarkMode(); - const [localOpen, setLocalOpen] = useState(_open ?? false); + const [localOpen, setLocalOpen] = useState(_open ?? defaultOpen); const setOpenRef = useRef(_setOpen); setOpenRef.current = _setOpen; const onOpenChange = useCallback(() => { @@ -80,7 +84,8 @@ function Accordion({ {...props} className={cx( darkMode ? buttonDarkThemeStyles : buttonLightThemeStyles, - buttonStyles + buttonStyles, + textClassName )} id={labelId} type="button" diff --git a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx b/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx index dcc8e8b32ad..a4455820639 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx @@ -17,7 +17,10 @@ import { selectRelationship, } from '../store/diagram'; import dataModel from '../../test/fixtures/data-model-with-relationships.json'; -import type { MongoDBDataModelDescription } from '../services/data-model-storage'; +import type { + MongoDBDataModelDescription, + Relationship, +} from '../services/data-model-storage'; async function comboboxSelectItem( label: string, @@ -67,7 +70,37 @@ describe('DiagramEditorSidePanel', function () { result.plugin.store.dispatch( selectRelationship('204b1fc0-601f-4d62-bba3-38fade71e049') ); - expect(screen.getByText('Edit Relationship')).to.be.visible; + + const name = screen.getByLabelText('Name'); + expect(name).to.be.visible; + expect(name).to.have.value('Airport Country'); + + const localCollectionInput = screen.getByLabelText('Local collection'); + expect(localCollectionInput).to.be.visible; + expect(localCollectionInput).to.have.value('countries'); + + const foreignCollectionInput = screen.getByLabelText('Foreign collection'); + expect(foreignCollectionInput).to.be.visible; + expect(foreignCollectionInput).to.have.value('airports'); + + const localFieldInput = screen.getByLabelText('Local field'); + expect(localFieldInput).to.be.visible; + expect(localFieldInput).to.have.value('name'); + + const foreignFieldInput = screen.getByLabelText('Foreign field'); + expect(foreignFieldInput).to.be.visible; + expect(foreignFieldInput).to.have.value('Country'); + + const localCardinalityInput = screen.getByLabelText('Local cardinality'); + expect(localCardinalityInput).to.be.visible; + expect(localCardinalityInput).to.have.value('1'); + + const foreignCardinalityInput = screen.getByLabelText( + 'Foreign cardinality' + ); + expect(foreignCardinalityInput).to.be.visible; + expect(foreignCardinalityInput).to.have.value('100'); + expect( document.querySelector( '[data-relationship-id="204b1fc0-601f-4d62-bba3-38fade71e049"]' @@ -120,7 +153,7 @@ describe('DiagramEditorSidePanel', function () { userEvent.click( within(relationshipCard!).getByRole('button', { name: 'Edit' }) ); - expect(screen.getByText('Edit Relationship')).to.be.visible; + expect(screen.getByLabelText('Local field')).to.be.visible; // Select new values await comboboxSelectItem('Local collection', 'planes'); @@ -133,7 +166,7 @@ describe('DiagramEditorSidePanel', function () { // model here const modifiedRelationship = selectCurrentModel( getCurrentDiagramFromState(result.plugin.store.getState()).edits - ).relationships.find((r) => { + ).relationships.find((r: Relationship) => { return r.id === '204b1fc0-601f-4d62-bba3-38fade71e049'; }); @@ -148,7 +181,7 @@ describe('DiagramEditorSidePanel', function () { { ns: 'flights.countries', fields: ['iso_code'], - cardinality: 1, + cardinality: 100, }, ]); }); diff --git a/packages/compass-data-modeling/src/components/diagram-editor.spec.tsx b/packages/compass-data-modeling/src/components/diagram-editor.spec.tsx index 9e12dc73a37..4cc93d4c6ce 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.spec.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.spec.tsx @@ -262,8 +262,14 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.deep.equal([ - { name: 'name', type: 'string', depth: 0, glyphs: [] }, - { name: 'age', type: 'int', depth: 0, glyphs: [] }, + { + name: 'name', + type: 'string', + depth: 0, + glyphs: [], + variant: undefined, + }, + { name: 'age', type: 'int', depth: 0, glyphs: [], variant: undefined }, ]); }); @@ -274,9 +280,45 @@ describe('getFieldsFromSchema', function () { age: { bsonType: ['int', 'string'] }, }, }); - expect(result[0]).to.deep.include({ name: 'age', depth: 0, glyphs: [] }); + expect(result[0]).to.deep.include({ + name: 'age', + depth: 0, + glyphs: [], + variant: undefined, + }); await validateMixedType(result[0].type, /int, string/); }); + + it('highlights the correct field', function () { + const result = getFieldsFromSchema( + { + bsonType: 'object', + properties: { + name: { bsonType: 'string' }, + age: { bsonType: 'int' }, + profession: { bsonType: 'string' }, + }, + }, + ['age'] + ); + expect(result).to.deep.equal([ + { + name: 'name', + type: 'string', + depth: 0, + glyphs: [], + variant: undefined, + }, + { name: 'age', type: 'int', depth: 0, glyphs: [], variant: 'preview' }, + { + name: 'profession', + type: 'string', + depth: 0, + glyphs: [], + variant: undefined, + }, + ]); + }); }); describe('nested schema', function () { @@ -300,11 +342,102 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.deep.equal([ - { name: 'person', type: 'object', depth: 0, glyphs: [] }, - { name: 'name', type: 'string', depth: 1, glyphs: [] }, - { name: 'address', type: 'object', depth: 1, glyphs: [] }, - { name: 'street', type: 'string', depth: 2, glyphs: [] }, - { name: 'city', type: 'string', depth: 2, glyphs: [] }, + { + name: 'person', + type: 'object', + depth: 0, + glyphs: [], + variant: undefined, + }, + { + name: 'name', + type: 'string', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'address', + type: 'object', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'street', + type: 'string', + depth: 2, + glyphs: [], + variant: undefined, + }, + { + name: 'city', + type: 'string', + depth: 2, + glyphs: [], + variant: undefined, + }, + ]); + }); + + it('highlights a field for a nested schema', function () { + const result = getFieldsFromSchema( + { + bsonType: 'object', + properties: { + person: { + bsonType: 'object', + properties: { + name: { bsonType: 'string' }, + address: { + bsonType: 'object', + properties: { + street: { bsonType: 'string' }, + city: { bsonType: 'string' }, + }, + }, + }, + }, + }, + }, + ['person', 'address', 'street'] + ); + expect(result).to.deep.equal([ + { + name: 'person', + type: 'object', + depth: 0, + glyphs: [], + variant: undefined, + }, + { + name: 'name', + type: 'string', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'address', + type: 'object', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'street', + type: 'string', + depth: 2, + glyphs: [], + variant: 'preview', + }, + { + name: 'city', + type: 'string', + depth: 2, + glyphs: [], + variant: undefined, + }, ]); }); @@ -319,7 +452,7 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.deep.equal([ - { name: 'tags', type: '[]', depth: 0, glyphs: [] }, + { name: 'tags', type: '[]', depth: 0, glyphs: [], variant: undefined }, ]); }); @@ -340,9 +473,21 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.deep.equal([ - { name: 'todos', type: '[]', depth: 0, glyphs: [] }, - { name: 'title', type: 'string', depth: 1, glyphs: [] }, - { name: 'completed', type: 'boolean', depth: 1, glyphs: [] }, + { name: 'todos', type: '[]', depth: 0, glyphs: [], variant: undefined }, + { + name: 'title', + type: 'string', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'completed', + type: 'boolean', + depth: 1, + glyphs: [], + variant: undefined, + }, ]); }); @@ -365,19 +510,26 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.have.lengthOf(3); - expect(result[0]).to.deep.include({ name: 'name', depth: 0, glyphs: [] }); + expect(result[0]).to.deep.include({ + name: 'name', + depth: 0, + glyphs: [], + variant: undefined, + }); await validateMixedType(result[0].type, /string, object/); expect(result[1]).to.deep.equal({ name: 'first', type: 'string', depth: 1, glyphs: [], + variant: undefined, }); expect(result[2]).to.deep.equal({ name: 'last', type: 'string', depth: 1, glyphs: [], + variant: undefined, }); }); @@ -403,9 +555,21 @@ describe('getFieldsFromSchema', function () { }, }); expect(result).to.deep.equal([ - { name: 'todos', type: '[]', depth: 0, glyphs: [] }, - { name: 'title', type: 'string', depth: 1, glyphs: [] }, - { name: 'completed', type: 'boolean', depth: 1, glyphs: [] }, + { name: 'todos', type: '[]', depth: 0, glyphs: [], variant: undefined }, + { + name: 'title', + type: 'string', + depth: 1, + glyphs: [], + variant: undefined, + }, + { + name: 'completed', + type: 'boolean', + depth: 1, + glyphs: [], + variant: undefined, + }, ]); }); }); diff --git a/packages/compass-data-modeling/src/components/diagram-editor.tsx b/packages/compass-data-modeling/src/components/diagram-editor.tsx index a3f1b43652e..636b0c570a2 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.tsx @@ -17,6 +17,7 @@ import { selectCollection, selectRelationship, selectBackground, + type DiagramState, } from '../store/diagram'; import { Banner, @@ -37,7 +38,7 @@ import { useDiagram, applyLayout, } from '@mongodb-js/diagramming'; -import type { StaticModel } from '../services/data-model-storage'; +import type { Relationship, StaticModel } from '../services/data-model-storage'; import DiagramEditorToolbar from './diagram-editor-toolbar'; import ExportDiagramModal from './export-diagram-modal'; import { useLogger } from '@mongodb-js/compass-logging/provider'; @@ -124,6 +125,7 @@ function getFieldTypeDisplay(bsonTypes: string[]) { export const getFieldsFromSchema = ( jsonSchema: MongoDBJSONSchema, + highlightedFields: string[] = [], depth = 0 ): NodeProps['fields'] => { if (!jsonSchema || !jsonSchema.properties) { @@ -135,7 +137,7 @@ export const getFieldsFromSchema = ( // types are either direct, or from anyof // children are either direct (properties), from anyOf, items or items.anyOf const types: (string | string[])[] = []; - const children = []; + const children: (MongoDBJSONSchema | MongoDBJSONSchema[])[] = []; if (field.bsonType) types.push(field.bsonType); if (field.properties) children.push(field); if (field.items) @@ -155,6 +157,11 @@ export const getFieldsFromSchema = ( type: getFieldTypeDisplay(types.flat()), depth: depth, glyphs: types.length === 1 && types[0] === 'objectId' ? ['key'] : [], + variant: + highlightedFields.length && + highlightedFields[highlightedFields.length - 1] === name + ? 'preview' + : undefined, }); if (children.length > 0) { @@ -162,7 +169,13 @@ export const getFieldsFromSchema = ( ...fields, ...children .flat() - .flatMap((child) => getFieldsFromSchema(child, depth + 1)), + .flatMap((child) => + getFieldsFromSchema( + child, + name === highlightedFields[0] ? highlightedFields.slice(1) : [], + depth + 1 + ) + ), ]; } } @@ -170,6 +183,25 @@ export const getFieldsFromSchema = ( return fields; }; +const getSelectedFields = ( + selectedItems: SelectedItems | null, + relationships?: Relationship[] +) => { + if (!selectedItems || selectedItems.type !== 'relationship') return {}; + const { id } = selectedItems; + const relationship = relationships?.find((rel) => rel.id === id); + if ( + !relationship || + !relationship.relationship[0].ns || + !relationship.relationship[1].ns + ) + return {}; + return { + [relationship.relationship[0].ns]: relationship.relationship[0].fields, + [relationship.relationship[1].ns]: relationship.relationship[1].fields, + }; +}; + const modelPreviewContainerStyles = css({ display: 'grid', gridTemplateColumns: '100%', @@ -182,6 +214,8 @@ const modelPreviewStyles = css({ minHeight: 0, }); +type SelectedItems = NonNullable['selectedItems']; + const DiagramEditor: React.FunctionComponent<{ diagramLabel: string; step: DataModelingState['step']; @@ -194,7 +228,7 @@ const DiagramEditor: React.FunctionComponent<{ onCollectionSelect: (namespace: string) => void; onRelationshipSelect: (rId: string) => void; onDiagramBackgroundClicked: () => void; - selectedItems: { type: 'relationship' | 'collection'; id: string } | null; + selectedItems: SelectedItems; }> = ({ diagramLabel, step, @@ -243,6 +277,10 @@ const DiagramEditor: React.FunctionComponent<{ }, [model?.relationships, selectedItems]); const nodes = useMemo(() => { + const selectedFields = getSelectedFields( + selectedItems, + model?.relationships + ); return (model?.collections ?? []).map( (coll): NodeProps => ({ id: coll.ns, @@ -252,14 +290,18 @@ const DiagramEditor: React.FunctionComponent<{ y: coll.displayPosition[1], }, title: toNS(coll.ns).collection, - fields: getFieldsFromSchema(coll.jsonSchema), + fields: getFieldsFromSchema( + coll.jsonSchema, + selectedFields[coll.ns] || undefined, + 0 + ), selected: !!selectedItems && selectedItems.type === 'collection' && selectedItems.id === coll.ns, }) ); - }, [model?.collections, selectedItems]); + }, [model?.collections, model?.relationships, selectedItems]); const applyInitialLayout = useCallback(async () => { try { diff --git a/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx b/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx index 3ed64961f80..2651f8c7f79 100644 --- a/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx +++ b/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx @@ -1,14 +1,25 @@ -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { connect } from 'react-redux'; import type { DataModelingState } from '../store/reducer'; import { Button, Combobox, FormFieldContainer, - H3, ComboboxOption, Select, Option, + Accordion, + TextInput, + spacing, + css, + Icon, + palette, } from '@mongodb-js/compass-components'; import { deleteRelationship, @@ -30,14 +41,29 @@ type RelationshipDrawerContentProps = { }; type RelationshipFormFields = { + name: string; localCollection: string; localField: string; + localCardinality: string; foreignCollection: string; foreignField: string; - localCardinality: string; foreignCardinality: string; }; +const formFieldContainerStyles = css({ + marginBottom: spacing[400], + marginTop: spacing[400], +}); + +const containerStyles = css({ + padding: spacing[400], +}); + +const accordionTitleStyles = css({ + fontSize: spacing[300], + color: palette.gray.dark1, +}); + const FIELD_DIVIDER = '~~##$$##~~'; function useRelationshipFormFields( @@ -57,10 +83,14 @@ function useRelationshipFormFields( const foreignCollection = foreign.ns ?? ''; const foreignField = foreign.fields?.join(FIELD_DIVIDER) ?? ''; const foreignCardinality = String(foreign.cardinality); + const name = relationship.name ?? ''; const onFieldChange = useCallback( (key: keyof RelationshipFormFields, value: string) => { const newRelationship = cloneDeep(relationship); switch (key) { + case 'name': + newRelationship.name = value; + break; case 'localCollection': newRelationship.relationship[0].ns = value; newRelationship.relationship[0].fields = null; @@ -87,6 +117,7 @@ function useRelationshipFormFields( [relationship] ); return { + name, localCollection, localField, localCardinality, @@ -97,7 +128,26 @@ function useRelationshipFormFields( }; } -const CARDINALITY_OPTIONS = [1, 10, 100, 1000]; +const cardinalityTagStyle = css({ + color: palette.gray.base, + fontWeight: 'bold', +}); + +const CardinalityLabel: React.FunctionComponent<{ + value: number; + tag: string; +}> = ({ value, tag }) => ( + <> + {tag} {value} + +); + +const CARDINALITY_OPTIONS = [ + { tag: 'One', value: 1 }, + { tag: 'Many', value: 10 }, + { tag: 'Many', value: 100 }, + { tag: 'Many', value: 1000 }, +]; const RelationshipDrawerContent: React.FunctionComponent< RelationshipDrawerContentProps @@ -112,6 +162,13 @@ const RelationshipDrawerContent: React.FunctionComponent< return Object.keys(fields); }, [fields]); + const [relationshipName, setRelationshipName] = useState( + relationship.name || '' + ); + useEffect(() => { + setRelationshipName(relationship.name || ''); + }, [relationship.name]); + const { localCollection, localField, @@ -131,156 +188,188 @@ const RelationshipDrawerContent: React.FunctionComponent< }, [fields, foreignCollection]); return ( -
-

Edit Relationship

+
+ + + ) => { + setRelationshipName(e.target.value); + }} + onBlur={(e: React.FocusEvent) => { + onFieldChange('name', e.target.value); + }} + /> + - - { - if (val) { - onFieldChange('localCollection', val); - } - }} - multiselect={false} - clearable={false} - > - {collections.map((ns) => { - return ( - - ); - })} - - + + + + - - { - if (val) { - onFieldChange('localField', val); - } - }} - multiselect={false} - clearable={false} - > - {localFieldOptions.map((field) => { - return ( - - ); - })} - - + + + { + if (val) { + onFieldChange('localCollection', val); + } + }} + multiselect={false} + clearable={false} + > + {collections.map((ns) => { + return ( + + ); + })} + + - - { - if (val) { - onFieldChange('foreignCollection', val); - } - }} - multiselect={false} - clearable={false} - > - {collections.map((ns) => { - return ( - - ); - })} - - + + { + if (val) { + onFieldChange('localField', val); + } + }} + multiselect={false} + clearable={false} + > + {localFieldOptions.map((field) => { + return ( + + ); + })} + + - - { - if (val) { - onFieldChange('foreignField', val); - } - }} - multiselect={false} - clearable={false} - > - {foreignFieldOptions.map((field) => { - return ( - - ); - })} - - + + { + if (val) { + onFieldChange('foreignCollection', val); + } + }} + multiselect={false} + clearable={false} + > + {collections.map((ns) => { + return ( + + ); + })} + + - - - + + { + if (val) { + onFieldChange('foreignField', val); + } + }} + multiselect={false} + clearable={false} + > + {foreignFieldOptions.map((field) => { + return ( + + ); + })} + + - - - + + + - - - + + + +
); }; diff --git a/packages/compass-data-modeling/src/services/data-model-storage.ts b/packages/compass-data-modeling/src/services/data-model-storage.ts index 4924fe957f8..333581dbea0 100644 --- a/packages/compass-data-modeling/src/services/data-model-storage.ts +++ b/packages/compass-data-modeling/src/services/data-model-storage.ts @@ -11,6 +11,7 @@ export type RelationshipSide = z.output; export const RelationshipSchema = z.object({ id: z.string().uuid(), + name: z.string().optional(), relationship: z.tuple([RelationshipSideSchema, RelationshipSideSchema]), isInferred: z.boolean(), }); diff --git a/packages/compass-data-modeling/src/store/diagram.ts b/packages/compass-data-modeling/src/store/diagram.ts index ce940c709ff..5b9033a66a7 100644 --- a/packages/compass-data-modeling/src/store/diagram.ts +++ b/packages/compass-data-modeling/src/store/diagram.ts @@ -306,10 +306,13 @@ export function selectCollection(namespace: string): CollectionSelectedAction { export function selectRelationship( relationshipId: string -): RelationSelectedAction { - return { - type: DiagramActionTypes.RELATIONSHIP_SELECTED, - relationshipId, +): DataModelingThunkAction { + return (dispatch, getState, { track }) => { + dispatch({ + type: DiagramActionTypes.RELATIONSHIP_SELECTED, + relationshipId, + }); + track('Data Modeling Relationship Form Opened', {}); }; } @@ -322,8 +325,11 @@ export function selectBackground(): DiagramBackgroundSelectedAction { export function createNewRelationship( namespace: string ): DataModelingThunkAction { - return (dispatch) => { + return (dispatch, getState, { track }) => { const relationshipId = new UUID().toString(); + const currentNumberOfRelationships = getCurrentNumberOfRelationships( + getState() + ); dispatch( applyEdit({ type: 'AddRelationship', @@ -341,6 +347,9 @@ export function createNewRelationship( type: DiagramActionTypes.RELATIONSHIP_SELECTED, relationshipId, }); + track('Data Modeling Relationship Added', { + num_relationships: currentNumberOfRelationships + 1, + }); }; } @@ -510,8 +519,23 @@ export function updateRelationship( }); } -export function deleteRelationship(relationshipId: string) { - return applyEdit({ type: 'RemoveRelationship', relationshipId }); +export function deleteRelationship( + relationshipId: string +): DataModelingThunkAction { + return (dispatch, getState, { track }) => { + const currentNumberOfRelationships = getCurrentNumberOfRelationships( + getState() + ); + dispatch( + applyEdit({ + type: 'RemoveRelationship', + relationshipId, + }) + ); + track('Data Modeling Relationship Deleted', { + num_relationships: currentNumberOfRelationships - 1, + }); + }; } export function closeDrawer(): DrawerClosedAction { @@ -669,3 +693,8 @@ export function getRelationshipForCurrentModel( (r) => r.id === relationshipId ); } + +function getCurrentNumberOfRelationships(state: DataModelingState): number { + return selectCurrentModel(getCurrentDiagramFromState(state).edits) + .relationships.length; +} diff --git a/packages/compass-data-modeling/test/fixtures/data-model-with-relationships.json b/packages/compass-data-modeling/test/fixtures/data-model-with-relationships.json index 5b6a15ad07d..650866e8da5 100644 --- a/packages/compass-data-modeling/test/fixtures/data-model-with-relationships.json +++ b/packages/compass-data-modeling/test/fixtures/data-model-with-relationships.json @@ -261,6 +261,7 @@ "type": "AddRelationship", "relationship": { "id": "204b1fc0-601f-4d62-bba3-38fade71e049", + "name": "Airport Country", "relationship": [ { "ns": "flights.countries", @@ -269,7 +270,7 @@ }, { "ns": "flights.airports", - "cardinality": 1, + "cardinality": 100, "fields": ["Country"] } ], diff --git a/packages/compass-telemetry/src/telemetry-events.ts b/packages/compass-telemetry/src/telemetry-events.ts index 3ced818db18..feef7f64e22 100644 --- a/packages/compass-telemetry/src/telemetry-events.ts +++ b/packages/compass-telemetry/src/telemetry-events.ts @@ -2901,6 +2901,40 @@ type DataModelingDiagramExported = CommonEvent<{ }; }>; +/** + * This event is fired when user adds a new relationship to a data modeling diagram. + * + * @category Data Modeling + */ +type DataModelingDiagramRelationshipAdded = CommonEvent<{ + name: 'Data Modeling Relationship Added'; + payload: { + num_relationships: number; + }; +}>; + +/** + * This event is fired when user edits a relationship in a data modeling diagram. + * + * @category Data Modeling + */ +type DataModelingDiagramRelationshipEdited = CommonEvent<{ + name: 'Data Modeling Relationship Form Opened'; + payload: Record; +}>; + +/** + * This event is fired when user deletes a relationship from a data modeling diagram. + * + * @category Data Modeling + */ +type DataModelingDiagramRelationshipDeleted = CommonEvent<{ + name: 'Data Modeling Relationship Deleted'; + payload: { + num_relationships: number; + }; +}>; + export type TelemetryEvent = | AggregationCanceledEvent | AggregationCopiedEvent @@ -3048,4 +3082,7 @@ export type TelemetryEvent = | CreateIndexIndexSuggestionsCopied | CreateIndexStrategiesDocumentationClicked | UUIDEncounteredEvent - | DataModelingDiagramExported; + | DataModelingDiagramExported + | DataModelingDiagramRelationshipAdded + | DataModelingDiagramRelationshipEdited + | DataModelingDiagramRelationshipDeleted;