From b17b3989eecac14a5305bf609435503138acbff5 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 22 May 2025 17:22:57 +0200 Subject: [PATCH 01/11] feat: lastModified and db for diagram list COMPASS-9398 --- .../src/hooks/use-formatted-date.tsx | 10 ++-- .../src/components/diagram-card.tsx | 53 +++++++++++++++++-- .../src/components/saved-diagrams-list.tsx | 47 +++++++++++----- 3 files changed, 90 insertions(+), 20 deletions(-) diff --git a/packages/compass-components/src/hooks/use-formatted-date.tsx b/packages/compass-components/src/hooks/use-formatted-date.tsx index d2ff80c2e9e..ec53b0a32fb 100644 --- a/packages/compass-components/src/hooks/use-formatted-date.tsx +++ b/packages/compass-components/src/hooks/use-formatted-date.tsx @@ -2,15 +2,15 @@ import { formatDate } from '../utils/format-date'; import { useState, useEffect } from 'react'; -export function useFormattedDate(timestamp: number) { - const [formattedDate, setFormattedDate] = useState(() => - formatDate(timestamp) +export function useFormattedDate(timestamp?: number) { + const [formattedDate, setFormattedDate] = useState( + () => timestamp && formatDate(timestamp) ); useEffect(() => { - setFormattedDate(formatDate(timestamp)); + setFormattedDate(timestamp && formatDate(timestamp)); const interval = setInterval(() => { - setFormattedDate(formatDate(timestamp)); + setFormattedDate(timestamp && formatDate(timestamp)); }, 1000 * 60); return () => { clearInterval(interval); diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index b4edae0e7c5..3a53d0eab37 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -2,18 +2,20 @@ 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', @@ -21,6 +23,34 @@ const diagramCardStyles = css({ 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], @@ -48,12 +78,16 @@ export function DiagramCard({ onRename, onDelete, }: { - diagram: MongoDBDataModelDescription; + diagram: MongoDBDataModelDescription & { + lastModified?: number; + databases?: string; + }; onOpen: (diagram: MongoDBDataModelDescription) => void; onRename: (id: string) => void; onDelete: (id: string) => void; }) { const darkmode = useDarkMode(); + const formattedDate = useFormattedDate(diagram.lastModified); return ( - {/* TODO(COMPASS-9398): Add lastModified and namespace to the card. */} +
+
+ + {diagram.databases} +
+
+ Last modified: {formattedDate} +
+
); } diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx index b87eb740a42..cc6d65940e1 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx @@ -13,7 +13,12 @@ import { WorkspaceContainer, } from '@mongodb-js/compass-components'; import { useDataModelSavedItems } from '../provider'; -import { deleteDiagram, openDiagram, renameDiagram } from '../store/diagram'; +import { + deleteDiagram, + getCurrentModel, + openDiagram, + renameDiagram, +} from '../store/diagram'; import type { MongoDBDataModelDescription } from '../services/data-model-storage'; import CollaborateIcon from './icons/collaborate'; import SchemaVisualizationIcon from './icons/schema-visualization'; @@ -21,17 +26,17 @@ import FlexibilityIcon from './icons/flexibility'; import InsightIcon from './icons/insight'; import { CARD_HEIGHT, CARD_WIDTH, DiagramCard } from './diagram-card'; import { DiagramListToolbar } from './diagram-list-toolbar'; +import toNS from 'mongodb-ns'; const sortBy = [ { name: 'name', label: 'Name', }, - // TODO(COMPASS-9398): Currently we do not have lastModified. - // { - // name: 'lastModified', - // label: 'Last Modified', - // }, + { + name: 'lastModified', + label: 'Last Modified', + }, ] as const; const listContainerStyles = css({ height: '100%' }); @@ -134,24 +139,42 @@ export const SavedDiagramsList: React.FunctionComponent<{ onDiagramDeleteClick, }) => { const { items, status } = useDataModelSavedItems(); + const decoratedItems = useMemo< + (MongoDBDataModelDescription & { + lastModified?: number; + databases?: string; + })[] + >(() => { + return items.map((item) => { + if (!item.edits.length) return item; + + const databases = new Set( + getCurrentModel(item).collections.map(({ ns }) => toNS(ns).database) + ); + return { + ...item, + lastModified: Date.parse(item.edits[item.edits.length - 1].timestamp), + databases: Array.from(databases).join(', '), + }; + }); + }, [items]); const [search, setSearch] = useState(''); const filteredItems = useMemo(() => { try { const regex = new RegExp(search, 'i'); - // TODO(COMPASS-9398): Currently only searching for name. - // We want to include more fields like namespace. - return items.filter((x) => regex.test(x.name)); + return decoratedItems.filter( + (x) => regex.test(x.name) || (x.databases && regex.test(x.databases)) + ); } catch { - return items; + return decoratedItems; } - }, [items, search]); + }, [decoratedItems, search]); const [sortControls, sortState] = useSortControls(sortBy); const sortedItems = useSortedItems(filteredItems, sortState); if (status === 'INITIAL' || status === 'LOADING') { return null; } - if (items.length === 0) { return ( From 4ea9ed5b34cc24c695d0084b291f2331c13e6cb2 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 22 May 2025 17:42:15 +0200 Subject: [PATCH 02/11] test: add tests --- .../components/saved-diagrams-list.spec.tsx | 81 +++++++++++++------ 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx index eee6e79f8f4..cbcfad1070c 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx @@ -21,7 +21,23 @@ const storageItems: MongoDBDataModelDescription[] = [ { id: '2', name: 'Two', - edits: [], + edits: [ + { + type: 'SetModel', + model: { + collections: [ + { + ns: 'db2.collection2', + indexes: [], + displayPosition: [0, 0], + shardKey: {}, + jsonSchema: { bsonType: 'object' }, + }, + ], + relationships: [], + }, + }, + ], connectionId: null, }, { @@ -126,33 +142,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; + 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; + }); }); - await waitFor(() => { - expect(screen.queryByText('Two')).to.not.exist; - expect(screen.queryByText('Three')).to.not.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; + }); }); - }); - 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; + 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; + }); }); }); }); From ea0aa6bc44db0ed145eea86ab3600a3b150ac82e Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 22 May 2025 18:40:01 +0200 Subject: [PATCH 03/11] better types --- .../src/hooks/use-formatted-date.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/compass-components/src/hooks/use-formatted-date.tsx b/packages/compass-components/src/hooks/use-formatted-date.tsx index ec53b0a32fb..4573eb41f4a 100644 --- a/packages/compass-components/src/hooks/use-formatted-date.tsx +++ b/packages/compass-components/src/hooks/use-formatted-date.tsx @@ -2,15 +2,20 @@ import { formatDate } from '../utils/format-date'; import { useState, useEffect } from 'react'; -export function useFormattedDate(timestamp?: number) { - const [formattedDate, setFormattedDate] = useState( - () => timestamp && formatDate(timestamp) +export function useFormattedDate(timestamp: number): string; +export function useFormattedDate(timestamp?: number): string | undefined { + const [formattedDate, setFormattedDate] = useState(() => + typeof timestamp === 'number' ? formatDate(timestamp) : undefined ); useEffect(() => { - setFormattedDate(timestamp && formatDate(timestamp)); + setFormattedDate( + typeof timestamp === 'number' ? formatDate(timestamp) : undefined + ); const interval = setInterval(() => { - setFormattedDate(timestamp && formatDate(timestamp)); + setFormattedDate( + typeof timestamp === 'number' ? formatDate(timestamp) : undefined + ); }, 1000 * 60); return () => { clearInterval(interval); From 858bc99001ccbdc99990803dce378ecdbb259c02 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 22 May 2025 19:03:31 +0200 Subject: [PATCH 04/11] trying to get it working --- packages/compass-components/src/index.ts | 2 +- .../src/components/saved-diagrams-list.spec.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 1857adc3c99..d577dd2bf13 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -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'; diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx index cbcfad1070c..4bea152c836 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx @@ -23,6 +23,8 @@ const storageItems: MongoDBDataModelDescription[] = [ name: 'Two', edits: [ { + id: 'edit-id', + timestamp: '2023-10-01T00:00:00.000Z', type: 'SetModel', model: { collections: [ From 5f8e99ef434d53e1bc6c116c337b1b9c83da6f2c Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 22 May 2025 19:03:45 +0200 Subject: [PATCH 05/11] tests --- .../src/components/diagram-card.spec.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/compass-data-modeling/src/components/diagram-card.spec.tsx diff --git a/packages/compass-data-modeling/src/components/diagram-card.spec.tsx b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx new file mode 100644 index 00000000000..222da74a572 --- /dev/null +++ b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { expect } from 'chai'; +import { render, screen } from '@mongodb-js/testing-library-compass'; +import { DiagramCard } from './diagram-card'; + +describe('DiagramCard', () => { + const props = { + diagram: { + id: 'test-diagram', + connectionId: 'test-connection', + name: 'Test Diagram', + edits: [], + lastModified: new Date('2025-01-01').getTime(), + databases: 'someDatabase', + }, + onOpen: () => {}, + onDelete: () => {}, + onRename: () => {}, + }; + + it('renders name, database, last edited', () => { + render(); + expect(screen.getByText('Test Diagram')).to.be.visible; + expect(screen.getByText('someDatabase')).to.be.visible; + expect(screen.getByText('Last modified: January 1, 2025')).to.be.visible; + }); +}); From d8c852d91b8f4c253f86180a79bc62c320f97b9c Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 23 May 2025 11:12:05 +0200 Subject: [PATCH 06/11] help needed --- packages/compass-data-modeling/src/components/diagram-card.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index 3a53d0eab37..fadd29fd3f7 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -87,7 +87,8 @@ export function DiagramCard({ onDelete: (id: string) => void; }) { const darkmode = useDarkMode(); - const formattedDate = useFormattedDate(diagram.lastModified); + // the hook does support undefined, but the function overload is somehow not transferred here + const formattedDate = useFormattedDate(diagram.lastModified as number); return ( Date: Fri, 23 May 2025 12:12:25 +0200 Subject: [PATCH 07/11] extra overloads --- packages/compass-components/src/hooks/use-formatted-date.tsx | 2 ++ packages/compass-data-modeling/src/components/diagram-card.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compass-components/src/hooks/use-formatted-date.tsx b/packages/compass-components/src/hooks/use-formatted-date.tsx index 4573eb41f4a..126e8fa954c 100644 --- a/packages/compass-components/src/hooks/use-formatted-date.tsx +++ b/packages/compass-components/src/hooks/use-formatted-date.tsx @@ -2,7 +2,9 @@ import { formatDate } from '../utils/format-date'; import { useState, useEffect } from 'react'; +export function useFormattedDate(timestamp: undefined): undefined; export function useFormattedDate(timestamp: number): string; +export function useFormattedDate(timestamp?: number): string | undefined; export function useFormattedDate(timestamp?: number): string | undefined { const [formattedDate, setFormattedDate] = useState(() => typeof timestamp === 'number' ? formatDate(timestamp) : undefined diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index fadd29fd3f7..424db6e0392 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -88,7 +88,7 @@ export function DiagramCard({ }) { const darkmode = useDarkMode(); // the hook does support undefined, but the function overload is somehow not transferred here - const formattedDate = useFormattedDate(diagram.lastModified as number); + const formattedDate = useFormattedDate(diagram.lastModified); return ( Date: Fri, 23 May 2025 12:15:00 +0200 Subject: [PATCH 08/11] cleanup --- packages/compass-data-modeling/src/components/diagram-card.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index 424db6e0392..3a53d0eab37 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -87,7 +87,6 @@ export function DiagramCard({ onDelete: (id: string) => void; }) { const darkmode = useDarkMode(); - // the hook does support undefined, but the function overload is somehow not transferred here const formattedDate = useFormattedDate(diagram.lastModified); return ( Date: Fri, 23 May 2025 12:36:50 +0200 Subject: [PATCH 09/11] cleanup --- packages/compass-components/src/hooks/use-formatted-date.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass-components/src/hooks/use-formatted-date.tsx b/packages/compass-components/src/hooks/use-formatted-date.tsx index 126e8fa954c..8ba19ad230b 100644 --- a/packages/compass-components/src/hooks/use-formatted-date.tsx +++ b/packages/compass-components/src/hooks/use-formatted-date.tsx @@ -2,7 +2,6 @@ import { formatDate } from '../utils/format-date'; import { useState, useEffect } from 'react'; -export function useFormattedDate(timestamp: undefined): undefined; export function useFormattedDate(timestamp: number): string; export function useFormattedDate(timestamp?: number): string | undefined; export function useFormattedDate(timestamp?: number): string | undefined { From 95f2333e6b8b5be7de73e0cb0a5c21bf268ded54 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 23 May 2025 15:29:48 +0200 Subject: [PATCH 10/11] type edits as a non empty array --- .../src/components/diagram-card.spec.tsx | 21 ++++++++- .../src/components/diagram-card.tsx | 4 +- .../components/saved-diagrams-list.spec.tsx | 44 +++++++++++++++++-- .../src/components/saved-diagrams-list.tsx | 6 +-- .../src/services/data-model-storage.ts | 2 +- .../src/store/diagram.ts | 14 +++--- 6 files changed, 74 insertions(+), 17 deletions(-) diff --git a/packages/compass-data-modeling/src/components/diagram-card.spec.tsx b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx index 222da74a572..6571b1771ec 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.spec.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx @@ -2,6 +2,7 @@ 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 = { @@ -9,7 +10,25 @@ describe('DiagramCard', () => { id: 'test-diagram', connectionId: 'test-connection', name: 'Test Diagram', - edits: [], + 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], lastModified: new Date('2025-01-01').getTime(), databases: 'someDatabase', }, diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index 3a53d0eab37..db46bd29312 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -79,8 +79,8 @@ export function DiagramCard({ onDelete, }: { diagram: MongoDBDataModelDescription & { - lastModified?: number; - databases?: string; + lastModified: number; + databases: string; }; onOpen: (diagram: MongoDBDataModelDescription) => void; onRename: (id: string) => void; diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx index 4bea152c836..6a4e133c4e7 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.spec.tsx @@ -15,7 +15,25 @@ const storageItems: MongoDBDataModelDescription[] = [ { id: '1', name: 'One', - edits: [], + edits: [ + { + id: 'edit-id-1', + timestamp: '2023-10-01T00:00:00.000Z', + type: 'SetModel', + model: { + collections: [ + { + ns: 'db1.collection1', + indexes: [], + displayPosition: [1, 1], + shardKey: {}, + jsonSchema: { bsonType: 'object' }, + }, + ], + relationships: [], + }, + }, + ], connectionId: null, }, { @@ -23,7 +41,7 @@ const storageItems: MongoDBDataModelDescription[] = [ name: 'Two', edits: [ { - id: 'edit-id', + id: 'edit-id-2', timestamp: '2023-10-01T00:00:00.000Z', type: 'SetModel', model: { @@ -31,7 +49,7 @@ const storageItems: MongoDBDataModelDescription[] = [ { ns: 'db2.collection2', indexes: [], - displayPosition: [0, 0], + displayPosition: [2, 2], shardKey: {}, jsonSchema: { bsonType: 'object' }, }, @@ -45,7 +63,25 @@ const storageItems: MongoDBDataModelDescription[] = [ { id: '3', name: 'Three', - edits: [], + 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, }, ]; diff --git a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx index cc6d65940e1..d5ab911b87e 100644 --- a/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx +++ b/packages/compass-data-modeling/src/components/saved-diagrams-list.tsx @@ -141,13 +141,11 @@ export const SavedDiagramsList: React.FunctionComponent<{ const { items, status } = useDataModelSavedItems(); const decoratedItems = useMemo< (MongoDBDataModelDescription & { - lastModified?: number; - databases?: string; + lastModified: number; + databases: string; })[] >(() => { return items.map((item) => { - if (!item.edits.length) return item; - const databases = new Set( getCurrentModel(item).collections.map(({ ns }) => toNS(ns).database) ); diff --git a/packages/compass-data-modeling/src/services/data-model-storage.ts b/packages/compass-data-modeling/src/services/data-model-storage.ts index 4aef7938400..04e6429d37c 100644 --- a/packages/compass-data-modeling/src/services/data-model-storage.ts +++ b/packages/compass-data-modeling/src/services/data-model-storage.ts @@ -88,7 +88,7 @@ export const MongoDBDataModelDescriptionSchema = z.object({ */ connectionId: z.string().nullable(), - edits: z.array(EditSchema).default([]), + edits: z.array(EditSchema).nonempty(), }); export type MongoDBDataModelDescription = z.output< diff --git a/packages/compass-data-modeling/src/store/diagram.ts b/packages/compass-data-modeling/src/store/diagram.ts index 238de33edc9..1d66631ec1a 100644 --- a/packages/compass-data-modeling/src/store/diagram.ts +++ b/packages/compass-data-modeling/src/store/diagram.ts @@ -12,11 +12,15 @@ import { memoize } from 'lodash'; import type { DataModelingState, DataModelingThunkAction } from './reducer'; import { showConfirmation, showPrompt } from '@mongodb-js/compass-components'; +function isNonEmptyArray(arr: T[]): arr is [T, ...T[]] { + return Array.isArray(arr) && arr.length > 0; +} + export type DiagramState = | (Omit & { edits: { prev: Edit[][]; - current: Edit[]; + current: [Edit, ...Edit[]]; next: Edit[][]; }; editErrors?: string[]; @@ -154,8 +158,8 @@ export const diagramReducer: Reducer = ( }; } if (isAction(action, DiagramActionTypes.UNDO_EDIT)) { - const newCurrent = state.edits.prev.pop(); - if (!newCurrent) { + const newCurrent = state.edits.prev.pop() || []; + if (!isNonEmptyArray(newCurrent)) { return state; } return { @@ -168,8 +172,8 @@ export const diagramReducer: Reducer = ( }; } if (isAction(action, DiagramActionTypes.REDO_EDIT)) { - const newCurrent = state.edits.next.pop(); - if (!newCurrent) { + const newCurrent = state.edits.next.pop() || []; + if (!isNonEmptyArray(newCurrent)) { return state; } return { From 50d0369269837cf0802d847b069bfd8c7f07e3ff Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Tue, 27 May 2025 14:44:41 +0200 Subject: [PATCH 11/11] store last modified at diagram level --- .../src/components/diagram-card.spec.tsx | 5 +++-- .../src/components/diagram-card.tsx | 3 +-- .../src/components/saved-diagrams-list.spec.tsx | 8 +++++++- .../src/components/saved-diagrams-list.tsx | 4 +--- .../src/services/data-model-storage.ts | 3 +++ .../src/store/diagram.spec.ts | 2 ++ .../compass-data-modeling/src/store/diagram.ts | 14 ++++++++++---- 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/compass-data-modeling/src/components/diagram-card.spec.tsx b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx index 6571b1771ec..3f97c7e97a0 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.spec.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.spec.tsx @@ -10,6 +10,8 @@ describe('DiagramCard', () => { 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', @@ -29,7 +31,6 @@ describe('DiagramCard', () => { }, }, ] as [Edit], - lastModified: new Date('2025-01-01').getTime(), databases: 'someDatabase', }, onOpen: () => {}, @@ -41,6 +42,6 @@ describe('DiagramCard', () => { render(); expect(screen.getByText('Test Diagram')).to.be.visible; expect(screen.getByText('someDatabase')).to.be.visible; - expect(screen.getByText('Last modified: January 1, 2025')).to.be.visible; + expect(screen.getByText('Last modified: October 3, 2023')).to.be.visible; }); }); diff --git a/packages/compass-data-modeling/src/components/diagram-card.tsx b/packages/compass-data-modeling/src/components/diagram-card.tsx index db46bd29312..3df4dc4241f 100644 --- a/packages/compass-data-modeling/src/components/diagram-card.tsx +++ b/packages/compass-data-modeling/src/components/diagram-card.tsx @@ -79,7 +79,6 @@ export function DiagramCard({ onDelete, }: { diagram: MongoDBDataModelDescription & { - lastModified: number; databases: string; }; onOpen: (diagram: MongoDBDataModelDescription) => void; @@ -87,7 +86,7 @@ export function DiagramCard({ onDelete: (id: string) => void; }) { const darkmode = useDarkMode(); - const formattedDate = useFormattedDate(diagram.lastModified); + const formattedDate = useFormattedDate(new Date(diagram.updatedAt).getTime()); return ( (() => { @@ -151,7 +150,6 @@ export const SavedDiagramsList: React.FunctionComponent<{ ); return { ...item, - lastModified: Date.parse(item.edits[item.edits.length - 1].timestamp), databases: Array.from(databases).join(', '), }; }); diff --git a/packages/compass-data-modeling/src/services/data-model-storage.ts b/packages/compass-data-modeling/src/services/data-model-storage.ts index 04e6429d37c..2af8b35012a 100644 --- a/packages/compass-data-modeling/src/services/data-model-storage.ts +++ b/packages/compass-data-modeling/src/services/data-model-storage.ts @@ -89,6 +89,9 @@ export const MongoDBDataModelDescriptionSchema = z.object({ connectionId: z.string().nullable(), edits: z.array(EditSchema).nonempty(), + + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), }); export type MongoDBDataModelDescription = z.output< diff --git a/packages/compass-data-modeling/src/store/diagram.spec.ts b/packages/compass-data-modeling/src/store/diagram.spec.ts index 5fd96312c98..14600c15a8d 100644 --- a/packages/compass-data-modeling/src/store/diagram.spec.ts +++ b/packages/compass-data-modeling/src/store/diagram.spec.ts @@ -55,6 +55,8 @@ const loadedDiagram: MongoDBDataModelDescription = { id: 'diagram-id', name: 'diagram-name', connectionId: 'connection-id', + createdAt: '2023-10-01T00:00:00.000Z', + updatedAt: '2023-10-05T00:00:00.000Z', edits: [{ type: 'SetModel', model } as Edit], }; diff --git a/packages/compass-data-modeling/src/store/diagram.ts b/packages/compass-data-modeling/src/store/diagram.ts index 1d66631ec1a..2b86d1efa3a 100644 --- a/packages/compass-data-modeling/src/store/diagram.ts +++ b/packages/compass-data-modeling/src/store/diagram.ts @@ -88,9 +88,7 @@ export const diagramReducer: Reducer = ( ) => { if (isAction(action, DiagramActionTypes.OPEN_DIAGRAM)) { return { - id: action.diagram.id, - connectionId: action.diagram.connectionId, - name: action.diagram.name, + ...action.diagram, edits: { prev: [], current: action.diagram.edits, @@ -104,6 +102,8 @@ export const diagramReducer: Reducer = ( id: new UUID().toString(), name: action.name, connectionId: action.connectionId, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), edits: { prev: [], current: [ @@ -138,6 +138,7 @@ export const diagramReducer: Reducer = ( return { ...state, name: action.name, + updatedAt: new Date().toISOString(), }; } if (isAction(action, DiagramActionTypes.APPLY_EDIT)) { @@ -149,6 +150,7 @@ export const diagramReducer: Reducer = ( next: [], }, editErrors: undefined, + updatedAt: new Date().toISOString(), }; } if (isAction(action, DiagramActionTypes.APPLY_EDIT_FAILED)) { @@ -169,6 +171,7 @@ export const diagramReducer: Reducer = ( current: newCurrent, next: [...state.edits.next, state.edits.current], }, + updatedAt: new Date().toISOString(), }; } if (isAction(action, DiagramActionTypes.REDO_EDIT)) { @@ -183,6 +186,7 @@ export const diagramReducer: Reducer = ( current: newCurrent, next: [...state.edits.next], }, + updatedAt: new Date().toISOString(), }; } return state; @@ -345,10 +349,12 @@ export function getCurrentDiagramFromState( id, connectionId, name, + createdAt, + updatedAt, edits: { current: edits }, } = state.diagram; - return { id, connectionId, name, edits }; + return { id, connectionId, name, edits, createdAt, updatedAt }; } export const selectCurrentModel = memoize(getCurrentModel);