Skip to content

Commit e09fe66

Browse files
authored
feat: lastModified and db for diagram list COMPASS-9398 (#6951)
1 parent 85bdf1b commit e09fe66

File tree

9 files changed

+266
-56
lines changed

9 files changed

+266
-56
lines changed

packages/compass-components/src/hooks/use-formatted-date.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import { formatDate } from '../utils/format-date';
22

33
import { useState, useEffect } from 'react';
44

5-
export function useFormattedDate(timestamp: number) {
5+
export function useFormattedDate(timestamp: number): string;
6+
export function useFormattedDate(timestamp?: number): string | undefined;
7+
export function useFormattedDate(timestamp?: number): string | undefined {
68
const [formattedDate, setFormattedDate] = useState(() =>
7-
formatDate(timestamp)
9+
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
810
);
911

1012
useEffect(() => {
11-
setFormattedDate(formatDate(timestamp));
13+
setFormattedDate(
14+
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
15+
);
1216
const interval = setInterval(() => {
13-
setFormattedDate(formatDate(timestamp));
17+
setFormattedDate(
18+
typeof timestamp === 'number' ? formatDate(timestamp) : undefined
19+
);
1420
}, 1000 * 60);
1521
return () => {
1622
clearInterval(interval);

packages/compass-components/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export { mergeProps } from './utils/merge-props';
167167
export { focusRing, useFocusRing } from './hooks/use-focus-ring';
168168
export { useDefaultAction } from './hooks/use-default-action';
169169
export { useSortControls, useSortedItems } from './hooks/use-sort';
170-
export { useFormattedDate } from './hooks/use-formatted-date';
170+
export * from './hooks/use-formatted-date';
171171
export { fontFamilies } from '@leafygreen-ui/tokens';
172172
export { default as BSONValue } from './components/bson-value';
173173
export * as DocumentList from './components/document-list';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { render, screen } from '@mongodb-js/testing-library-compass';
4+
import { DiagramCard } from './diagram-card';
5+
import type { Edit } from '../services/data-model-storage';
6+
7+
describe('DiagramCard', () => {
8+
const props = {
9+
diagram: {
10+
id: 'test-diagram',
11+
connectionId: 'test-connection',
12+
name: 'Test Diagram',
13+
createdAt: '2023-10-01T00:00:00.000Z',
14+
updatedAt: '2023-10-03T00:00:00.000Z',
15+
edits: [
16+
{
17+
id: 'edit-id',
18+
timestamp: '2023-10-01T00:00:00.000Z',
19+
type: 'SetModel',
20+
model: {
21+
collections: [
22+
{
23+
ns: 'db.collection',
24+
indexes: [],
25+
displayPosition: [0, 0],
26+
shardKey: {},
27+
jsonSchema: { bsonType: 'object' },
28+
},
29+
],
30+
relationships: [],
31+
},
32+
},
33+
] as [Edit],
34+
databases: 'someDatabase',
35+
},
36+
onOpen: () => {},
37+
onDelete: () => {},
38+
onRename: () => {},
39+
};
40+
41+
it('renders name, database, last edited', () => {
42+
render(<DiagramCard {...props} />);
43+
expect(screen.getByText('Test Diagram')).to.be.visible;
44+
expect(screen.getByText('someDatabase')).to.be.visible;
45+
expect(screen.getByText('Last modified: October 3, 2023')).to.be.visible;
46+
});
47+
});

packages/compass-data-modeling/src/components/diagram-card.tsx

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,55 @@ import {
22
Card,
33
css,
44
cx,
5+
Icon,
56
ItemActionMenu,
67
palette,
78
spacing,
89
Subtitle,
910
useDarkMode,
11+
useFormattedDate,
1012
} from '@mongodb-js/compass-components';
1113
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
1214
import React from 'react';
1315

1416
// Same as saved-queries-aggregations
1517
export const CARD_WIDTH = spacing[1600] * 4;
16-
export const CARD_HEIGHT = 218;
18+
export const CARD_HEIGHT = 180;
1719

1820
const diagramCardStyles = css({
1921
display: 'flex',
2022
flexDirection: 'column',
2123
overflow: 'hidden',
2224
});
2325

26+
const cardContentStyles = css({
27+
display: 'flex',
28+
flexDirection: 'column',
29+
height: '100%',
30+
justifyContent: 'flex-end',
31+
gap: spacing[300],
32+
});
33+
34+
const namespaceNameStyles = css({
35+
whiteSpace: 'nowrap',
36+
textOverflow: 'ellipsis',
37+
overflow: 'hidden',
38+
});
39+
40+
const namespaceIconStyles = css({
41+
flexShrink: 0,
42+
});
43+
44+
const lastModifiedLabel = css({
45+
fontStyle: 'italic',
46+
});
47+
48+
const namespaceStyles = css({
49+
display: 'flex',
50+
alignItems: 'center',
51+
gap: spacing[200],
52+
});
53+
2454
const cardHeaderStyles = css({
2555
display: 'flex',
2656
gap: spacing[200],
@@ -48,12 +78,15 @@ export function DiagramCard({
4878
onRename,
4979
onDelete,
5080
}: {
51-
diagram: MongoDBDataModelDescription;
81+
diagram: MongoDBDataModelDescription & {
82+
databases: string;
83+
};
5284
onOpen: (diagram: MongoDBDataModelDescription) => void;
5385
onRename: (id: string) => void;
5486
onDelete: (id: string) => void;
5587
}) {
5688
const darkmode = useDarkMode();
89+
const formattedDate = useFormattedDate(new Date(diagram.updatedAt).getTime());
5790
return (
5891
<Card
5992
className={diagramCardStyles}
@@ -91,7 +124,20 @@ export function DiagramCard({
91124
}}
92125
></ItemActionMenu>
93126
</div>
94-
{/* TODO(COMPASS-9398): Add lastModified and namespace to the card. */}
127+
<div className={cardContentStyles}>
128+
<div className={namespaceStyles}>
129+
<Icon
130+
title={null}
131+
glyph="Database"
132+
color={palette.gray.dark1}
133+
className={namespaceIconStyles}
134+
></Icon>
135+
<span className={namespaceNameStyles}>{diagram.databases}</span>
136+
</div>
137+
<div className={lastModifiedLabel}>
138+
Last&nbsp;modified: {formattedDate}
139+
</div>
140+
</div>
95141
</Card>
96142
);
97143
}

packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,79 @@ const storageItems: MongoDBDataModelDescription[] = [
1515
{
1616
id: '1',
1717
name: 'One',
18-
edits: [],
18+
createdAt: '2023-10-01T00:00:00.000Z',
19+
updatedAt: '2023-10-03T00:00:00.000Z',
20+
edits: [
21+
{
22+
id: 'edit-id-1',
23+
timestamp: '2023-10-02T00:00:00.000Z',
24+
type: 'SetModel',
25+
model: {
26+
collections: [
27+
{
28+
ns: 'db1.collection1',
29+
indexes: [],
30+
displayPosition: [1, 1],
31+
shardKey: {},
32+
jsonSchema: { bsonType: 'object' },
33+
},
34+
],
35+
relationships: [],
36+
},
37+
},
38+
],
1939
connectionId: null,
2040
},
2141
{
2242
id: '2',
2343
name: 'Two',
24-
edits: [],
44+
createdAt: '2023-10-02T00:00:00.000Z',
45+
updatedAt: '2023-10-04T00:00:00.000Z',
46+
edits: [
47+
{
48+
id: 'edit-id-2',
49+
timestamp: '2023-10-01T00:00:00.000Z',
50+
type: 'SetModel',
51+
model: {
52+
collections: [
53+
{
54+
ns: 'db2.collection2',
55+
indexes: [],
56+
displayPosition: [2, 2],
57+
shardKey: {},
58+
jsonSchema: { bsonType: 'object' },
59+
},
60+
],
61+
relationships: [],
62+
},
63+
},
64+
],
2565
connectionId: null,
2666
},
2767
{
2868
id: '3',
2969
name: 'Three',
30-
edits: [],
70+
createdAt: '2023-10-01T00:00:00.000Z',
71+
updatedAt: '2023-10-05T00:00:00.000Z',
72+
edits: [
73+
{
74+
id: 'edit-id-3',
75+
timestamp: '2023-10-01T00:00:00.000Z',
76+
type: 'SetModel',
77+
model: {
78+
collections: [
79+
{
80+
ns: 'db3.collection3',
81+
indexes: [],
82+
displayPosition: [3, 3],
83+
shardKey: {},
84+
jsonSchema: { bsonType: 'object' },
85+
},
86+
],
87+
relationships: [],
88+
},
89+
},
90+
],
3191
connectionId: null,
3292
},
3393
];
@@ -126,33 +186,50 @@ describe('SavedDiagramsList', function () {
126186
expect(store.getState().generateDiagramWizard.inProgress).to.be.true;
127187
});
128188

129-
it('filters the list of diagrams', async function () {
130-
const searchInput = screen.getByPlaceholderText('Search');
131-
userEvent.type(searchInput, 'One');
132-
await waitFor(() => {
133-
expect(screen.queryByText('One')).to.exist;
134-
});
135-
136-
await waitFor(() => {
137-
expect(screen.queryByText('Two')).to.not.exist;
138-
expect(screen.queryByText('Three')).to.not.exist;
189+
describe('search', function () {
190+
it('filters the list of diagrams by name', async function () {
191+
const searchInput = screen.getByPlaceholderText('Search');
192+
userEvent.type(searchInput, 'One');
193+
await waitFor(() => {
194+
expect(screen.queryByText('One')).to.exist;
195+
});
196+
197+
await waitFor(() => {
198+
expect(screen.queryByText('Two')).to.not.exist;
199+
expect(screen.queryByText('Three')).to.not.exist;
200+
});
139201
});
140-
});
141202

142-
it('shows empty content when filter for a non-existent diagram', async function () {
143-
const searchInput = screen.getByPlaceholderText('Search');
144-
userEvent.type(searchInput, 'Hello');
145-
await waitFor(() => {
146-
expect(screen.queryByText('No results found.')).to.exist;
147-
expect(
148-
screen.queryByText("We can't find any diagram matching your search.")
149-
).to.exist;
203+
it('filters the list of diagrams by database', async function () {
204+
const searchInput = screen.getByPlaceholderText('Search');
205+
userEvent.type(searchInput, 'db2');
206+
await waitFor(() => {
207+
expect(screen.queryByText('Two')).to.exist;
208+
});
209+
210+
await waitFor(() => {
211+
expect(screen.queryByText('One')).to.not.exist;
212+
expect(screen.queryByText('Three')).to.not.exist;
213+
});
150214
});
151215

152-
await waitFor(() => {
153-
expect(screen.queryByText('One')).to.not.exist;
154-
expect(screen.queryByText('Two')).to.not.exist;
155-
expect(screen.queryByText('Three')).to.not.exist;
216+
it('shows empty content when filter for a non-existent diagram', async function () {
217+
const searchInput = screen.getByPlaceholderText('Search');
218+
userEvent.type(searchInput, 'Hello');
219+
await waitFor(() => {
220+
expect(screen.queryByText('No results found.')).to.exist;
221+
expect(
222+
screen.queryByText(
223+
"We can't find any diagram matching your search."
224+
)
225+
).to.exist;
226+
});
227+
228+
await waitFor(() => {
229+
expect(screen.queryByText('One')).to.not.exist;
230+
expect(screen.queryByText('Two')).to.not.exist;
231+
expect(screen.queryByText('Three')).to.not.exist;
232+
});
156233
});
157234
});
158235
});

0 commit comments

Comments
 (0)