Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState } from 'react';
import { render, screen, waitFor } from '@mongodb-js/testing-library-compass';
import {
DrawerContentProvider,
DrawerSection,
DrawerAnchor,
} from './drawer-portal';
import { expect } from 'chai';

describe('DrawerSection', function () {
it('renders DrawerSection in the portal and updates the content when it updates', async function () {
let setCount;

function TestDrawer() {
const [count, _setCount] = useState(0);
setCount = _setCount;
return (
<DrawerContentProvider>
<DrawerAnchor>
<DrawerSection
id="test-section"
label="Test section"
title={`Test section: ${count}`}
glyph="Trash"
autoOpen
>
This is a test section and the count is {count}
</DrawerSection>
</DrawerAnchor>
</DrawerContentProvider>
);
}

render(<TestDrawer></TestDrawer>);

await waitFor(() => {
expect(screen.getByText('Test section: 0')).to.be.visible;
expect(screen.getByText('This is a test section and the count is 0')).to
.be.visible;
});

setCount(42);

await waitFor(() => {
expect(screen.getByText('Test section: 42')).to.be.visible;
expect(screen.getByText('This is a test section and the count is 42')).to
.be.visible;
});
});
});
14 changes: 14 additions & 0 deletions packages/compass-components/src/components/drawer-portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ const drawerLayoutFixesStyles = css({
'& > div:nth-child(2)': {
marginTop: -1, // hiding the top border as we already have one in the place where the Anchor is currently rendered
},

// We're stretching the title container to all available width so that we can
// layout the controls there better. Doing our best to target the section
// title here, leafygreen really doesn't give us anything else to try.
//
// TODO(ticket): This is obviously a horrible selector and we should make sure
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hoping to get a ticket number from this Slack thread

// that LG team provides a better one for us to achieve this behavior when
// we're removing the vendored version of the drawer
'& > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child > div:first-child > div:first-child':
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So a slightly less hacky version of this that I had initially was div:has(>strong), but our version of jsdom can't handle it and I didn't want to include version bump in this PR, but I'll revisit this after

{
flex: 'none',
width: 'calc(100% - 28px)', // disallow going over the title size (100 - close button width)
overflow: 'hidden',
},
});

