Skip to content

Commit d6b51a7

Browse files
authored
fix(compass-schema-validation): add generate button to the editor COMPASS-9145 (#6809)
1 parent 510217f commit d6b51a7

File tree

3 files changed

+161
-26
lines changed

3 files changed

+161
-26
lines changed

packages/compass-schema-validation/src/components/validation-editor.spec.tsx

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,44 @@ import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
33
import { expect } from 'chai';
44
import sinon from 'sinon';
55
import { ValidationEditor } from './validation-editor';
6+
import { PreferencesProvider } from 'compass-preferences-model/provider';
7+
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
68

7-
function renderValidationEditor(
9+
async function renderValidationEditor(
810
props: Partial<React.ComponentProps<typeof ValidationEditor>>
911
) {
1012
const validation = {
11-
validator: '',
13+
validator: '{}',
1214
validationAction: 'warn',
1315
validationLevel: 'moderate',
1416
isChanged: false,
1517
syntaxError: null,
1618
error: null,
1719
} as const;
1820

21+
const preferences = await createSandboxFromDefaultPreferences();
22+
await preferences.savePreferences({ enableExportSchema: true });
23+
1924
return render(
20-
<ValidationEditor
21-
namespace="test.test"
22-
validatorChanged={() => {}}
23-
validationActionChanged={() => {}}
24-
validationLevelChanged={() => {}}
25-
cancelValidation={() => {}}
26-
saveValidation={() => {}}
27-
clearSampleDocuments={() => {}}
28-
serverVersion="8.0.5"
29-
onClickEnableEditRules={() => {}}
30-
validation={validation}
31-
isEditable
32-
isEditingEnabled
33-
{...props}
34-
/>
25+
<PreferencesProvider value={preferences}>
26+
<ValidationEditor
27+
namespace="test.test"
28+
validatorChanged={() => {}}
29+
validationActionChanged={() => {}}
30+
validationLevelChanged={() => {}}
31+
cancelValidation={() => {}}
32+
saveValidation={() => {}}
33+
clearSampleDocuments={() => {}}
34+
serverVersion="8.0.5"
35+
onClickEnableEditRules={() => {}}
36+
validation={validation}
37+
generateValidationRules={() => {}}
38+
isRulesGenerationInProgress={false}
39+
isEditable
40+
isEditingEnabled
41+
{...props}
42+
/>
43+
</PreferencesProvider>
3544
);
3645
}
3746

@@ -40,9 +49,9 @@ describe('ValidationEditor [Component]', function () {
4049
'when it is an editable mode but editing is not yet enabled',
4150
function () {
4251
let onClickEnableEditRulesSpy: sinon.SinonSpy;
43-
beforeEach(function () {
52+
beforeEach(async function () {
4453
onClickEnableEditRulesSpy = sinon.spy();
45-
renderValidationEditor({
54+
await renderValidationEditor({
4655
onClickEnableEditRules: onClickEnableEditRulesSpy,
4756
isEditingEnabled: false,
4857
isEditable: true,
@@ -62,8 +71,8 @@ describe('ValidationEditor [Component]', function () {
6271
);
6372

6473
context('when it is an editable mode and editing is enabled', function () {
65-
beforeEach(function () {
66-
renderValidationEditor({
74+
beforeEach(async function () {
75+
await renderValidationEditor({
6776
isEditable: true,
6877
isEditingEnabled: true,
6978
});
@@ -78,8 +87,8 @@ describe('ValidationEditor [Component]', function () {
7887
});
7988

8089
context('when it is a not editable mode', function () {
81-
beforeEach(function () {
82-
renderValidationEditor({
90+
beforeEach(async function () {
91+
await renderValidationEditor({
8392
isEditable: false,
8493
});
8594
});
@@ -91,4 +100,83 @@ describe('ValidationEditor [Component]', function () {
91100
).to.not.exist;
92101
});
93102
});
103+
104+
context('when the validator is empty', function () {
105+
let onGenerateRulesSpy: sinon.SinonSpy;
106+
beforeEach(async function () {
107+
onGenerateRulesSpy = sinon.spy();
108+
await renderValidationEditor({
109+
generateValidationRules: onGenerateRulesSpy,
110+
isEditable: true,
111+
validation: {
112+
validator: '',
113+
validationAction: 'error',
114+
validationLevel: 'moderate',
115+
isChanged: false,
116+
syntaxError: null,
117+
error: null,
118+
},
119+
});
120+
});
121+
122+
it('allows to generate rules', function () {
123+
const generateBtn = screen.getByRole('button', {
124+
name: 'Generate rules',
125+
});
126+
expect(generateBtn).to.be.visible;
127+
userEvent.click(generateBtn);
128+
expect(onGenerateRulesSpy).to.have.been.calledOnce;
129+
});
130+
});
131+
132+
context('when the validator is empty object', function () {
133+
let onGenerateRulesSpy: sinon.SinonSpy;
134+
beforeEach(async function () {
135+
onGenerateRulesSpy = sinon.spy();
136+
await renderValidationEditor({
137+
generateValidationRules: onGenerateRulesSpy,
138+
isEditable: true,
139+
validation: {
140+
validator: '{}',
141+
validationAction: 'error',
142+
validationLevel: 'moderate',
143+
isChanged: false,
144+
syntaxError: null,
145+
error: null,
146+
},
147+
});
148+
});
149+
150+
it('allows to generate rules', function () {
151+
const generateBtn = screen.getByRole('button', {
152+
name: 'Generate rules',
153+
});
154+
expect(generateBtn).to.be.visible;
155+
userEvent.click(generateBtn);
156+
expect(onGenerateRulesSpy).to.have.been.calledOnce;
157+
});
158+
});
159+
160+
context('when rules generation is in progress', function () {
161+
beforeEach(async function () {
162+
await renderValidationEditor({
163+
isEditable: true,
164+
isRulesGenerationInProgress: true,
165+
validation: {
166+
validator: '{}',
167+
validationAction: 'error',
168+
validationLevel: 'moderate',
169+
isChanged: false,
170+
syntaxError: null,
171+
error: null,
172+
},
173+
});
174+
});
175+
176+
it('allows to generate rules', function () {
177+
const generateBtn = screen.getByTestId('generate-rules-button');
178+
expect(generateBtn).to.be.visible;
179+
expect(generateBtn).to.have.attribute('aria-disabled', 'true');
180+
});
181+
});
94182
});

packages/compass-schema-validation/src/components/validation-editor.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
useConfirmationModal,
1515
useDarkMode,
1616
KeylineCard,
17+
ButtonVariant,
18+
SpinLoader,
19+
Tooltip,
1720
} from '@mongodb-js/compass-components';
1821
import {
1922
CodemirrorMultilineEditor,
@@ -39,6 +42,8 @@ import {
3942
} from '../modules/validation';
4043
import { clearSampleDocuments } from '../modules/sample-documents';
4144
import { enableEditRules } from '../modules/edit-mode';
45+
import { usePreference } from 'compass-preferences-model/provider';
46+
import { generateValidationRules } from '../modules/rules-generation';
4247

4348
const validationEditorStyles = css({
4449
padding: spacing[400],
@@ -48,6 +53,10 @@ const validationOptionsStyles = css({
4853
display: 'flex',
4954
});
5055

56+
const generateButtonContainerStyles = css({
57+
flexGrow: 1,
58+
});
59+
5160
const actionsStyles = css({
5261
display: 'flex',
5362
alignItems: 'center',
@@ -126,6 +135,7 @@ type ValidationEditorProps = {
126135
validationLevelChanged: (level: ValidationLevel) => void;
127136
cancelValidation: () => void;
128137
saveValidation: (text: Validation) => void;
138+
generateValidationRules: () => void;
129139
serverVersion: string;
130140
validation: Pick<
131141
ValidationState,
@@ -138,6 +148,7 @@ type ValidationEditorProps = {
138148
>;
139149
isEditable: boolean;
140150
isEditingEnabled: boolean;
151+
isRulesGenerationInProgress: boolean;
141152
};
142153

143154
/**
@@ -154,11 +165,14 @@ export const ValidationEditor: React.FunctionComponent<
154165
validationLevelChanged,
155166
cancelValidation,
156167
saveValidation,
168+
generateValidationRules,
157169
serverVersion,
158170
validation,
159171
isEditable,
160172
isEditingEnabled,
173+
isRulesGenerationInProgress,
161174
}) => {
175+
const enableExportSchema = usePreference('enableExportSchema');
162176
const track = useTelemetry();
163177
const connectionInfoRef = useConnectionInfoRef();
164178
const { showConfirmation } = useConfirmationModal();
@@ -227,12 +241,43 @@ export const ValidationEditor: React.FunctionComponent<
227241
saveValidationRef.current(validationRef.current);
228242
}, [showConfirmation]);
229243

244+
const isEmpty = useMemo<boolean>(() => {
245+
if (!validation.validator || validation.validator.length === 0) return true;
246+
try {
247+
return Object.keys(JSON.parse(validation.validator)).length === 0;
248+
} catch {
249+
return false;
250+
}
251+
}, [validation.validator]);
252+
230253
return (
231254
<KeylineCard
232255
data-testid="validation-editor"
233256
className={validationEditorStyles}
234257
>
235258
<div className={validationOptionsStyles}>
259+
{enableExportSchema && (
260+
<div className={generateButtonContainerStyles}>
261+
<Tooltip
262+
enabled={!isEmpty}
263+
trigger={
264+
<Button
265+
data-testid="generate-rules-button"
266+
disabled={!isEmpty}
267+
isLoading={isRulesGenerationInProgress}
268+
loadingIndicator={<SpinLoader />}
269+
onClick={generateValidationRules}
270+
variant={ButtonVariant.Primary}
271+
size="small"
272+
>
273+
Generate rules
274+
</Button>
275+
}
276+
>
277+
Clear existing rules before generating new ones
278+
</Tooltip>
279+
</div>
280+
)}
236281
<ActionSelector
237282
isEditable={isEditable && isEditingEnabled}
238283
validationActionChanged={validationActionChanged}
@@ -326,6 +371,7 @@ const mapStateToProps = (state: RootState) => ({
326371
isEditingEnabled: state.editMode.isEditingEnabledByUser,
327372
validation: state.validation,
328373
namespace: state.namespace.ns,
374+
isRulesGenerationInProgress: state.rulesGeneration.isInProgress,
329375
});
330376

331377
/**
@@ -339,4 +385,5 @@ export default connect(mapStateToProps, {
339385
onClickEnableEditRules: enableEditRules,
340386
validationActionChanged,
341387
validationLevelChanged,
388+
generateValidationRules,
342389
})(ValidationEditor);

packages/compass-schema-validation/src/components/validation-selectors.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const LEVEL_HELP_URL =
2323

2424
const validationOptionStyles = css({
2525
display: 'flex',
26-
marginRight: spacing[4],
26+
marginLeft: spacing[4],
2727
alignItems: 'center',
2828
});
2929

@@ -47,7 +47,7 @@ export function ActionSelector({
4747

4848
return (
4949
<div className={validationOptionStyles}>
50-
<Label htmlFor={controlId}>Validation Action</Label>
50+
<Label htmlFor={controlId}>Action</Label>
5151
<IconButton
5252
href={ACTION_HELP_URL}
5353
target="_blank"
@@ -88,7 +88,7 @@ export function LevelSelector({
8888

8989
return (
9090
<div className={validationOptionStyles}>
91-
<Label htmlFor={controlId}>Validation Level</Label>
91+
<Label htmlFor={controlId}>Level</Label>
9292
<IconButton
9393
href={LEVEL_HELP_URL}
9494
target="_blank"

0 commit comments

Comments
 (0)