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
122 changes: 105 additions & 17 deletions packages/compass-data-modeling/src/components/diagram-editor.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';
import { expect } from 'chai';
import {
createDefaultConnectionInfo,
createPluginTestHelpers,
screen,
userEvent,
waitFor,
within,
} from '@mongodb-js/testing-library-compass';
import DiagramEditor from './diagram-editor';
import type { DataModelingStore } from '../../test/setup-store';
Expand All @@ -17,6 +20,13 @@ import { DiagramProvider } from '@mongodb-js/diagramming';
import { DataModelingWorkspaceTab } from '..';
import { openDiagram } from '../store/diagram';
import { DrawerAnchor } from '@mongodb-js/compass-components';
import { type AnalysisOptions, startAnalysis } from '../store/analysis-process';
import type { DataService } from '@mongodb-js/compass-connections/provider';

const mockConnections = [
{ ...createDefaultConnectionInfo(), id: 'connection1' },
{ ...createDefaultConnectionInfo(), id: 'connection2' },
];

const storageItems: MongoDBDataModelDescription[] = [
{
Expand Down Expand Up @@ -108,49 +118,87 @@ const mockDiagramming = {
},
};

const renderDiagramEditor = ({
items = storageItems,
renderedItem = items[0],
}: {
items?: MongoDBDataModelDescription[];
renderedItem?: MongoDBDataModelDescription;
} = {}) => {
const renderDiagramEditor = async ({
existingDiagram,
newDiagram,
}:
| {
existingDiagram: MongoDBDataModelDescription;
newDiagram?: never;
}
| {
newDiagram: {
name: string;
database: string;
connectionId: string;
collections: string[];
analysisOptions: AnalysisOptions;
};
existingDiagram?: never;
}) => {
const mockDataModelStorage = {
status: 'READY',
error: null,
items,
items: storageItems,
save: () => {
return Promise.resolve(false);
},
delete: () => {
return Promise.resolve(false);
},
loadAll: () => Promise.resolve(items),
loadAll: () => Promise.resolve(storageItems),
load: (id: string) => {
return Promise.resolve(items.find((x) => x.id === id) ?? null);
return Promise.resolve(storageItems.find((x) => x.id === id) ?? null);
},
};

const { renderWithConnections } = createPluginTestHelpers(
const { renderWithActiveConnection } = createPluginTestHelpers(
DataModelingWorkspaceTab.provider.withMockServices({
services: {
dataModelStorage: mockDataModelStorage,
},
}),
{
namespace: 'foo.bar',
} as any
}
);
const {
plugin: { store },
} = renderWithConnections(
} = await renderWithActiveConnection(
<DrawerAnchor>
<DiagramProvider fitView>
<DiagramEditor />
</DiagramProvider>
</DrawerAnchor>
</DrawerAnchor>,
mockConnections[0],
{
connections: mockConnections,
connectFn: () => {
return {
sample: () =>
Promise.resolve([
{
_id: 'doc1',
},
{
_id: 'doc2',
},
]),
} as unknown as DataService;
},
}
);
store.dispatch(openDiagram(renderedItem));
if (existingDiagram) store.dispatch(openDiagram(existingDiagram));
if (newDiagram)
store.dispatch(
startAnalysis(
newDiagram.name,
newDiagram.connectionId,
newDiagram.database,
newDiagram.collections,
newDiagram.analysisOptions
)
);

return { store };
};
Expand All @@ -168,8 +216,8 @@ describe('DiagramEditor', function () {

context('with existing diagram', function () {
beforeEach(async function () {
const result = renderDiagramEditor({
renderedItem: storageItems[0],
const result = await renderDiagramEditor({
existingDiagram: storageItems[0],
});
store = result.store;

Expand All @@ -179,6 +227,10 @@ describe('DiagramEditor', function () {
});
});

it('does not show the banner', function () {
expect(screen.queryByText('Worried about your data?')).not.to.exist;
});

it('does not change the position of the nodes', function () {
const state = store.getState();

Expand All @@ -200,4 +252,40 @@ describe('DiagramEditor', function () {
);
});
});

context('with a new diagram', function () {
beforeEach(async function () {
const result = await renderDiagramEditor({
newDiagram: {
name: 'New Diagram',
database: 'test',
connectionId: 'connection1',
collections: ['collection1', 'collection2'],
analysisOptions: {
automaticallyInferRelations: false,
},
},
});
store = result.store;

// wait till the editor is loaded
await waitFor(() => {
expect(screen.getByTestId('model-preview')).to.be.visible;
});
});

it('shows the banner', function () {
expect(screen.getByText('Questions about your data?')).to.be.visible;
});

it('banner can be closed', function () {
const closeBtn = within(screen.getByTestId('data-info-banner')).getByRole(
'button',
{ name: 'Close Message' }
);
expect(closeBtn).to.be.visible;
userEvent.click(closeBtn);
expect(screen.queryByText('Questions about your data?')).not.to.exist;
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
getHighlightedFields,
relationshipToDiagramEdge,
} from '../utils/nodes-and-edges';
import toNS from 'mongodb-ns';

const loadingContainerStyles = css({
width: '100%',
Expand All @@ -57,14 +58,25 @@ const loaderStyles = css({
margin: '0 auto',
});

const bannerStyles = css({
const errorBannerStyles = css({
margin: spacing[200],
'& > div': {
display: 'flex',
alignItems: 'center',
},
});

const dataInfoBannerStyles = css({
margin: spacing[400],
position: 'absolute',
zIndex: 100,

h4: {
marginTop: 0,
marginBottom: 0,
},
});

const bannerButtonStyles = css({
marginLeft: 'auto',
});
Expand All @@ -73,7 +85,7 @@ const ErrorBannerWithRetry: React.FunctionComponent<{
onRetryClick: () => void;
}> = ({ children, onRetryClick }) => {
return (
<Banner variant="danger" className={bannerStyles}>
<Banner variant="danger" className={errorBannerStyles}>
<div>{children}</div>
<Button
className={bannerButtonStyles}
Expand Down Expand Up @@ -113,6 +125,8 @@ type SelectedItems = NonNullable<DiagramState>['selectedItems'];

const DiagramContent: React.FunctionComponent<{
diagramLabel: string;
database: string | null;
isNewlyCreatedDiagram?: boolean;
model: StaticModel | null;
isInRelationshipDrawingMode: boolean;
editErrors?: string[];
Expand All @@ -134,6 +148,8 @@ const DiagramContent: React.FunctionComponent<{
onRelationshipDrawn: () => void;
}> = ({
diagramLabel,
database,
isNewlyCreatedDiagram,
model,
isInRelationshipDrawingMode,
newCollection,
Expand All @@ -151,6 +167,9 @@ const DiagramContent: React.FunctionComponent<{
const diagram = useRef(useDiagram());
const { openDrawer } = useDrawerActions();
const { isDrawerOpen } = useDrawerState();
const [showDataInfoBanner, setshowDataInfoBanner] = useState(
isNewlyCreatedDiagram ?? false
);

const setDiagramContainerRef = useCallback((ref: HTMLDivElement | null) => {
if (ref) {
Expand Down Expand Up @@ -307,6 +326,20 @@ const DiagramContent: React.FunctionComponent<{
data-testid="diagram-editor-container"
>
<div className={modelPreviewStyles} data-testid="model-preview">
{showDataInfoBanner && (
<Banner
variant="info"
dismissible
onClose={() => setshowDataInfoBanner(false)}
className={dataInfoBannerStyles}
data-testid="data-info-banner"
>
<h4>Questions about your data?</h4>
This diagram was generated based on a sample of documents from{' '}
{database ?? 'a database'}. Changes made to the diagram will not
impact your data
</Banner>
)}
<Diagram
isDarkMode={isDarkMode}
title={diagramLabel}
Expand All @@ -331,11 +364,16 @@ const DiagramContent: React.FunctionComponent<{
const ConnectedDiagramContent = connect(
(state: DataModelingState) => {
const { diagram } = state;
const model = diagram ? selectCurrentModelFromState(state) : null;
return {
model: diagram ? selectCurrentModelFromState(state) : null,
model,
diagramLabel: diagram?.name || 'Schema Preview',
selectedItems: state.diagram?.selectedItems ?? null,
newCollection: diagram?.draftCollection,
isNewlyCreatedDiagram: diagram?.isNewlyCreated,
database: model?.collections[0]?.ns
? toNS(model.collections[0].ns).database
: null, // TODO(COMPASS-9718): use diagram.database
};
},
{
Expand Down
3 changes: 3 additions & 0 deletions packages/compass-data-modeling/src/store/diagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type DiagramState =
};
editErrors?: string[];
selectedItems: SelectedItems | null;
isNewlyCreated: boolean;
draftCollection?: string;
})
| null; // null when no diagram is currently open
Expand Down Expand Up @@ -159,6 +160,7 @@ export const diagramReducer: Reducer<DiagramState> = (
prev.shift(); // Remove the first item, which is initial SetModel and there's no previous edit for it.
return {
...action.diagram,
isNewlyCreated: false,
edits: {
prev,
current,
Expand All @@ -171,6 +173,7 @@ export const diagramReducer: Reducer<DiagramState> = (
if (isAction(action, AnalysisProcessActionTypes.ANALYSIS_FINISHED)) {
return {
id: new UUID().toString(),
isNewlyCreated: true,
name: action.name,
connectionId: action.connectionId,
createdAt: new Date().toISOString(),
Expand Down
1 change: 1 addition & 0 deletions packages/compass-e2e-tests/helpers/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ export const DataModelCollectionRelationshipItemEdit = `[aria-label="Edit relati
export const DataModelCollectionRelationshipItemDelete = `[aria-label="Delete relationship"]`;
export const DataModelCollectionSidebarItemDelete = `[aria-label="Delete collection"]`;
export const DataModelCollectionSidebarItemDeleteButton = `[data-action="delete"]`;
export const DataModelInfoBannerCloseBtn = `[data-testid="data-info-banner"] [aria-label="Close Message"]`;

// Side drawer
export const SideDrawer = `[data-testid="${getDrawerIds().root}"]`;
Expand Down
5 changes: 5 additions & 0 deletions packages/compass-e2e-tests/tests/data-modeling-tab.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ async function setupDiagram(
// Wait for the diagram editor to load
const dataModelEditor = browser.$(Selectors.DataModelEditor);
await dataModelEditor.waitForDisplayed();

// Close the info banner to get it out of the way
const infoBannerCloseBtn = browser.$(Selectors.DataModelInfoBannerCloseBtn);
await infoBannerCloseBtn.waitForClickable();
await browser.clickVisible(Selectors.DataModelInfoBannerCloseBtn);
}

async function selectCollectionOnTheDiagram(
Expand Down
Loading