-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move rulesGeneration into separate module + align errors
- Loading branch information
1 parent
4d326f7
commit a48c7fa
Showing
5 changed files
with
290 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
223 changes: 223 additions & 0 deletions
223
packages/compass-schema-validation/src/modules/rules-generation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
import type { SchemaValidationThunkAction } from '.'; | ||
import { zeroStateChanged } from './zero-state'; | ||
import { enableEditRules } from './edit-mode'; | ||
import { analyzeSchema } from '@mongodb-js/compass-schema'; | ||
import type { MongoError } from 'mongodb'; | ||
import type { Action, AnyAction, Reducer } from 'redux'; | ||
import { validationLevelChanged, validatorChanged } from './validation'; | ||
|
||
export function isAction<A extends AnyAction>( | ||
action: AnyAction, | ||
type: A['type'] | ||
): action is A { | ||
return action.type === type; | ||
} | ||
|
||
export type ValidationServerAction = 'error' | 'warn'; | ||
export type ValidationLevel = 'off' | 'moderate' | 'strict'; | ||
|
||
const ERROR_CODE_MAX_TIME_MS_EXPIRED = 50; | ||
|
||
const SAMPLE_SIZE = 1000; | ||
const ABORT_MESSAGE = 'Operation cancelled'; | ||
|
||
export const enum RulesGenerationActions { | ||
generationStarted = 'schema-validation/rules-generation/generationStarted', | ||
generationFailed = 'schema-validation/rules-generation/generationFailed', | ||
generationFinished = 'schema-validation/rules-generation/generationFinished', | ||
generationErrorCleared = 'schema-validation/rules-generation/generationErrorCleared', | ||
} | ||
|
||
export type RulesGenerationStarted = { | ||
type: RulesGenerationActions.generationStarted; | ||
}; | ||
|
||
export type RulesGenerationFailed = { | ||
type: RulesGenerationActions.generationFailed; | ||
error: Error; | ||
}; | ||
|
||
export type RulesGenerationErrorCleared = { | ||
type: RulesGenerationActions.generationErrorCleared; | ||
}; | ||
|
||
export type RulesGenerationFinished = { | ||
type: RulesGenerationActions.generationFinished; | ||
}; | ||
|
||
export type RulesGenerationError = { | ||
errorMessage: string; | ||
errorType: 'timeout' | 'highComplexity' | 'general'; | ||
}; | ||
|
||
export interface RulesGenerationState { | ||
isInProgress: boolean; | ||
error?: RulesGenerationError; | ||
} | ||
|
||
/** | ||
* The initial state. | ||
*/ | ||
export const INITIAL_STATE: RulesGenerationState = { | ||
isInProgress: false, | ||
}; | ||
|
||
function getErrorDetails(error: Error): RulesGenerationError { | ||
const errorCode = (error as MongoError).code; | ||
const errorMessage = error.message || 'Unknown error'; | ||
let errorType: RulesGenerationError['errorType'] = 'general'; | ||
if (errorCode === ERROR_CODE_MAX_TIME_MS_EXPIRED) { | ||
errorType = 'timeout'; | ||
} else if (error.message.includes('Schema analysis aborted: Fields count')) { | ||
errorType = 'highComplexity'; | ||
} | ||
|
||
return { | ||
errorType, | ||
errorMessage, | ||
}; | ||
} | ||
|
||
/** | ||
* Reducer function for handle state changes to status. | ||
*/ | ||
export const rulesGenerationReducer: Reducer<RulesGenerationState, Action> = ( | ||
state = INITIAL_STATE, | ||
action | ||
) => { | ||
if ( | ||
isAction<RulesGenerationStarted>( | ||
action, | ||
RulesGenerationActions.generationStarted | ||
) | ||
) { | ||
return { | ||
...state, | ||
isInProgress: true, | ||
error: undefined, | ||
}; | ||
} | ||
|
||
if ( | ||
isAction<RulesGenerationFinished>( | ||
action, | ||
RulesGenerationActions.generationFinished | ||
) | ||
) { | ||
return { | ||
...state, | ||
isInProgress: false, | ||
}; | ||
} | ||
|
||
if ( | ||
isAction<RulesGenerationFailed>( | ||
action, | ||
RulesGenerationActions.generationFailed | ||
) | ||
) { | ||
return { | ||
...state, | ||
isInProgress: false, | ||
error: getErrorDetails(action.error), | ||
}; | ||
} | ||
|
||
if ( | ||
isAction<RulesGenerationErrorCleared>( | ||
action, | ||
RulesGenerationActions.generationErrorCleared | ||
) | ||
) { | ||
return { | ||
...state, | ||
error: undefined, | ||
}; | ||
} | ||
|
||
return state; | ||
}; | ||
|
||
export const clearRulesGenerationError = | ||
(): SchemaValidationThunkAction<RulesGenerationErrorCleared> => { | ||
return (dispatch) => | ||
dispatch({ type: RulesGenerationActions.generationErrorCleared }); | ||
}; | ||
|
||
export const stopRulesGeneration = (): SchemaValidationThunkAction<void> => { | ||
return (dispatch, getState, { rulesGenerationAbortControllerRef }) => { | ||
if (!rulesGenerationAbortControllerRef.current) return; | ||
rulesGenerationAbortControllerRef.current?.abort(ABORT_MESSAGE); | ||
}; | ||
}; | ||
|
||
/** | ||
* Get $jsonSchema from schema analysis | ||
* @returns | ||
*/ | ||
export const generateValidationRules = (): SchemaValidationThunkAction< | ||
Promise<void> | ||
> => { | ||
return async ( | ||
dispatch, | ||
getState, | ||
{ dataService, logger, preferences, rulesGenerationAbortControllerRef } | ||
) => { | ||
dispatch({ type: RulesGenerationActions.generationStarted }); | ||
|
||
rulesGenerationAbortControllerRef.current = new AbortController(); | ||
const abortSignal = rulesGenerationAbortControllerRef.current.signal; | ||
|
||
const { namespace } = getState(); | ||
const { maxTimeMS } = preferences.getPreferences(); | ||
|
||
try { | ||
const samplingOptions = { | ||
query: {}, | ||
size: SAMPLE_SIZE, | ||
fields: undefined, | ||
}; | ||
const driverOptions = { | ||
maxTimeMS, | ||
}; | ||
const schemaAccessor = await analyzeSchema( | ||
dataService, | ||
abortSignal, | ||
namespace.toString(), | ||
samplingOptions, | ||
driverOptions, | ||
logger, | ||
preferences | ||
); | ||
if (abortSignal?.aborted) { | ||
throw new Error(ABORT_MESSAGE); | ||
} | ||
|
||
const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema({ | ||
signal: abortSignal, | ||
}); | ||
if (abortSignal?.aborted) { | ||
throw new Error(ABORT_MESSAGE); | ||
} | ||
const validator = JSON.stringify( | ||
{ $jsonSchema: jsonSchema }, | ||
undefined, | ||
2 | ||
); | ||
dispatch(validationLevelChanged('moderate')); | ||
dispatch(validatorChanged(validator)); | ||
dispatch(enableEditRules()); | ||
dispatch({ type: RulesGenerationActions.generationFinished }); | ||
dispatch(zeroStateChanged(false)); | ||
} catch (error) { | ||
if (abortSignal.aborted) { | ||
dispatch({ type: RulesGenerationActions.generationFinished }); | ||
return; | ||
} | ||
dispatch({ | ||
type: RulesGenerationActions.generationFailed, | ||
error, | ||
}); | ||
} | ||
}; | ||
}; |
Oops, something went wrong.