Skip to content

Commit 5f9a46a

Browse files
committed
add schema
1 parent 7f34ee8 commit 5f9a46a

File tree

8 files changed

+194
-55
lines changed

8 files changed

+194
-55
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
palette,
2222
} from '@mongodb-js/compass-components';
2323
import { cancelAnalysis, retryAnalysis } from '../store/analysis-process';
24+
import { EditTypeSchema } from '../services/data-model-schema';
2425

2526
const loadingContainerStyles = css({
2627
width: '100%',
@@ -95,7 +96,6 @@ const DiagramEditor: React.FunctionComponent<{
9596
model: unknown;
9697
onRetryClick: () => void;
9798
onCancelClick: () => void;
98-
// TODO
9999
onApplyClick: (edit: unknown) => void;
100100
}> = ({
101101
step,
@@ -112,6 +112,7 @@ const DiagramEditor: React.FunctionComponent<{
112112
const isEditValid = useMemo(() => {
113113
try {
114114
JSON.parse(applyInput);
115+
EditTypeSchema.parse(JSON.parse(applyInput));
115116
return true;
116117
} catch {
117118
return false;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SavedDiagramsList from './saved-diagrams-list';
99
import { renderWithStore } from '../../test/setup-store';
1010
import type { DataModelingStore } from '../../test/setup-store';
1111
import { DataModelStorageServiceProvider } from '../provider';
12-
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
12+
import type { MongoDBDataModelDescription } from '../services/data-model-schema';
1313

1414
const storageItems: MongoDBDataModelDescription[] = [
1515
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@mongodb-js/compass-components';
1515
import { useDataModelSavedItems } from '../provider';
1616
import { deleteDiagram, openDiagram, renameDiagram } from '../store/diagram';
17-
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
17+
import type { MongoDBDataModelDescription } from '../services/data-model-schema';
1818
import CollaborateIcon from './icons/collaborate';
1919
import SchemaVisualizationIcon from './icons/schema-visualization';
2020
import FlexibilityIcon from './icons/flexibility';
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { z } from '@mongodb-js/compass-user-data';
2+
3+
const FieldPathSchema = z.array(z.string());
4+
const NamespaceSchema = z.string().includes('.');
5+
6+
const RelationshipSideSchema = z.object({
7+
ns: NamespaceSchema,
8+
cardinality: z.number(),
9+
fields: z.array(FieldPathSchema),
10+
});
11+
const MongoDBJSONSchema = z.unknown();
12+
const RelationshipSchema = z.object({
13+
id: z.string().uuid(),
14+
relationship: z.tuple([RelationshipSideSchema, RelationshipSideSchema]),
15+
isInferred: z.boolean(),
16+
});
17+
const StaticModelSchema = z.object({
18+
collections: z.array(
19+
z.object({
20+
ns: NamespaceSchema,
21+
jsonSchema: MongoDBJSONSchema,
22+
indexes: z.array(z.unknown()),
23+
shardKey: z.unknown().optional(),
24+
displayPosition: z.tuple([z.number(), z.number()]),
25+
})
26+
),
27+
relationships: z.array(RelationshipSchema),
28+
});
29+
30+
const SetModelSchema = z.object({
31+
type: z.literal('SetModel'),
32+
model: StaticModelSchema,
33+
});
34+
const RenameCollectionSchema = z.object({
35+
type: z.literal('RenameCollection'),
36+
from: NamespaceSchema,
37+
to: NamespaceSchema,
38+
});
39+
const RenameFieldSchema = z.object({
40+
type: z.literal('RenameField'),
41+
ns: NamespaceSchema,
42+
from: FieldPathSchema,
43+
to: FieldPathSchema,
44+
});
45+
const AddFieldSchema = z.object({
46+
type: z.literal('AddField'),
47+
ns: NamespaceSchema,
48+
field: FieldPathSchema,
49+
jsonSchema: MongoDBJSONSchema,
50+
});
51+
const RemoveFieldSchema = z.object({
52+
type: z.literal('RemoveField'),
53+
ns: NamespaceSchema,
54+
field: FieldPathSchema,
55+
});
56+
const AddCollectionSchema = z.object({
57+
type: z.literal('AddCollection'),
58+
ns: NamespaceSchema,
59+
});
60+
const RemoveCollectionSchema = z.object({
61+
type: z.literal('RemoveCollection'),
62+
ns: NamespaceSchema,
63+
});
64+
const ConvertFieldTypeSchema = z.object({
65+
type: z.literal('ConvertFieldType'),
66+
ns: NamespaceSchema,
67+
field: FieldPathSchema,
68+
from: MongoDBJSONSchema,
69+
to: MongoDBJSONSchema,
70+
});
71+
const EmbedCollectionSchema = z.object({
72+
type: z.literal('EmbedCollection'),
73+
source: NamespaceSchema,
74+
target: NamespaceSchema,
75+
targetField: FieldPathSchema,
76+
asNestedArray: z.boolean(),
77+
});
78+
const UnembedCollectionSchema = z.object({
79+
type: z.literal('UnembedCollection'),
80+
source: NamespaceSchema,
81+
target: NamespaceSchema,
82+
fromField: FieldPathSchema,
83+
});
84+
const AddRelationshipSchema = z.object({
85+
type: z.literal('AddRelationship'),
86+
relationship: RelationshipSchema,
87+
});
88+
const RemoveRelationshipSchema = z.object({
89+
type: z.literal('RemoveRelationship'),
90+
id: z.string().uuid(),
91+
});
92+
export const EditTypeSchema = z.discriminatedUnion('type', [
93+
SetModelSchema,
94+
RenameCollectionSchema,
95+
RenameFieldSchema,
96+
AddFieldSchema,
97+
RemoveFieldSchema,
98+
AddCollectionSchema,
99+
RemoveCollectionSchema,
100+
ConvertFieldTypeSchema,
101+
EmbedCollectionSchema,
102+
UnembedCollectionSchema,
103+
AddRelationshipSchema,
104+
RemoveRelationshipSchema,
105+
]);
106+
107+
const EditBasePropsSchema = z.object({
108+
id: z.string(),
109+
timestamp: z.string().datetime(),
110+
copiesEdit: z.string().optional(),
111+
});
112+
113+
export const EditSchema = z.intersection(EditBasePropsSchema, EditTypeSchema);
114+
export type Edit = z.infer<typeof EditSchema>;
115+
116+
export const MongoDBDataModelDescriptionSchema = z.object({
117+
id: z.string(),
118+
name: z.string(),
119+
/**
120+
* Connection id associated with the data model at the moment of configuring
121+
* and analyzing. No connection id means diagram was imported and not attached
122+
* to a connection. Practically speaking it just means that we can't do
123+
* anything that would require re-fetching data associated with the diagram
124+
*/
125+
connectionId: z.string().nullable(),
126+
edits: z.array(EditSchema),
127+
});
128+
129+
export type MongoDBDataModelDescription = z.output<
130+
typeof MongoDBDataModelDescriptionSchema
131+
>;

packages/compass-data-modeling/src/services/data-model-storage-electron.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import React from 'react';
22
import { UserData } from '@mongodb-js/compass-user-data';
3-
import type {
4-
DataModelStorage,
5-
MongoDBDataModelDescription,
6-
} from './data-model-storage';
7-
import { MongoDBDataModelDescriptionSchema } from './data-model-storage';
3+
import type { DataModelStorage } from './data-model-storage';
4+
import type { MongoDBDataModelDescription } from './data-model-schema';
5+
import { MongoDBDataModelDescriptionSchema } from './data-model-schema';
86
import { DataModelStorageServiceProvider } from '../provider';
97

108
class DataModelStorageElectron implements DataModelStorage {

packages/compass-data-modeling/src/services/data-model-storage.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,4 @@
1-
import { z } from '@mongodb-js/compass-user-data';
2-
3-
export const MongoDBDataModelDescriptionSchema = z.object({
4-
id: z.string(),
5-
name: z.string(),
6-
/**
7-
* Connection id associated with the data model at the moment of configuring
8-
* and analyzing. No connection id means diagram was imported and not attached
9-
* to a connection. Practically speaking it just means that we can't do
10-
* anything that would require re-fetching data associated with the diagram
11-
*/
12-
connectionId: z.string().nullable(),
13-
14-
// TODO: define rest of the schema based on arch doc / tech design
15-
edits: z.array(z.unknown()).default([]),
16-
});
17-
18-
export type MongoDBDataModelDescription = z.output<
19-
typeof MongoDBDataModelDescriptionSchema
20-
>;
21-
1+
import type { MongoDBDataModelDescription } from './data-model-schema';
222
export interface DataModelStorage {
233
save(description: MongoDBDataModelDescription): Promise<boolean>;
244
delete(id: MongoDBDataModelDescription['id']): Promise<boolean>;

packages/compass-data-modeling/src/store/analysis-process.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { isAction } from './util';
33
import type { DataModelingThunkAction } from './reducer';
44
import { analyzeDocuments } from 'mongodb-schema';
55
import { getCurrentDiagramFromState } from './diagram';
6-
import type { Document } from 'bson';
6+
import { UUID, type Document } from 'bson';
77
import type { AggregationCursor } from 'mongodb';
8+
import type { Edit } from '../services/data-model-schema';
89

910
export type AnalysisProcessState = {
1011
currentAnalysisOptions:
@@ -61,9 +62,7 @@ export type AnalysisFinishedAction = {
6162
type: AnalysisProcessActionTypes.ANALYSIS_FINISHED;
6263
name: string;
6364
connectionId: string;
64-
// TODO
65-
schema: Record<string, unknown>;
66-
relations: unknown[];
65+
edit: Edit;
6766
};
6867

6968
export type AnalysisFailedAction = {
@@ -188,7 +187,7 @@ export function startAnalysis(
188187
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
189188
namespace: ns,
190189
});
191-
return [ns, schema];
190+
return [ns, schema] as const;
192191
})
193192
);
194193
if (options.automaticallyInferRelations) {
@@ -197,12 +196,27 @@ export function startAnalysis(
197196
if (cancelController.signal.aborted) {
198197
throw cancelController.signal.reason;
199198
}
199+
// As the analysis finishes, we set the model on the diagram
200+
const edit: Edit = {
201+
id: new UUID().toString(),
202+
timestamp: new Date().toISOString(),
203+
type: 'SetModel',
204+
model: {
205+
relationships: [],
206+
collections: schema.map(([ns, jsonSchema]) => ({
207+
ns,
208+
jsonSchema,
209+
indexes: [],
210+
shardKey: undefined,
211+
displayPosition: [0, 0],
212+
})),
213+
},
214+
};
200215
dispatch({
201216
type: AnalysisProcessActionTypes.ANALYSIS_FINISHED,
202217
name,
203218
connectionId,
204-
schema: Object.fromEntries(schema),
205-
relations: [],
219+
edit,
206220
});
207221
void services.dataModelStorage.save(
208222
getCurrentDiagramFromState(getState())

packages/compass-data-modeling/src/store/diagram.ts

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import type { Reducer } from 'redux';
22
import { UUID } from 'bson';
33
import { isAction } from './util';
4-
import type { MongoDBDataModelDescription } from '../services/data-model-storage';
4+
import {
5+
type MongoDBDataModelDescription,
6+
type Edit,
7+
EditSchema,
8+
EditTypeSchema,
9+
} from '../services/data-model-schema';
510
import { AnalysisProcessActionTypes } from './analysis-process';
611
import { memoize } from 'lodash';
712
import type { DataModelingState, DataModelingThunkAction } from './reducer';
8-
import { showConfirmation, showPrompt } from '@mongodb-js/compass-components';
13+
import {
14+
openToast,
15+
showConfirmation,
16+
showPrompt,
17+
} from '@mongodb-js/compass-components';
918

1019
export type DiagramState =
1120
| (Omit<MongoDBDataModelDescription, 'edits'> & {
@@ -44,8 +53,7 @@ export type RenameDiagramAction = {
4453

4554
export type ApplyEditAction = {
4655
type: DiagramActionTypes.APPLY_EDIT;
47-
// TODO
48-
edit: unknown;
56+
edit: Edit;
4957
};
5058

5159
export type UndoEditAction = {
@@ -90,16 +98,7 @@ export const diagramReducer: Reducer<DiagramState> = (
9098
connectionId: action.connectionId,
9199
edits: {
92100
prev: [],
93-
current: [
94-
{
95-
// TODO
96-
type: 'SetModel',
97-
model: {
98-
schema: action.schema,
99-
relations: action.relations,
100-
},
101-
},
102-
],
101+
current: [action.edit],
103102
next: [],
104103
},
105104
};
@@ -172,10 +171,16 @@ export function redoEdit(): DataModelingThunkAction<void, RedoEditAction> {
172171
}
173172

174173
export function applyEdit(
175-
edit: unknown
174+
data: unknown
176175
): DataModelingThunkAction<void, ApplyEditAction> {
177176
return (dispatch, getState, { dataModelStorage }) => {
178-
dispatch({ type: DiagramActionTypes.APPLY_EDIT, edit });
177+
const edit = EditTypeSchema.parse(data);
178+
const extendedEdit = EditSchema.parse({
179+
...edit,
180+
id: new UUID().toString(),
181+
timestamp: new Date().toISOString(),
182+
});
183+
dispatch({ type: DiagramActionTypes.APPLY_EDIT, edit: extendedEdit });
179184
void dataModelStorage.save(getCurrentDiagramFromState(getState()));
180185
};
181186
}
@@ -205,7 +210,7 @@ export function deleteDiagram(
205210
export function renameDiagram(
206211
id: string // TODO maybe pass the whole thing here, we always have it when calling this, then we don't need to re-load storage
207212
): DataModelingThunkAction<Promise<void>, RenameDiagramAction> {
208-
return async (dispatch, getState, { dataModelStorage }) => {
213+
return async (dispatch, getState, { dataModelStorage, logger }) => {
209214
try {
210215
const diagram = await dataModelStorage.load(id);
211216
if (!diagram) {
@@ -222,13 +227,23 @@ export function renameDiagram(
222227
dispatch({ type: DiagramActionTypes.RENAME_DIAGRAM, id, name: newName });
223228
void dataModelStorage.save({ ...diagram, name: newName });
224229
} catch (err) {
225-
// TODO log
230+
logger.log.error(
231+
logger.mongoLogId(1_001_000_351),
232+
'DataModeling',
233+
'Failed to rename diagram',
234+
{ err }
235+
);
236+
openToast('data-modeling-rename-toast', {
237+
variant: 'important',
238+
title: 'Failed to rename diagram',
239+
description: 'Please try again.',
240+
});
226241
}
227242
};
228243
}
229244

230245
// TODO
231-
function _applyEdit(model: any, edit: any) {
246+
function _applyEdit(model: any, edit: Edit) {
232247
if (edit && 'type' in edit) {
233248
if (edit.type === 'SetModel') {
234249
return edit.model;

0 commit comments

Comments
 (0)