Skip to content

Commit 1d51702

Browse files
committed
feat(schema-validation): add in progress saving state
1 parent f954cb5 commit 1d51702

File tree

5 files changed

+127
-1
lines changed

5 files changed

+127
-1
lines changed

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async function renderValidationEditor(
3636
validation={validation}
3737
generateValidationRules={() => {}}
3838
isRulesGenerationInProgress={false}
39+
isSavingInProgress={false}
3940
isEditable
4041
isEditingEnabled
4142
{...props}
@@ -44,6 +45,8 @@ async function renderValidationEditor(
4445
);
4546
}
4647

48+
const updateValidationTestId = 'update-validation-button';
49+
4750
describe('ValidationEditor [Component]', function () {
4851
context(
4952
'when it is an editable mode but editing is not yet enabled',
@@ -173,10 +176,73 @@ describe('ValidationEditor [Component]', function () {
173176
});
174177
});
175178

176-
it('allows to generate rules', function () {
179+
it('disables the ability to generate rules', function () {
177180
const generateBtn = screen.getByTestId('generate-rules-button');
178181
expect(generateBtn).to.be.visible;
179182
expect(generateBtn).to.have.attribute('aria-disabled', 'true');
180183
});
181184
});
185+
186+
context('when the validator is not changed', function () {
187+
beforeEach(async function () {
188+
await renderValidationEditor({
189+
validation: {
190+
validator: '{}',
191+
validationAction: 'error',
192+
validationLevel: 'moderate',
193+
isChanged: false,
194+
syntaxError: null,
195+
error: null,
196+
},
197+
});
198+
});
199+
200+
it('does not allow applying the rules', function () {
201+
const applyBtn = screen.queryByTestId(updateValidationTestId);
202+
expect(applyBtn).to.not.exist;
203+
});
204+
});
205+
206+
context('when the validator is changed', function () {
207+
beforeEach(async function () {
208+
await renderValidationEditor({
209+
validation: {
210+
validator: '{}',
211+
validationAction: 'error',
212+
validationLevel: 'moderate',
213+
isChanged: true,
214+
syntaxError: null,
215+
error: null,
216+
},
217+
});
218+
});
219+
220+
it('allows to apply changes', function () {
221+
const applyBtn = screen.getByTestId(updateValidationTestId);
222+
expect(applyBtn).to.be.visible;
223+
expect(applyBtn).to.have.attribute('aria-disabled', 'false');
224+
});
225+
});
226+
227+
context('when the validation saving is in progress', function () {
228+
beforeEach(async function () {
229+
await renderValidationEditor({
230+
validation: {
231+
validator: '{}',
232+
validationAction: 'error',
233+
validationLevel: 'moderate',
234+
isChanged: true,
235+
syntaxError: null,
236+
error: null,
237+
},
238+
isSavingInProgress: true,
239+
});
240+
});
241+
242+
it('disables the apply button and shows a spin loader', function () {
243+
const applyBtn = screen.queryByTestId(updateValidationTestId);
244+
expect(applyBtn).to.be.visible;
245+
expect(applyBtn).to.have.attribute('aria-disabled', 'true');
246+
});
247+
});
182248
});

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ type ValidationEditorProps = {
149149
isEditable: boolean;
150150
isEditingEnabled: boolean;
151151
isRulesGenerationInProgress: boolean;
152+
isSavingInProgress: boolean;
152153
};
153154