const emptyDrawerLayoutFixesStyles = css({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ export const DrawerToolbarLayoutContainer = forwardRef<
) => {
const { openDrawer, closeDrawer, getActiveDrawerContent, isDrawerOpen } =
useDrawerToolbarContext();
const { id, title, content } = getActiveDrawerContent() || {};
const { id } = getActiveDrawerContent() || {};
const lgIds = getLgIds(dataLgId);
const hasData = toolbarData && toolbarData.length > 0;
const { title, content } =
toolbarData.find((data) => {
return data.id === id;
}) ?? {};
Comment on lines +42 to +48
Copy link
Collaborator Author

@gribnoysup gribnoysup Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is like a super minimal version of the fix that LG team did in this patch, mapping their changes on top of our vendored package was tricky due to naming (and seems like code was generally shifted around more there), but we can be sure that when this is not vendored anymore, this will continue working

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I promised a test for this, let me add that

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 1538798


const handleOnClose = (event: React.MouseEvent<HTMLButtonElement>) => {
onClose?.(event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ describe('DiagramEditorSidePanel', function () {
result.plugin.store.dispatch(selectCollection('flights.airlines'));

await waitFor(() => {
const nameInput = screen.getByLabelText('Name');
expect(nameInput).to.be.visible;
expect(nameInput).to.have.value('flights.airlines');
expect(screen.getByTitle('flights.airlines')).to.exist;
});

const nameInput = screen.getByLabelText('Name');
expect(nameInput).to.be.visible;
expect(nameInput).to.have.value('flights.airlines');

userEvent.click(screen.getByRole('textbox', { name: 'Notes' }));
userEvent.type(
screen.getByRole('textbox', { name: 'Notes' }),
Expand All @@ -104,8 +106,8 @@ describe('DiagramEditorSidePanel', function () {
);

await waitFor(() => {
const section = screen.getByText('Relationship properties');
expect(section).to.be.visible;
expect(screen.getByTitle('countries.name → airports.Country')).to.be
.visible;
});

const localCollectionInput = screen.getByLabelText('Local collection');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,162 @@
import React from 'react';
import React, { useMemo } from 'react';
import { connect } from 'react-redux';
import type { DataModelingState } from '../../store/reducer';
import { DrawerSection } from '@mongodb-js/compass-components';
import {
css,
DrawerSection,
ItemActionControls,
} from '@mongodb-js/compass-components';
import CollectionDrawerContent from './collection-drawer-content';
import RelationshipDrawerContent from './relationship-drawer-content';
import {
deleteRelationship,
selectCurrentModelFromState,
} from '../../store/diagram';
import { getDefaultRelationshipName } from '../../utils';

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

const drawerTitleStyles = css({
display: 'flex',
width: '100%',
});

const drawerTitleTextStyles = css({
flex: 1,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
});

const drawerTitleActionGroupStyles = css({});

type DiagramEditorSidePanelProps = {
selectedItems: { type: 'relationship' | 'collection'; id: string } | null;
selectedItems: {
id: string;
type: 'relationship' | 'collection';
label: string;
} | null;
onDeleteRelationship: (rId: string) => void;
};

function DiagramEditorSidePanel({
selectedItems,
onDeleteRelationship,
}: DiagramEditorSidePanelProps) {
if (!selectedItems) {
return null;
}
const { content, label, actions, handleAction } = useMemo(() => {
if (selectedItems?.type === 'collection') {
return {
label: selectedItems.label,
content: (
<CollectionDrawerContent
key={selectedItems.id}
namespace={selectedItems.id}
></CollectionDrawerContent>
),
actions: [],
handleAction: () => {},
};
}

if (selectedItems?.type === 'relationship') {
return {
label: selectedItems.label,
content: (
<RelationshipDrawerContent
key={selectedItems.id}
relationshipId={selectedItems.id}
></RelationshipDrawerContent>
),
actions: [
{ action: 'delete', label: 'Delete', icon: 'Trash' as const },
],
handleAction: (actionName: string) => {
if (actionName === 'delete') {
onDeleteRelationship(selectedItems.id);
}
},
};
}

return { content: null };
}, [selectedItems, onDeleteRelationship]);

let content;

if (selectedItems.type === 'collection') {
content = (
<CollectionDrawerContent
key={selectedItems.id}
namespace={selectedItems.id}
></CollectionDrawerContent>
);
} else if (selectedItems.type === 'relationship') {
content = (
<RelationshipDrawerContent
key={selectedItems.id}
relationshipId={selectedItems.id}
></RelationshipDrawerContent>
);
if (!content) {
return null;
}

return (
<DrawerSection
id={DATA_MODELING_DRAWER_ID}
title="Details"
label="Details"
glyph="InfoWithCircle"
title={
<div className={drawerTitleStyles}>
<span className={drawerTitleTextStyles} title={label}>
{label}
</span>

<ItemActionControls
actions={actions}
iconSize="small"
onAction={handleAction}
className={drawerTitleActionGroupStyles}
// Because the close button here is out of our control, we have do
// adjust the actions rendering in a bit of an unconventional way:
// if there's more than one action available, collapse it to "...",
// if it's just one, make sure button is not collapsed by setting
// collapseAfter to >0
collapseAfter={actions.length > 1 ? 0 : 1}
></ItemActionControls>
</div>
}
label={label}
glyph="Wrench"
autoOpen
// TODO: Leafygreen doesn't allow us to tie close event to a particular
// action. We can add this functionality ourselves, but I'm not sure that
// adding even more logic on top of the drawer is a good idea. Maybe we're
// okay with the drawer close button click just staying there until you
// explicitly click something else?
// onClose={onClose}
>
{content}
</DrawerSection>
);
}

export default connect((state: DataModelingState) => {
return {
selectedItems: state.diagram?.selectedItems ?? null,
};
})(DiagramEditorSidePanel);
export default connect(
(state: DataModelingState) => {
const selected = state.diagram?.selectedItems;

if (!selected) {
return {
selectedItems: null,
};
}

if (selected.type === 'collection') {
return {
selectedItems: {
...selected,
label: selected.id,
},
};
}

if (selected.type === 'relationship') {
const model = selectCurrentModelFromState(state);
const relationship = model.relationships.find((relationship) => {
return relationship.id === selected.id;
});

if (!relationship) {
throw new Error(
'Can not find corresponding relationship when rendering DiagramEditorSidePanel'
);
}

return {
selectedItems: {
...selected,
label: getDefaultRelationshipName(relationship.relationship),
},
};
}
},
{
onDeleteRelationship: deleteRelationship,
}
)(DiagramEditorSidePanel);
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
spacing,
css,
palette,
Button,
Icon,
TextArea,
} from '@mongodb-js/compass-components';
import {
Expand All @@ -34,7 +32,6 @@ type RelationshipDrawerContentProps = {
relationship: Relationship;
fields: Record<string, string[][]>;
onRelationshipUpdate: (relationship: Relationship) => void;
onDeleteRelationshipClick: (rId: string) => void;
};

type RelationshipFormFields = {
Expand All @@ -47,11 +44,6 @@ type RelationshipFormFields = {
note: string;
};

const titleBtnStyles = css({
marginLeft: 'auto',
maxHeight: 20, // to make sure we're matching accordion line height
});

const FIELD_DIVIDER = '~~##$$##~~';

function useRelationshipFormFields(
Expand Down Expand Up @@ -156,13 +148,7 @@ const configurationForeignFieldStyles = css({

const RelationshipDrawerContent: React.FunctionComponent<
RelationshipDrawerContentProps
> = ({
relationshipId,
relationship,
fields,
onRelationshipUpdate,
onDeleteRelationshipClick,
}) => {
> = ({ relationshipId, relationship, fields, onRelationshipUpdate }) => {
const collections = useMemo(() => {
return Object.keys(fields);
}, [fields]);
Expand Down Expand Up @@ -192,25 +178,7 @@ const RelationshipDrawerContent: React.FunctionComponent<

return (
<div data-relationship-id={relationshipId}>
<DMDrawerSection
label={
<>
<span>Relationship properties</span>

<Button
variant="dangerOutline"
leftGlyph={<Icon glyph="Trash" />}
className={titleBtnStyles}
size="xsmall"
onClick={() => {
onDeleteRelationshipClick(relationshipId);
}}
>
Delete
</Button>
</>
}
>
<DMDrawerSection label="Relationship properties">
<div className={configurationContainerStyles}>
<div className={configurationLocalFieldStyles}>
<DMFormFieldContainer>
Expand Down
Loading