diff --git a/packages/compass-components/src/components/accordion.tsx b/packages/compass-components/src/components/accordion.tsx index a5c7b13187c..dce0b3fa34a 100644 --- a/packages/compass-components/src/components/accordion.tsx +++ b/packages/compass-components/src/components/accordion.tsx @@ -51,6 +51,7 @@ interface AccordionProps extends React.HTMLProps { text: string | React.ReactNode; hintText?: string; textClassName?: string; + buttonTextClassName?: string; open?: boolean; defaultOpen?: boolean; setOpen?: (newValue: boolean) => void; @@ -59,6 +60,7 @@ function Accordion({ text, hintText, textClassName, + buttonTextClassName, open: _open, setOpen: _setOpen, defaultOpen = false, @@ -97,7 +99,7 @@ function Accordion({ -
+
{text} {hintText && ( {hintText} diff --git a/packages/compass-data-modeling/src/components/collection-drawer-content.tsx b/packages/compass-data-modeling/src/components/collection-drawer-content.tsx deleted file mode 100644 index 60bc33e9875..00000000000 --- a/packages/compass-data-modeling/src/components/collection-drawer-content.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import type { Relationship } from '../services/data-model-storage'; -import { Button, H3 } from '@mongodb-js/compass-components'; -import { - createNewRelationship, - deleteRelationship, - getCurrentDiagramFromState, - selectCurrentModel, - selectRelationship, -} from '../store/diagram'; -import type { DataModelingState } from '../store/reducer'; - -type CollectionDrawerContentProps = { - namespace: string; - relationships: Relationship[]; - shouldShowRelationshipEditingForm?: boolean; - onCreateNewRelationshipClick: (namespace: string) => void; - onEditRelationshipClick: (rId: string) => void; - onDeleteRelationshipClick: (rId: string) => void; -}; - -const CollectionDrawerContent: React.FunctionComponent< - CollectionDrawerContentProps -> = ({ - namespace, - relationships, - onCreateNewRelationshipClick, - onEditRelationshipClick, - onDeleteRelationshipClick, -}) => { - return ( - <> -

{namespace}

-
    - {relationships.map((r) => { - return ( -
  • - {r.relationship[0].fields?.join('.')} ->  - {r.relationship[1].fields?.join('.')} - - -
  • - ); - })} -
- - - ); -}; - -export default connect( - (state: DataModelingState, ownProps: { namespace: string }) => { - return { - relationships: selectCurrentModel( - getCurrentDiagramFromState(state).edits - ).relationships.filter((r) => { - const [local, foreign] = r.relationship; - return ( - local.ns === ownProps.namespace || foreign.ns === ownProps.namespace - ); - }), - }; - }, - { - onCreateNewRelationshipClick: createNewRelationship, - onEditRelationshipClick: selectRelationship, - onDeleteRelationshipClick: deleteRelationship, - } -)(CollectionDrawerContent); diff --git a/packages/compass-data-modeling/src/components/data-modeling.tsx b/packages/compass-data-modeling/src/components/data-modeling.tsx index a6eac583895..6db1f08d592 100644 --- a/packages/compass-data-modeling/src/components/data-modeling.tsx +++ b/packages/compass-data-modeling/src/components/data-modeling.tsx @@ -5,7 +5,7 @@ import SavedDiagramsList from './saved-diagrams-list'; import NewDiagramFormModal from './new-diagram-form'; import type { DataModelingState } from '../store/reducer'; import { DiagramProvider } from '@mongodb-js/diagramming'; -import DiagramEditorSidePanel from './diagram-editor-side-panel'; +import DiagramEditorSidePanel from './drawer/diagram-editor-side-panel'; type DataModelingProps = { showList: boolean; diff --git a/packages/compass-data-modeling/src/components/diagram-editor.tsx b/packages/compass-data-modeling/src/components/diagram-editor.tsx index 39e8339b97d..df20f701a9d 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.tsx @@ -31,7 +31,7 @@ import { import type { StaticModel } from '../services/data-model-storage'; import DiagramEditorToolbar from './diagram-editor-toolbar'; import ExportDiagramModal from './export-diagram-modal'; -import { DATA_MODELING_DRAWER_ID } from './diagram-editor-side-panel'; +import { DATA_MODELING_DRAWER_ID } from './drawer/diagram-editor-side-panel'; import { collectionToDiagramNode, getSelectedFields, diff --git a/packages/compass-data-modeling/src/components/drawer/collection-drawer-content.tsx b/packages/compass-data-modeling/src/components/drawer/collection-drawer-content.tsx new file mode 100644 index 00000000000..e72e4ad2f8c --- /dev/null +++ b/packages/compass-data-modeling/src/components/drawer/collection-drawer-content.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import type { Relationship } from '../../services/data-model-storage'; +import { + Badge, + Button, + IconButton, + css, + FormFieldContainer, + palette, + spacing, + TextInput, + Icon, +} from '@mongodb-js/compass-components'; +import { + createNewRelationship, + deleteRelationship, + getCurrentDiagramFromState, + selectCurrentModel, + selectRelationship, +} from '../../store/diagram'; +import type { DataModelingState } from '../../store/reducer'; +import { getRelationshipName } from '../../utils'; +import DMDrawerSection from './dm-drawer-section'; + +type CollectionDrawerContentProps = { + namespace: string; + relationships: Relationship[]; + onCreateNewRelationshipClick: (namespace: string) => void; + onEditRelationshipClick: (rId: string) => void; + onDeleteRelationshipClick: (rId: string) => void; +}; + +const formFieldContainerStyles = css({ + marginBottom: spacing[400], + marginTop: spacing[400], +}); + +const titleBtnStyles = css({ + marginLeft: 'auto', +}); + +const emptyRelationshipMessageStyles = css({ + color: palette.gray.dark1, +}); + +const relationshipsListStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[200], +}); + +const relationshipItemStyles = css({ + display: 'flex', + alignItems: 'center', +}); + +const relationshipNameStyles = css({ + flexGrow: 1, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + minWidth: 0, + paddingRight: spacing[200], +}); + +const relationshipContentStyles = css({ + marginTop: spacing[400], +}); + +const CollectionDrawerContent: React.FunctionComponent< + CollectionDrawerContentProps +> = ({ + namespace, + relationships, + onCreateNewRelationshipClick, + onEditRelationshipClick, + onDeleteRelationshipClick, +}) => { + return ( + <> + + + + + + + + RELATIONSHIPS  + {relationships.length} + + + } + > +
+ {!relationships.length ? ( +
+ This collection does not have any relationships yet. +
+ ) : ( +
    + {relationships.map((r) => { + return ( +
  • + + {getRelationshipName(r)} + + { + onEditRelationshipClick(r.id); + }} + > + + + { + onDeleteRelationshipClick(r.id); + }} + > + + +
  • + ); + })} +
+ )} +
+
+ + ); +}; + +export default connect( + (state: DataModelingState, ownProps: { namespace: string }) => { + return { + relationships: selectCurrentModel( + getCurrentDiagramFromState(state).edits + ).relationships.filter((r) => { + const [local, foreign] = r.relationship; + return ( + local.ns === ownProps.namespace || foreign.ns === ownProps.namespace + ); + }), + }; + }, + { + onCreateNewRelationshipClick: createNewRelationship, + onEditRelationshipClick: selectRelationship, + onDeleteRelationshipClick: deleteRelationship, + } +)(CollectionDrawerContent); diff --git a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx similarity index 78% rename from packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx rename to packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx index ec1f493989b..9faf342a756 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.spec.tsx +++ b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx @@ -7,7 +7,7 @@ import { userEvent, within, } from '@mongodb-js/testing-library-compass'; -import { DataModelingWorkspaceTab } from '../index'; +import { DataModelingWorkspaceTab } from '../../index'; import DiagramEditorSidePanel from './diagram-editor-side-panel'; import { getCurrentDiagramFromState, @@ -15,12 +15,12 @@ import { selectCollection, selectCurrentModel, selectRelationship, -} from '../store/diagram'; -import dataModel from '../../test/fixtures/data-model-with-relationships.json'; +} from '../../store/diagram'; +import dataModel from '../../../test/fixtures/data-model-with-relationships.json'; import type { MongoDBDataModelDescription, Relationship, -} from '../services/data-model-storage'; +} from '../../services/data-model-storage'; import { DrawerAnchor } from '@mongodb-js/compass-components'; async function comboboxSelectItem( @@ -74,7 +74,9 @@ describe('DiagramEditorSidePanel', function () { result.plugin.store.dispatch(selectCollection('flights.airlines')); await waitFor(() => { - expect(screen.getByText('flights.airlines')).to.be.visible; + const nameInput = screen.getByLabelText('Name'); + expect(nameInput).to.be.visible; + expect(nameInput).to.have.value('flights.airlines'); }); }); @@ -129,14 +131,15 @@ describe('DiagramEditorSidePanel', function () { result.plugin.store.dispatch(selectCollection('flights.airlines')); await waitFor(() => { - expect(screen.getByText('flights.airlines')).to.be.visible; + expect(screen.getByLabelText('Name')).to.have.value('flights.airlines'); }); result.plugin.store.dispatch( selectCollection('flights.airports_coordinates_for_schema') ); - expect(screen.getByText('flights.airports_coordinates_for_schema')).to.be - .visible; + expect(screen.getByLabelText('Name')).to.have.value( + 'flights.airports_coordinates_for_schema' + ); result.plugin.store.dispatch( selectRelationship('204b1fc0-601f-4d62-bba3-38fade71e049') @@ -157,7 +160,7 @@ describe('DiagramEditorSidePanel', function () { ).to.be.visible; result.plugin.store.dispatch(selectCollection('flights.planes')); - expect(screen.getByText('flights.planes')).to.be.visible; + expect(screen.getByLabelText('Name')).to.have.value('flights.planes'); }); it('should open and edit relationship starting from collection', async function () { @@ -165,15 +168,16 @@ describe('DiagramEditorSidePanel', function () { result.plugin.store.dispatch(selectCollection('flights.countries')); await waitFor(() => { - expect(screen.getByText('flights.countries')).to.be.visible; + expect(screen.getByLabelText('Name')).to.have.value('flights.countries'); }); // Open relationshipt editing form - const relationshipCard = document.querySelector( - '[data-relationship-id="204b1fc0-601f-4d62-bba3-38fade71e049"]' - ); + const relationshipItem = screen.getByText('Airport Country').closest('li'); + expect(relationshipItem).to.be.visible; userEvent.click( - within(relationshipCard!).getByRole('button', { name: 'Edit' }) + within(relationshipItem!).getByRole('button', { + name: 'Edit relationship', + }) ); expect(screen.getByLabelText('Local field')).to.be.visible; @@ -207,4 +211,28 @@ describe('DiagramEditorSidePanel', function () { }, ]); }); + + it('should delete a relationship from collection', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.countries')); + + await waitFor(() => { + expect(screen.getByLabelText('Name')).to.have.value('flights.countries'); + }); + + // Find the relationhip item + const relationshipItem = screen.getByText('Airport Country').closest('li'); + expect(relationshipItem).to.be.visible; + + // Delete relationship + userEvent.click( + within(relationshipItem!).getByRole('button', { + name: 'Delete relationship', + }) + ); + + await waitFor(() => { + expect(screen.queryByText('Airport Country')).not.to.exist; + }); + }); }); diff --git a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.tsx b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.tsx similarity index 90% rename from packages/compass-data-modeling/src/components/diagram-editor-side-panel.tsx rename to packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.tsx index a01b194a30c..56096066b20 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor-side-panel.tsx +++ b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import type { DataModelingState } from '../store/reducer'; +import type { DataModelingState } from '../../store/reducer'; import { DrawerSection } from '@mongodb-js/compass-components'; import CollectionDrawerContent from './collection-drawer-content'; import RelationshipDrawerContent from './relationship-drawer-content'; -import { closeDrawer } from '../store/diagram'; +import { closeDrawer } from '../../store/diagram'; export const DATA_MODELING_DRAWER_ID = 'data-modeling-drawer'; @@ -13,7 +13,7 @@ type DiagramEditorSidePanelProps = { onClose: () => void; }; -function DiagmramEditorSidePanel({ +function DiagramEditorSidePanel({ selectedItems, }: DiagramEditorSidePanelProps) { if (!selectedItems) { @@ -64,4 +64,4 @@ export default connect( { onClose: closeDrawer, } -)(DiagmramEditorSidePanel); +)(DiagramEditorSidePanel); diff --git a/packages/compass-data-modeling/src/components/drawer/dm-drawer-section.tsx b/packages/compass-data-modeling/src/components/drawer/dm-drawer-section.tsx new file mode 100644 index 00000000000..86c2ada1672 --- /dev/null +++ b/packages/compass-data-modeling/src/components/drawer/dm-drawer-section.tsx @@ -0,0 +1,53 @@ +import { + Accordion, + css, + palette, + spacing, + cx, + useDarkMode, +} from '@mongodb-js/compass-components'; +import React from 'react'; + +const containerStyles = css({ + '&:first-child': { + marginTop: `-${spacing[400]}px`, + }, + borderBottom: `1px solid ${palette.gray.light2}`, + marginLeft: `-${spacing[400]}px`, + marginRight: `-${spacing[400]}px`, + padding: spacing[400], +}); + +const darkModeContainerStyles = css({ + borderBottom: `1px solid ${palette.gray.dark2}`, +}); + +const accordionTitleStyles = css({ + fontSize: spacing[300], + width: '100%', +}); + +const buttonStyles = css({ + width: '100%', + display: 'flex', +}); + +const DMDrawerSection: React.FC<{ + label: React.ReactNode; +}> = ({ label, children }) => { + const darkMode = useDarkMode(); + return ( +
+ + {children} + +
+ ); +}; + +export default DMDrawerSection; diff --git a/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx b/packages/compass-data-modeling/src/components/drawer/relationship-drawer-content.tsx similarity index 92% rename from packages/compass-data-modeling/src/components/relationship-drawer-content.tsx rename to packages/compass-data-modeling/src/components/drawer/relationship-drawer-content.tsx index 845c78ec49d..ea44b48e0b2 100644 --- a/packages/compass-data-modeling/src/components/relationship-drawer-content.tsx +++ b/packages/compass-data-modeling/src/components/drawer/relationship-drawer-content.tsx @@ -6,7 +6,7 @@ import React, { useState, } from 'react'; import { connect } from 'react-redux'; -import type { DataModelingState } from '../store/reducer'; +import type { DataModelingState } from '../../store/reducer'; import { Button, Combobox, @@ -14,7 +14,6 @@ import { ComboboxOption, Select, Option, - Accordion, TextInput, spacing, css, @@ -27,10 +26,11 @@ import { getRelationshipForCurrentModel, selectFieldsForCurrentModel, updateRelationship, -} from '../store/diagram'; +} from '../../store/diagram'; import toNS from 'mongodb-ns'; -import type { Relationship } from '../services/data-model-storage'; +import type { Relationship } from '../../services/data-model-storage'; import { cloneDeep } from 'lodash'; +import DMDrawerSection from './dm-drawer-section'; type RelationshipDrawerContentProps = { relationshipId: string; @@ -55,9 +55,8 @@ const formFieldContainerStyles = css({ marginTop: spacing[400], }); -const accordionTitleStyles = css({ - fontSize: spacing[300], - color: palette.gray.dark1, +const titleBtnStyles = css({ + marginLeft: 'auto', }); const FIELD_DIVIDER = '~~##$$##~~'; @@ -185,10 +184,23 @@ const RelationshipDrawerContent: React.FunctionComponent< return (
- + RELATIONSHIP + + + } > + - - - - - - + - +
); }; diff --git a/packages/compass-data-modeling/src/utils.ts b/packages/compass-data-modeling/src/utils.ts new file mode 100644 index 00000000000..9ce7e8820b9 --- /dev/null +++ b/packages/compass-data-modeling/src/utils.ts @@ -0,0 +1,12 @@ +import toNS from 'mongodb-ns'; +import type { Relationship } from './services/data-model-storage'; + +export function getRelationshipName({ + relationship, + name, +}: Relationship): string { + if (name) return name; + const coll1 = relationship[0].ns ? toNS(relationship[0].ns).collection : ''; + const coll2 = relationship[1].ns ? toNS(relationship[1].ns).collection : ''; + return [coll1, coll2].join(` \u2192 `).trim(); +} diff --git a/packages/compass-data-modeling/test/fixtures/flights-model.json b/packages/compass-data-modeling/test/fixtures/flights-model.json index 13fc64f3de8..d62e958af24 100644 --- a/packages/compass-data-modeling/test/fixtures/flights-model.json +++ b/packages/compass-data-modeling/test/fixtures/flights-model.json @@ -241,6 +241,7 @@ }, { "id": "204b1fc0-601f-4d62-bba3-38fade71e049", + "name": "Airport Country", "relationship": [ { "ns": "flights.countries",