154155
/**
@@ -171,6 +172,7 @@ export const ValidationEditor: React.FunctionComponent<
171172
isEditable,
172173
isEditingEnabled,
173174
isRulesGenerationInProgress,
175+
isSavingInProgress,
174176
}) => {
175177
const enableExportSchema = usePreference('enableExportSchema');
176178
const track = useTelemetry();
@@ -343,6 +345,8 @@ export const ValidationEditor: React.FunctionComponent<
343345
onClick={() => {
344346
void onClickApplyValidation();
345347
}}
348+
isLoading={isSavingInProgress}
349+
loadingIndicator={<SpinLoader title="Updating validation…" />}
346350
disabled={!!syntaxError}
347351
>
348352
Apply
@@ -372,6 +376,7 @@ const mapStateToProps = (state: RootState) => ({
372376
validation: state.validation,
373377
namespace: state.namespace.ns,
374378
isRulesGenerationInProgress: state.rulesGeneration.isInProgress,
379+
isSavingInProgress: state.validation.isSaving,
375380
});
376381

377382
/**

packages/compass-schema-validation/src/modules/validation.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('validation module', function () {
3232
validationAction: 'error',
3333
validationLevel: 'strict',
3434
isChanged: false,
35+
isSaving: false,
3536
syntaxError: null,
3637
error: null,
3738
});
@@ -131,6 +132,7 @@ describe('validation module', function () {
131132

132133
expect(validation).to.deep.equal({
133134
isChanged: false,
135+
isSaving: false,
134136
prevValidation: {
135137
validator,
136138
validationAction: 'warn',
@@ -159,6 +161,7 @@ describe('validation module', function () {
159161
validationAction: 'error',
160162
validationLevel: 'strict',
161163
isChanged: false,
164+
isSaving: false,
162165
syntaxError: null,
163166
error: {
164167
message: 'Validation save failed!',

packages/compass-schema-validation/src/modules/validation.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export type ValidationLevel = 'off' | 'moderate' | 'strict';
1818
export const enum ValidationActions {
1919
ValidatorChanged = 'compass-schema-validation/validation/ValidatorChanged',
2020
ValidationCanceled = 'compass-schema-validation/validation/ValidationCanceled',
21+
ValidationSaveStarted = 'compass-schema-validation/validation/ValidationSaveStarted',
22+
ValidationSaveEnded = 'compass-schema-validation/validation/ValidationSaveEnded',
2123
ValidationSaveFailed = 'compass-schema-validation/validation/ValidationSaveFailed',
2224
ValidationFetched = 'compass-schema-validation/validation/ValidationFetched',
2325
EmptyValidationFetched = 'compass-schema-validation/validation/EmptyValidationFetched',
@@ -35,6 +37,14 @@ export interface ValidationCanceledAction {
3537
type: ValidationActions.ValidationCanceled;
3638
}
3739

40+
interface ValidationSaveStartedAction {
41+
type: ValidationActions.ValidationSaveStarted;
42+
}
43+
44+
interface ValidationSaveEndedAction {
45+
type: ValidationActions.ValidationSaveEnded;
46+
}
47+
3848
interface ValidationSaveFailedAction {
3949
type: ValidationActions.ValidationSaveFailed;
4050
error: { message: string };
@@ -86,6 +96,7 @@ export interface Validation {
8696

8797
export interface ValidationState extends Validation {
8898
isChanged: boolean;
99+
isSaving: boolean;
89100
syntaxError: null | { message: string };
90101
error: null | { message: string };
91102
prevValidation?: Validation;
@@ -99,6 +110,7 @@ export const INITIAL_STATE: ValidationState = {
99110
validationAction: 'error',
100111
validationLevel: 'strict',
101112
isChanged: false,
113+
isSaving: false,
102114
syntaxError: null,
103115
error: null,
104116
};
@@ -165,6 +177,31 @@ export default function reducer(
165177
};
166178
}
167179

180+
if (
181+
isAction<ValidationSaveStartedAction>(
182+
action,
183+
ValidationActions.ValidationSaveStarted
184+
)
185+
) {
186+
return {
187+
...state,
188+
error: null,
189+
isSaving: true,
190+
};
191+
}
192+
193+
if (
194+
isAction<ValidationSaveEndedAction>(
195+
action,
196+
ValidationActions.ValidationSaveEnded
197+
)
198+
) {
199+
return {
200+
...state,
201+
isSaving: false,
202+
};
203+
}
204+
168205
if (
169206
isAction<ValidationSaveFailedAction>(
170207
action,
@@ -369,6 +406,14 @@ export const emptyValidationFetched = ({
369406
validationTemplate,
370407
});
371408

409+
export const validationSaveStarted = (): ValidationSaveStartedAction => ({
410+
type: ValidationActions.ValidationSaveStarted,
411+
});
412+
413+
export const validationSaveEnded = (): ValidationSaveEndedAction => ({
414+
type: ValidationActions.ValidationSaveEnded,
415+
});
416+
372417
export const validationCanceled = (): ValidationCanceledAction => ({
373418
type: ValidationActions.ValidationCanceled,
374419
});
@@ -446,6 +491,9 @@ export const saveValidation = (
446491
validation_level: validation.validationLevel,
447492
};
448493
track('Schema Validation Updated', trackEvent, connectionInfoRef.current);
494+
495+
dispatch(validationSaveStarted());
496+
449497
try {
450498
await dataService.updateCollection(
451499
`${namespace.database}.${namespace.collection}`,
@@ -463,6 +511,8 @@ export const saveValidation = (
463511
dispatch(disableEditRules());
464512
} catch (error) {
465513
dispatch(validationSaveFailed(error as Error));
514+
} finally {
515+
dispatch(validationSaveEnded());
466516
}
467517
};
468518
};

packages/compass-schema-validation/src/stores/store.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ describe('Schema Validation Store', function () {
217217
error: null,
218218
syntaxError: null,
219219
isChanged: false,
220+
isSaving: false,
220221
prevValidation: {
221222
validator,
222223
validationAction: 'warn',
@@ -242,6 +243,7 @@ describe('Schema Validation Store', function () {
242243
error: { message: 'Validation fetch failed!' },
243244
syntaxError: null,
244245
isChanged: true,
246+
isSaving: false,
245247
prevValidation: {
246248
validator: '{}',
247249
validationAction: 'error',

0 commit comments

Comments
 (0)