diff --git a/packages/compass-schema-validation/src/components/validation-editor.spec.tsx b/packages/compass-schema-validation/src/components/validation-editor.spec.tsx index aee40f00dbb..43945859c61 100644 --- a/packages/compass-schema-validation/src/components/validation-editor.spec.tsx +++ b/packages/compass-schema-validation/src/components/validation-editor.spec.tsx @@ -36,6 +36,7 @@ async function renderValidationEditor( validation={validation} generateValidationRules={() => {}} isRulesGenerationInProgress={false} + isSavingInProgress={false} isEditable isEditingEnabled {...props} @@ -44,6 +45,8 @@ async function renderValidationEditor( ); } +const updateValidationTestId = 'update-validation-button'; + describe('ValidationEditor [Component]', function () { context( 'when it is an editable mode but editing is not yet enabled', @@ -173,10 +176,73 @@ describe('ValidationEditor [Component]', function () { }); }); - it('allows to generate rules', function () { + it('disables the ability to generate rules', function () { const generateBtn = screen.getByTestId('generate-rules-button'); expect(generateBtn).to.be.visible; expect(generateBtn).to.have.attribute('aria-disabled', 'true'); }); }); + + context('when the validator is not changed', function () { + beforeEach(async function () { + await renderValidationEditor({ + validation: { + validator: '{}', + validationAction: 'error', + validationLevel: 'moderate', + isChanged: false, + syntaxError: null, + error: null, + }, + }); + }); + + it('does not allow applying the rules', function () { + const applyBtn = screen.queryByTestId(updateValidationTestId); + expect(applyBtn).to.not.exist; + }); + }); + + context('when the validator is changed', function () { + beforeEach(async function () { + await renderValidationEditor({ + validation: { + validator: '{}', + validationAction: 'error', + validationLevel: 'moderate', + isChanged: true, + syntaxError: null, + error: null, + }, + }); + }); + + it('allows to apply changes', function () { + const applyBtn = screen.getByTestId(updateValidationTestId); + expect(applyBtn).to.be.visible; + expect(applyBtn).to.have.attribute('aria-disabled', 'false'); + }); + }); + + context('when the validation saving is in progress', function () { + beforeEach(async function () { + await renderValidationEditor({ + validation: { + validator: '{}', + validationAction: 'error', + validationLevel: 'moderate', + isChanged: true, + syntaxError: null, + error: null, + }, + isSavingInProgress: true, + }); + }); + + it('disables the apply button and shows a spin loader', function () { + const applyBtn = screen.queryByTestId(updateValidationTestId); + expect(applyBtn).to.be.visible; + expect(applyBtn).to.have.attribute('aria-disabled', 'true'); + }); + }); }); diff --git a/packages/compass-schema-validation/src/components/validation-editor.tsx b/packages/compass-schema-validation/src/components/validation-editor.tsx index 2c41c9239c8..613e9ca472a 100644 --- a/packages/compass-schema-validation/src/components/validation-editor.tsx +++ b/packages/compass-schema-validation/src/components/validation-editor.tsx @@ -149,6 +149,7 @@ type ValidationEditorProps = { isEditable: boolean; isEditingEnabled: boolean; isRulesGenerationInProgress: boolean; + isSavingInProgress: boolean; }; /** @@ -171,6 +172,7 @@ export const ValidationEditor: React.FunctionComponent< isEditable, isEditingEnabled, isRulesGenerationInProgress, + isSavingInProgress, }) => { const enableExportSchema = usePreference('enableExportSchema'); const track = useTelemetry(); @@ -343,6 +345,8 @@ export const ValidationEditor: React.FunctionComponent< onClick={() => { void onClickApplyValidation(); }} + isLoading={isSavingInProgress} + loadingIndicator={} disabled={!!syntaxError} > Apply @@ -372,6 +376,7 @@ const mapStateToProps = (state: RootState) => ({ validation: state.validation, namespace: state.namespace.ns, isRulesGenerationInProgress: state.rulesGeneration.isInProgress, + isSavingInProgress: state.validation.isSaving, }); /** diff --git a/packages/compass-schema-validation/src/modules/validation.spec.ts b/packages/compass-schema-validation/src/modules/validation.spec.ts index cce90e53ddf..876076209d9 100644 --- a/packages/compass-schema-validation/src/modules/validation.spec.ts +++ b/packages/compass-schema-validation/src/modules/validation.spec.ts @@ -32,6 +32,7 @@ describe('validation module', function () { validationAction: 'error', validationLevel: 'strict', isChanged: false, + isSaving: false, syntaxError: null, error: null, }); @@ -131,6 +132,7 @@ describe('validation module', function () { expect(validation).to.deep.equal({ isChanged: false, + isSaving: false, prevValidation: { validator, validationAction: 'warn', @@ -159,6 +161,7 @@ describe('validation module', function () { validationAction: 'error', validationLevel: 'strict', isChanged: false, + isSaving: false, syntaxError: null, error: { message: 'Validation save failed!', diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index 01211209f03..ac00b17670b 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -18,6 +18,8 @@ export type ValidationLevel = 'off' | 'moderate' | 'strict'; export const enum ValidationActions { ValidatorChanged = 'compass-schema-validation/validation/ValidatorChanged', ValidationCanceled = 'compass-schema-validation/validation/ValidationCanceled', + ValidationSaveStarted = 'compass-schema-validation/validation/ValidationSaveStarted', + ValidationSaveEnded = 'compass-schema-validation/validation/ValidationSaveEnded', ValidationSaveFailed = 'compass-schema-validation/validation/ValidationSaveFailed', ValidationFetched = 'compass-schema-validation/validation/ValidationFetched', EmptyValidationFetched = 'compass-schema-validation/validation/EmptyValidationFetched', @@ -35,6 +37,14 @@ export interface ValidationCanceledAction { type: ValidationActions.ValidationCanceled; } +interface ValidationSaveStartedAction { + type: ValidationActions.ValidationSaveStarted; +} + +interface ValidationSaveEndedAction { + type: ValidationActions.ValidationSaveEnded; +} + interface ValidationSaveFailedAction { type: ValidationActions.ValidationSaveFailed; error: { message: string }; @@ -86,6 +96,7 @@ export interface Validation { export interface ValidationState extends Validation { isChanged: boolean; + isSaving: boolean; syntaxError: null | { message: string }; error: null | { message: string }; prevValidation?: Validation; @@ -99,6 +110,7 @@ export const INITIAL_STATE: ValidationState = { validationAction: 'error', validationLevel: 'strict', isChanged: false, + isSaving: false, syntaxError: null, error: null, }; @@ -165,6 +177,31 @@ export default function reducer( }; } + if ( + isAction( + action, + ValidationActions.ValidationSaveStarted + ) + ) { + return { + ...state, + error: null, + isSaving: true, + }; + } + + if ( + isAction( + action, + ValidationActions.ValidationSaveEnded + ) + ) { + return { + ...state, + isSaving: false, + }; + } + if ( isAction( action, @@ -369,6 +406,14 @@ export const emptyValidationFetched = ({ validationTemplate, }); +export const validationSaveStarted = (): ValidationSaveStartedAction => ({ + type: ValidationActions.ValidationSaveStarted, +}); + +export const validationSaveEnded = (): ValidationSaveEndedAction => ({ + type: ValidationActions.ValidationSaveEnded, +}); + export const validationCanceled = (): ValidationCanceledAction => ({ type: ValidationActions.ValidationCanceled, }); @@ -446,6 +491,9 @@ export const saveValidation = ( validation_level: validation.validationLevel, }; track('Schema Validation Updated', trackEvent, connectionInfoRef.current); + + dispatch(validationSaveStarted()); + try { await dataService.updateCollection( `${namespace.database}.${namespace.collection}`, @@ -463,6 +511,8 @@ export const saveValidation = ( dispatch(disableEditRules()); } catch (error) { dispatch(validationSaveFailed(error as Error)); + } finally { + dispatch(validationSaveEnded()); } }; }; diff --git a/packages/compass-schema-validation/src/stores/store.spec.ts b/packages/compass-schema-validation/src/stores/store.spec.ts index cbda112d886..1357dfec014 100644 --- a/packages/compass-schema-validation/src/stores/store.spec.ts +++ b/packages/compass-schema-validation/src/stores/store.spec.ts @@ -217,6 +217,7 @@ describe('Schema Validation Store', function () { error: null, syntaxError: null, isChanged: false, + isSaving: false, prevValidation: { validator, validationAction: 'warn', @@ -242,6 +243,7 @@ describe('Schema Validation Store', function () { error: { message: 'Validation fetch failed!' }, syntaxError: null, isChanged: true, + isSaving: false, prevValidation: { validator: '{}', validationAction: 'error',