Skip to content

Commit c594482

Browse files
committed
feat: lastModified and db for diagram list COMPASS-9398
1 parent 4efa62e commit c594482

File tree

3 files changed

+90
-20
lines changed

3 files changed

+90
-20
lines changed

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

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

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

5-
export function useFormattedDate(timestamp: number) {
6-
const [formattedDate, setFormattedDate] = useState(() =>
7-
formatDate(timestamp)
5+
export function useFormattedDate(timestamp?: number) {
6+
const [formattedDate, setFormattedDate] = useState(
7+
() => timestamp && formatDate(timestamp)
88
);
99

1010
useEffect(() => {
11-
setFormattedDate(formatDate(timestamp));
11+
setFormattedDate(timestamp && formatDate(timestamp));
1212
const interval = setInterval(() => {
13-
setFormattedDate(formatDate(timestamp));
13+
setFormattedDate(timestamp && formatDate(timestamp));
1414
}, 1000 * 60);
1515
return () => {
1616
clearInterval(interval);

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

Lines changed: 50 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,16 @@ export function DiagramCard({
4878
onRename,
4979
onDelete,
5080
}: {
51-
diagram: MongoDBDataModelDescription;
81+
diagram: MongoDBDataModelDescription & {
82+
lastModified?: number;
83+
databases?: string;
84+
};
5285
onOpen: (diagram: MongoDBDataModelDescription) => void;
5386
onRename: (id: string) => void;
5487
onDelete: (id: string) => void;
5588
}) {
5689
const darkmode = useDarkMode();
90+
const formattedDate = useFormattedDate(diagram.lastModified);
5791
return (
5892
<Card
5993
className={diagramCardStyles}
@@ -91,7 +125,20 @@ export function DiagramCard({
91125
}}
92126
></ItemActionMenu>
93127
</div>
94-
{/* TODO(COMPASS-9398): Add lastModified and namespace to the card. */}
128+
<div className={cardContentStyles}>
129+
<div className={namespaceStyles}>
130+
<Icon
131+
title={null}
132+
glyph="Database"
133+
color={palette.gray.dark1}
134+
className={namespaceIconStyles}
135+
></Icon>
136+
<span className={namespaceNameStyles}>{diagram.databases}</span>
137+
</div>
138+
<div className={lastModifiedLabel}>
139+
Last&nbsp;modified: {formattedDate}
140+
</div>
141+
</div>
95142
</Card>
96143
);
97144
}

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,30 @@ import {
1313
WorkspaceContainer,
1414
} from '@mongodb-js/compass-components';
1515
import { useDataModelSavedItems } from '../provider';
16-
import { deleteDiagram, openDiagram, renameDiagram } from '../store/diagram';
16+
import {
17+
deleteDiagram,
18+
getCurrentModel,
19+
openDiagram,
20+
renameDiagram,
21+
} from '../store/diagram';
1722
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
1823
import CollaborateIcon from './icons/collaborate';
1924
import SchemaVisualizationIcon from './icons/schema-visualization';
2025
import FlexibilityIcon from './icons/flexibility';
2126
import InsightIcon from './icons/insight';
2227
import { CARD_HEIGHT, CARD_WIDTH, DiagramCard } from './diagram-card';
2328
import { DiagramListToolbar } from './diagram-list-toolbar';
29+
import toNS from 'mongodb-ns';
2430

2531
const sortBy = [
2632
{
2733
name: 'name',
2834
label: 'Name',
2935
},
30-
// TODO(COMPASS-9398): Currently we do not have lastModified.
31-
// {
32-
// name: 'lastModified',
33-
// label: 'Last Modified',
34-
// },
36+
{
37+
name: 'lastModified',
38+
label: 'Last Modified',
39+
},
3540
] as const;
3641

3742
const listContainerStyles = css({ height: '100%' });
@@ -134,24 +139,42 @@ export const SavedDiagramsList: React.FunctionComponent<{
134139
onDiagramDeleteClick,
135140
}) => {
136141
const { items, status } = useDataModelSavedItems();
142+
const decoratedItems = useMemo<
143+
(MongoDBDataModelDescription & {
144+
lastModified?: number;
145+
databases?: string;
146+
})[]
147+
>(() => {
148+
return items.map((item) => {
149+
if (!item.edits.length) return item;
150+
151+
const databases = new Set(
152+
getCurrentModel(item).collections.map(({ ns }) => toNS(ns).database)
153+
);
154+
return {
155+
...item,
156+
lastModified: Date.parse(item.edits[item.edits.length - 1].timestamp),
157+
databases: Array.from(databases).join(', '),
158+
};
159+
});
160+
}, [items]);
137161
const [search, setSearch] = useState('');
138162
const filteredItems = useMemo(() => {
139163
try {
140164
const regex = new RegExp(search, 'i');
141-
// TODO(COMPASS-9398): Currently only searching for name.
142-
// We want to include more fields like namespace.
143-
return items.filter((x) => regex.test(x.name));
165+
return decoratedItems.filter(
166+
(x) => regex.test(x.name) || (x.databases && regex.test(x.databases))
167+
);
144168
} catch {
145-
return items;
169+
return decoratedItems;
146170
}
147-
}, [items, search]);
171+
}, [decoratedItems, search]);
148172
const [sortControls, sortState] = useSortControls(sortBy);
149173
const sortedItems = useSortedItems(filteredItems, sortState);
150174

151175
if (status === 'INITIAL' || status === 'LOADING') {
152176
return null;
153177
}
154-
155178
if (items.length === 0) {
156179
return (
157180
<WorkspaceContainer>

0 commit comments

Comments
 (0)