Skip to content

feat: lastModified and db for diagram list COMPASS-9398 #6951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 10 additions & 4 deletions packages/compass-components/src/hooks/use-formatted-date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { formatDate } from '../utils/format-date';

import { useState, useEffect } from 'react';

export function useFormattedDate(timestamp: number) {
export function useFormattedDate(timestamp: number): string;
export function useFormattedDate(timestamp?: number): string | undefined;
export function useFormattedDate(timestamp?: number): string | undefined {
const [formattedDate, setFormattedDate] = useState(() =>
formatDate(timestamp)
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
);

useEffect(() => {
setFormattedDate(formatDate(timestamp));
setFormattedDate(
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
);
const interval = setInterval(() => {
setFormattedDate(formatDate(timestamp));
setFormattedDate(
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
);
}, 1000 * 60);
return () => {
clearInterval(interval);
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export { mergeProps } from './utils/merge-props';
export { focusRing, useFocusRing } from './hooks/use-focus-ring';
export { useDefaultAction } from './hooks/use-default-action';
export { useSortControls, useSortedItems } from './hooks/use-sort';
export { useFormattedDate } from './hooks/use-formatted-date';
export * from './hooks/use-formatted-date';
export { fontFamilies } from '@leafygreen-ui/tokens';
export { default as BSONValue } from './components/bson-value';
export * as DocumentList from './components/document-list';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { expect } from 'chai';
import { render, screen } from '@mongodb-js/testing-library-compass';
import { DiagramCard } from './diagram-card';
import type { Edit } from '../services/data-model-storage';

describe('DiagramCard', () => {
const props = {
diagram: {
id: 'test-diagram',
connectionId: 'test-connection',
name: 'Test Diagram',
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2023-10-03T00:00:00.000Z',
edits: [
{
id: 'edit-id',
timestamp: '2023-10-01T00:00:00.000Z',
type: 'SetModel',
model: {
collections: [
{
ns: 'db.collection',
indexes: [],
displayPosition: [0, 0],
shardKey: {},
jsonSchema: { bsonType: 'object' },
},
],
relationships: [],
},
},
] as [Edit],
databases: 'someDatabase',
},
onOpen: () => {},
onDelete: () => {},
onRename: () => {},
};

it('renders name, database, last edited', () => {
render(<DiagramCard {...props} />);
expect(screen.getByText('Test Diagram')).to.be.visible;
expect(screen.getByText('someDatabase')).to.be.visible;
expect(screen.getByText('Last modified: October 3, 2023')).to.be.visible;
});
});
52 changes: 49 additions & 3 deletions packages/compass-data-modeling/src/components/diagram-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,55 @@ import {
Card,
css,
cx,
Icon,
ItemActionMenu,
palette,
spacing,
Subtitle,
useDarkMode,
useFormattedDate,
} from '@mongodb-js/compass-components';
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
import React from 'react';

// Same as saved-queries-aggregations
export const CARD_WIDTH = spacing[1600] * 4;
export const CARD_HEIGHT = 218;
export const CARD_HEIGHT = 180;

const diagramCardStyles = css({
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
});

const cardContentStyles = css({
display: 'flex',
flexDirection: 'column',
height: '100%',
justifyContent: 'flex-end',
gap: spacing[300],
});

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

const namespaceIconStyles = css({
flexShrink: 0,
});

const lastModifiedLabel = css({
fontStyle: 'italic',
});

const namespaceStyles = css({
display: 'flex',
alignItems: 'center',
gap: spacing[200],
});

const cardHeaderStyles = css({
display: 'flex',
gap: spacing[200],
Expand Down Expand Up @@ -48,12 +78,15 @@ export function DiagramCard({
onRename,
onDelete,
}: {
diagram: MongoDBDataModelDescription;
diagram: MongoDBDataModelDescription & {
databases: string;
};
onOpen: (diagram: MongoDBDataModelDescription) => void;
onRename: (id: string) => void;
onDelete: (id: string) => void;
}) {
const darkmode = useDarkMode();
const formattedDate = useFormattedDate(new Date(diagram.updatedAt).getTime());
return (
<Card
className={diagramCardStyles}
Expand Down Expand Up @@ -91,7 +124,20 @@ export function DiagramCard({
}}
></ItemActionMenu>
</div>
{/* TODO(COMPASS-9398): Add lastModified and namespace to the card. */}
<div className={cardContentStyles}>
<div className={namespaceStyles}>
<Icon
title={null}
glyph="Database"
color={palette.gray.dark1}
className={namespaceIconStyles}
></Icon>
<span className={namespaceNameStyles}>{diagram.databases}</span>
</div>
<div className={lastModifiedLabel}>
Last&nbsp;modified: {formattedDate}
</div>
</div>
</Card>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,79 @@ const storageItems: MongoDBDataModelDescription[] = [
{
id: '1',
name: 'One',
edits: [],
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2023-10-03T00:00:00.000Z',
edits: [
{
id: 'edit-id-1',
timestamp: '2023-10-02T00:00:00.000Z',
type: 'SetModel',
model: {
collections: [
{
ns: 'db1.collection1',
indexes: [],
displayPosition: [1, 1],
shardKey: {},
jsonSchema: { bsonType: 'object' },
},
],
relationships: [],
},
},
],
connectionId: null,
},
{
id: '2',
name: 'Two',
edits: [],
createdAt: '2023-10-02T00:00:00.000Z',
updatedAt: '2023-10-04T00:00:00.000Z',
edits: [
{
id: 'edit-id-2',
timestamp: '2023-10-01T00:00:00.000Z',
type: 'SetModel',
model: {
collections: [
{
ns: 'db2.collection2',
indexes: [],
displayPosition: [2, 2],
shardKey: {},
jsonSchema: { bsonType: 'object' },
},
],
relationships: [],
},
},
],
connectionId: null,
},
{
id: '3',
name: 'Three',
edits: [],
createdAt: '2023-10-01T00:00:00.000Z',
updatedAt: '2023-10-05T00:00:00.000Z',
edits: [
{
id: 'edit-id-3',
timestamp: '2023-10-01T00:00:00.000Z',
type: 'SetModel',
model: {
collections: [
{
ns: 'db3.collection3',
indexes: [],
displayPosition: [3, 3],
shardKey: {},
jsonSchema: { bsonType: 'object' },
},
],
relationships: [],
},
},
],
connectionId: null,
},
];
Expand Down Expand Up @@ -126,33 +186,50 @@ describe('SavedDiagramsList', function () {
expect(store.getState().generateDiagramWizard.inProgress).to.be.true;
});

it('filters the list of diagrams', async function () {
const searchInput = screen.getByPlaceholderText('Search');
userEvent.type(searchInput, 'One');
await waitFor(() => {
expect(screen.queryByText('One')).to.exist;
});

await waitFor(() => {
expect(screen.queryByText('Two')).to.not.exist;
expect(screen.queryByText('Three')).to.not.exist;
describe('search', function () {
it('filters the list of diagrams by name', async function () {
const searchInput = screen.getByPlaceholderText('Search');
userEvent.type(searchInput, 'One');
await waitFor(() => {
expect(screen.queryByText('One')).to.exist;
});

await waitFor(() => {
expect(screen.queryByText('Two')).to.not.exist;
expect(screen.queryByText('Three')).to.not.exist;
});
});
});

it('shows empty content when filter for a non-existent diagram', async function () {
const searchInput = screen.getByPlaceholderText('Search');
userEvent.type(searchInput, 'Hello');
await waitFor(() => {
expect(screen.queryByText('No results found.')).to.exist;
expect(
screen.queryByText("We can't find any diagram matching your search.")
).to.exist;
it('filters the list of diagrams by database', async function () {
const searchInput = screen.getByPlaceholderText('Search');
userEvent.type(searchInput, 'db2');
await waitFor(() => {
expect(screen.queryByText('Two')).to.exist;
});

await waitFor(() => {
expect(screen.queryByText('One')).to.not.exist;
expect(screen.queryByText('Three')).to.not.exist;
});
});

await waitFor(() => {
expect(screen.queryByText('One')).to.not.exist;
expect(screen.queryByText('Two')).to.not.exist;
expect(screen.queryByText('Three')).to.not.exist;
it('shows empty content when filter for a non-existent diagram', async function () {
const searchInput = screen.getByPlaceholderText('Search');
userEvent.type(searchInput, 'Hello');
await waitFor(() => {
expect(screen.queryByText('No results found.')).to.exist;
expect(
screen.queryByText(
"We can't find any diagram matching your search."
)
).to.exist;
});

await waitFor(() => {
expect(screen.queryByText('One')).to.not.exist;
expect(screen.queryByText('Two')).to.not.exist;
expect(screen.queryByText('Three')).to.not.exist;
});
});
});
});
Expand Down
Loading
Loading