Skip to content

Commit 298348d

Browse files
committed
add validation
1 parent ab816be commit 298348d

File tree

5 files changed

+108
-39
lines changed

5 files changed

+108
-39
lines changed

package-lock.json

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-data-modeling/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
"react-dom": "^17.0.2",
9494
"sinon": "^17.0.1",
9595
"typescript": "^5.0.4",
96-
"xvfb-maybe": "^0.2.1"
96+
"xvfb-maybe": "^0.2.1",
97+
"zod": "^3.25.1"
9798
},
9899
"is_compass_plugin": true
99100
}

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

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ import {
1919
spacing,
2020
Button,
2121
palette,
22-
SplitButton,
23-
RadioBoxGroup,
24-
RadioBox,
22+
ErrorSummary,
2523
} from '@mongodb-js/compass-components';
2624
import { cancelAnalysis, retryAnalysis } from '../store/analysis-process';
25+
import type { Edit, StaticModel } from '../services/data-model-storage';
2726

2827
const loadingContainerStyles = css({
2928
width: '100%',
@@ -76,17 +75,20 @@ const modelPreviewStyles = css({
7675
});
7776

7877
const editorContainerStyles = css({
79-
height: 46 + 160 + 34 + 16,
8078
display: 'flex',
8179
flexDirection: 'column',
8280
gap: 8,
8381
boxShadow: `0 0 0 2px ${palette.gray.light2}`,
8482
});
8583

86-
const editorContainerApplyButtonStyles = css({
84+
const editorContainerApplyContainerStyles = css({
8785
paddingLeft: 8,
8886
paddingRight: 8,
89-
alignSelf: 'flex-end',
87+
justifyContent: 'flex-end',
88+
gap: spacing[200],
89+
display: 'flex',
90+
width: '100%',
91+
height: spacing[1200],
9092
});
9193

9294
const editorContainerPlaceholderButtonStyles = css({
@@ -104,18 +106,19 @@ const DiagramEditor: React.FunctionComponent<{
104106
onUndoClick: () => void;
105107
hasRedo: boolean;
106108
onRedoClick: () => void;
107-
model: unknown;
109+
model: StaticModel | null;
110+
editErrors?: string[];
108111
onRetryClick: () => void;
109112
onCancelClick: () => void;
110-
// TODO
111-
onApplyClick: (edit: unknown) => void;
113+
onApplyClick: (edit: Edit) => void;
112114
}> = ({
113115
step,
114116
hasUndo,
115117
onUndoClick,
116118
hasRedo,
117119
onRedoClick,
118120
model,
121+
editErrors,
119122
onRetryClick,
120123
onCancelClick,
121124
onApplyClick,
@@ -137,26 +140,27 @@ const DiagramEditor: React.FunctionComponent<{
137140
case 'AddRelationship':
138141
placeholder = {
139142
type: 'AddRelationship',
140-
id: 'relationship1',
141-
relationship: [
142-
{
143-
ns: 'db.sourceCollection',
144-
cardinality: 1,
145-
fields: ['field1'],
146-
},
147-
{
148-
ns: 'db.targetCollection',
149-
cardinality: 1,
150-
fields: ['field2'],
151-
},
152-
],
153-
isInferred: false,
143+
relationship: {
144+
id: 'relationship1',
145+
relationship: [
146+
{
147+
ns: 'db.sourceCollection',
148+
cardinality: 1,
149+
fields: ['field1'],
150+
},
151+
{
152+
ns: 'db.targetCollection',
153+
cardinality: 1,
154+
fields: ['field2'],
155+
},
156+
],
157+
isInferred: false,
158+
},
154159
};
155160
break;
156161
case 'RemoveRelationship':
157162
placeholder = {
158163
type: 'RemoveRelationship',
159-
id: 'relationship1',
160164
relationshipId: 'relationship1',
161165
};
162166
break;
@@ -244,7 +248,8 @@ const DiagramEditor: React.FunctionComponent<{
244248
maxLines={10}
245249
></CodemirrorMultilineEditor>
246250
</div>
247-
<div className={editorContainerApplyButtonStyles}>
251+
<div className={editorContainerApplyContainerStyles}>
252+
{editErrors && <ErrorSummary errors={editErrors} />}
248253
<Button
249254
onClick={() => {
250255
onApplyClick(JSON.parse(applyInput));
@@ -302,6 +307,7 @@ export default connect(
302307
model: diagram
303308
? selectCurrentModel(getCurrentDiagramFromState(state))
304309
: null,
310+
editErrors: diagram?.editErrors,
305311
};
306312
},
307313
{

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from '@mongodb-js/compass-user-data';
2+
import type { ZodError } from 'zod';
23

34
export const RelationshipSideSchema = z.object({
45
ns: z.string(),
@@ -52,6 +53,24 @@ export const EditSchema = z.discriminatedUnion('type', [
5253
}),
5354
]);
5455

56+
export const validateEdit = (
57+
edit: unknown
58+
): { result: true; errors?: never } | { result: false; errors: string[] } => {
59+
try {
60+
EditSchema.parse(edit);
61+
return { result: true };
62+
} catch (e) {
63+
return {
64+
result: false,
65+
errors: (e as ZodError).issues.map(({ path, message }) =>
66+
message === 'Required'
67+
? `'${path}' is required`
68+
: `Invalid field '${path}': ${message}`
69+
),
70+
};
71+
}
72+
};
73+
5574
export type Edit = z.output<typeof EditSchema>;
5675

5776
export const MongoDBDataModelDescriptionSchema = z.object({

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Reducer } from 'redux';
22
import { UUID } from 'bson';
33
import { isAction } from './util';
4-
import type {
5-
Edit,
6-
MongoDBDataModelDescription,
7-
StaticModel,
4+
import {
5+
validateEdit,
6+
type Edit,
7+
type MongoDBDataModelDescription,
8+
type StaticModel,
89
} from '../services/data-model-storage';
910
import { AnalysisProcessActionTypes } from './analysis-process';
1011
import { memoize } from 'lodash';
@@ -18,6 +19,7 @@ export type DiagramState =
1819
current: Edit[];
1920
next: Edit[][];
2021
};
22+
editErrors?: string[];
2123
})
2224
| null; // null when no diagram is currently open
2325

@@ -26,6 +28,7 @@ export enum DiagramActionTypes {
2628
DELETE_DIAGRAM = 'data-modeling/diagram/DELETE_DIAGRAM',
2729
RENAME_DIAGRAM = 'data-modeling/diagram/RENAME_DIAGRAM',
2830
APPLY_EDIT = 'data-modeling/diagram/APPLY_EDIT',
31+
APPLY_EDIT_FAILED = 'data-modeling/diagram/APPLY_EDIT_FAILED',
2932
UNDO_EDIT = 'data-modeling/diagram/UNDO_EDIT',
3033
REDO_EDIT = 'data-modeling/diagram/REDO_EDIT',
3134
}
@@ -51,6 +54,11 @@ export type ApplyEditAction = {
5154
edit: Edit;
5255
};
5356

57+
export type ApplyEditFailedAction = {
58+
type: DiagramActionTypes.APPLY_EDIT_FAILED;
59+
errors: string[];
60+
};
61+
5462
export type UndoEditAction = {
5563
type: DiagramActionTypes.UNDO_EDIT;
5664
};
@@ -64,6 +72,7 @@ export type DiagramActions =
6472
| DeleteDiagramAction
6573
| RenameDiagramAction
6674
| ApplyEditAction
75+
| ApplyEditFailedAction
6776
| UndoEditAction
6877
| RedoEditAction;
6978

@@ -135,6 +144,13 @@ export const diagramReducer: Reducer<DiagramState> = (
135144
current: [...state.edits.current, action.edit],
136145
next: [],
137146
},
147+
editErrors: undefined,
148+
};
149+
}
150+
if (isAction(action, DiagramActionTypes.APPLY_EDIT_FAILED)) {
151+
return {
152+
...state,
153+
editErrors: action.errors,
138154
};
139155
}
140156
if (isAction(action, DiagramActionTypes.UNDO_EDIT)) {
@@ -183,16 +199,25 @@ export function redoEdit(): DataModelingThunkAction<void, RedoEditAction> {
183199
}
184200

185201
export function applyEdit(
186-
edit: Edit
187-
): DataModelingThunkAction<void, ApplyEditAction> {
202+
rawEdit: Edit
203+
): DataModelingThunkAction<void, ApplyEditAction | ApplyEditFailedAction> {
188204
return (dispatch, getState, { dataModelStorage }) => {
205+
const edit = {
206+
...rawEdit,
207+
id: new UUID().toString(),
208+
timestamp: new Date().toISOString(),
209+
};
210+
const { result: isValid, errors } = validateEdit(edit);
211+
if (!isValid) {
212+
dispatch({
213+
type: DiagramActionTypes.APPLY_EDIT_FAILED,
214+
errors,
215+
});
216+
return;
217+
}
189218
dispatch({
190219
type: DiagramActionTypes.APPLY_EDIT,
191-
edit: {
192-
...edit,
193-
id: new UUID().toString(),
194-
timestamp: new Date().toISOString(),
195-
},
220+
edit,
196221
});
197222
void dataModelStorage.save(getCurrentDiagramFromState(getState()));
198223
};

0 commit comments

Comments
 (0)