From 721f272e944e19738484d1be4f03a54fade7b5d0 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 6 Mar 2025 15:58:07 +0100 Subject: [PATCH 01/13] rules generation wip --- README.md | 1 + package-lock.json | 223 ++++++++++++++++++ .../compass-schema-validation/package.json | 1 + .../src/components/validation-states.tsx | 46 +++- .../src/components/zero-graphic.tsx | 10 +- .../compass-schema-validation/src/index.ts | 6 +- .../src/modules/index.ts | 16 +- .../src/modules/validation.ts | 53 +++++ .../src/stores/store.ts | 12 +- packages/compass-schema/package.json | 1 + .../src/components/schema-toolbar.tsx | 2 +- .../src/stores/schema-analysis-reducer.ts | 2 +- .../src/stores/schema-export-reducer.ts | 2 +- packages/schema-analysis/.depcheckrc | 8 + packages/schema-analysis/.eslintignore | 2 + packages/schema-analysis/.eslintrc.js | 8 + packages/schema-analysis/.mocharc.js | 1 + packages/schema-analysis/.prettierignore | 3 + packages/schema-analysis/.prettierrc.json | 1 + packages/schema-analysis/package.json | 72 ++++++ packages/schema-analysis/src/index.spec.tsx | 0 packages/schema-analysis/src/index.ts | 1 + .../src}/schema-analysis.spec.ts | 0 .../src}/schema-analysis.ts | 12 +- packages/schema-analysis/tsconfig-lint.json | 5 + packages/schema-analysis/tsconfig.json | 8 + 26 files changed, 460 insertions(+), 36 deletions(-) create mode 100644 packages/schema-analysis/.depcheckrc create mode 100644 packages/schema-analysis/.eslintignore create mode 100644 packages/schema-analysis/.eslintrc.js create mode 100644 packages/schema-analysis/.mocharc.js create mode 100644 packages/schema-analysis/.prettierignore create mode 100644 packages/schema-analysis/.prettierrc.json create mode 100644 packages/schema-analysis/package.json create mode 100644 packages/schema-analysis/src/index.spec.tsx create mode 100644 packages/schema-analysis/src/index.ts rename packages/{compass-schema/src/modules => schema-analysis/src}/schema-analysis.spec.ts (100%) rename packages/{compass-schema/src/modules => schema-analysis/src}/schema-analysis.ts (93%) create mode 100644 packages/schema-analysis/tsconfig-lint.json create mode 100644 packages/schema-analysis/tsconfig.json diff --git a/README.md b/README.md index b05d0667a2c..27a57f62fc8 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Is there anything else you’d like to see in Compass? Let us know by submitting - [**@mongodb-js/compass-maybe-protect-connection-string**](packages/compass-maybe-protect-connection-string): Utility for protecting connection strings if requested - [**@mongodb-js/compass-settings**](packages/compass-settings): Settings for compass - [**@mongodb-js/compass-telemetry**](packages/compass-telemetry): Compass telemetry +- [**@mongodb-js/compass-schema-analysis**](packages/compass-schema-analysis): Compass schema analysis - [**@mongodb-js/compass-test-server**](packages/compass-test-server): Wrapper around mongodb-runner to manage test servers for Compass - [**@mongodb-js/compass-user-data**](packages/compass-user-data): undefined - [**@mongodb-js/compass-utils**](packages/compass-utils): Utilities for MongoDB Compass Development diff --git a/package-lock.json b/package-lock.json index 235b0d7bbf8..f8c11e621c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7931,6 +7931,10 @@ "resolved": "packages/compass-schema", "link": true }, + "node_modules/@mongodb-js/compass-schema-analysis": { + "resolved": "packages/schema-analysis", + "link": true + }, "node_modules/@mongodb-js/compass-schema-validation": { "resolved": "packages/compass-schema-validation", "link": true @@ -46403,6 +46407,7 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { + "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/eslint-config-compass": "^1.3.5", "@mongodb-js/mocha-config-compass": "^1.6.5", "@mongodb-js/my-queries-storage": "^0.22.5", @@ -46440,6 +46445,7 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", + "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "bson": "^6.10.3", @@ -50481,6 +50487,120 @@ "node": ">=0.10.0" } }, + "packages/schema-analysis": { + "name": "@mongodb-js/compass-schema-analysis", + "version": "1.0.0", + "license": "SSPL", + "devDependencies": { + "@mongodb-js/compass-connections": "^1.50.3", + "@mongodb-js/eslint-config-compass": "^1.3.3", + "@mongodb-js/mocha-config-compass": "^1.6.3", + "@mongodb-js/prettier-config-compass": "^1.2.3", + "@mongodb-js/testing-library-compass": "^1.2.3", + "@mongodb-js/tsconfig-compass": "^1.2.3", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4" + } + }, + "packages/schema-analysis/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/schema-analysis/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "packages/schema-analysis/node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "packages/schema-analysis/node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "packages/schema-analysis/node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "packages/schema-analysis/node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "packages/schema-analysis/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "packages/schema-analysis/node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/ssh-tunnel": { "name": "@mongodb-js/ssh-tunnel", "version": "2.3.3", @@ -57909,6 +58029,7 @@ "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", "@mongodb-js/compass-query-bar": "^8.51.5", + "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/connection-storage": "^0.26.5", "@mongodb-js/eslint-config-compass": "^1.3.5", @@ -57977,6 +58098,107 @@ } } }, + "@mongodb-js/compass-schema-analysis": { + "version": "file:packages/schema-analysis", + "requires": { + "@mongodb-js/compass-connections": "^1.50.3", + "@mongodb-js/eslint-config-compass": "^1.3.3", + "@mongodb-js/mocha-config-compass": "^1.6.3", + "@mongodb-js/prettier-config-compass": "^1.2.3", + "@mongodb-js/testing-library-compass": "^1.2.3", + "@mongodb-js/tsconfig-compass": "^1.2.3", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1" + } + }, + "@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + }, + "dependencies": { + "type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true + } + } + }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + } + } + } + }, "@mongodb-js/compass-schema-validation": { "version": "file:packages/compass-schema-validation", "requires": { @@ -57988,6 +58210,7 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", + "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "@mongodb-js/eslint-config-compass": "^1.3.5", diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index d16513c5dfc..e49495f12f9 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -79,6 +79,7 @@ "@mongodb-js/compass-logging": "^1.6.5", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", + "@mongodb-js/compass-schema-analysis": "^1.0.0", "bson": "^6.10.3", "compass-preferences-model": "^2.33.5", "hadron-app-registry": "^9.4.5", diff --git a/packages/compass-schema-validation/src/components/validation-states.tsx b/packages/compass-schema-validation/src/components/validation-states.tsx index e13d9d8375c..b905e3ac3d5 100644 --- a/packages/compass-schema-validation/src/components/validation-states.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.tsx @@ -10,7 +10,7 @@ import { spacing, } from '@mongodb-js/compass-components'; import { connect } from 'react-redux'; -import { usePreference } from 'compass-preferences-model/provider'; +import { usePreferences } from 'compass-preferences-model/provider'; import { changeZeroState } from '../modules/zero-state'; import type { RootState } from '../modules'; import ValidationEditor from './validation-editor'; @@ -19,6 +19,10 @@ import { ZeroGraphic } from './zero-graphic'; const validationStatesStyles = css({ padding: spacing[3] }); const contentContainerStyles = css({ height: '100%' }); +const zeroStateButtonsStyles = css({ + display: 'flex', + gap: spacing[400], +}); /** * Warnings for the banner. @@ -110,7 +114,10 @@ export function ValidationStates({ changeZeroState, editMode, }: ValidationStatesProps) { - const readOnly = usePreference('readOnly'); + const { readOnly, enableExportSchema } = usePreferences([ + 'readOnly', + 'enableExportSchema', + ]); const isEditable = !editMode.collectionReadOnly && @@ -130,18 +137,31 @@ export function ValidationStates({ {isZeroState ? ( changeZeroState(false)} - variant={ButtonVariant.Primary} - size="small" - > - Add Rule - +
+ {enableExportSchema && ( + + )} + +
} callToActionLink={ diff --git a/packages/compass-schema-validation/src/components/zero-graphic.tsx b/packages/compass-schema-validation/src/components/zero-graphic.tsx index c5f1592a4f4..171ef6f40f0 100644 --- a/packages/compass-schema-validation/src/components/zero-graphic.tsx +++ b/packages/compass-schema-validation/src/components/zero-graphic.tsx @@ -1,5 +1,12 @@ import React, { useMemo } from 'react'; -import { palette, useDarkMode } from '@mongodb-js/compass-components'; +import { + css, + palette, + spacing, + useDarkMode, +} from '@mongodb-js/compass-components'; + +const svgStyles = css({ marginLeft: spacing[300] }); const ZeroGraphic: React.FunctionComponent = () => { const darkMode = useDarkMode(); @@ -16,6 +23,7 @@ const ZeroGraphic: React.FunctionComponent = () => { viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg" + className={svgStyles} > , connectionInfoRef: connectionInfoRefLocator, instance: mongoDBInstanceLocator, diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index dcbf69bd2f1..eabf4986217 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -25,7 +25,7 @@ import type { ThunkAction } from 'redux-thunk'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { ConnectionInfoRef, - DataService, + DataService as OriginalDataService, } from '@mongodb-js/compass-connections/provider'; import type AppRegistry from 'hadron-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; @@ -60,11 +60,17 @@ export type RootAction = | EditModeAction | ResetAction; +export type DataService = Pick< + OriginalDataService, + | 'aggregate' + | 'collectionInfo' + | 'updateCollection' + | 'sample' + | 'isCancelError' +>; + export type SchemaValidationExtraArgs = { - dataService: Pick< - DataService, - 'aggregate' | 'collectionInfo' | 'updateCollection' - >; + dataService: DataService; connectionInfoRef: ConnectionInfoRef; preferences: PreferencesAccess; globalAppRegistry: AppRegistry; diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index c2059e38ba7..b082709cc5a 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -8,6 +8,7 @@ import { isLoadedChanged } from './is-loaded'; import { isEqual, pick } from 'lodash'; import type { ThunkDispatch } from 'redux-thunk'; import { disableEditRules } from './edit-mode'; +import { analyzeSchema } from '@mongodb-js/compass-schema-analysis'; export type ValidationServerAction = 'error' | 'warn'; export type ValidationLevel = 'off' | 'moderate' | 'strict'; @@ -83,6 +84,19 @@ interface SyntaxErrorOccurredAction { syntaxError: null | { message: string }; } +export const RULES_GENERATION_STARTED = + `${PREFIX}/RULES_GENERATION_STARTED` as const; +interface RulesGenerationStartedAction { + type: typeof RULES_GENERATION_STARTED; +} + +export const RULES_GENERATION_FAILED = + `${PREFIX}/RULES_GENERATION_FAILED` as const; +interface RulesGenerationFailedAction { + type: typeof RULES_GENERATION_STARTED; + syntaxError: null | { message: string }; +} + export type ValidationAction = | ValidatorChangedAction | ValidationCanceledAction @@ -110,6 +124,8 @@ export interface ValidationState extends Validation { syntaxError: null | { message: string }; error: null | { message: string }; prevValidation?: Validation; + rulesGenerationStatus?: 'in-progress' | 'failed'; + rulesGenerationErrorMessage?: string; } /** @@ -536,3 +552,40 @@ export const activateValidation = (): SchemaValidationThunkAction => { dispatch(fetchValidation(namespace)); }; }; + +/** + * Get $jsonSchema from schema analysis + * @returns + */ +export const generateValidationRules = (): SchemaValidationThunkAction< + Promise +> => { + return async (dispatch, getState, { dataService, logger, preferences }) => { + dispatch({ type: RULES_GENERATION_STARTED }); + + try { + ///// TODO + const samplingOptions = {}; + const driverOptions = {}; + const abortSignal = new AbortSignal(); + const namespace = ''; + const schemaAccessor = await analyzeSchema( + dataService, + abortSignal, + namespace, + samplingOptions, + driverOptions, + logger, + preferences + ); + + const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema(); + const validator = JSON.stringify(jsonSchema, undefined, 2); + + dispatch(validationLevelChanged('moderate')); + dispatch(validatorChanged(validator)); + } catch (error) { + dispatch({ type: RULES_GENERATION_FAILED }); + } + }; +}; diff --git a/packages/compass-schema-validation/src/stores/store.ts b/packages/compass-schema-validation/src/stores/store.ts index 138e8655726..402f288817a 100644 --- a/packages/compass-schema-validation/src/stores/store.ts +++ b/packages/compass-schema-validation/src/stores/store.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; -import type { RootState } from '../modules'; +import type { DataService, RootState } from '../modules'; import reducer, { INITIAL_STATE } from '../modules'; import toNS from 'mongodb-ns'; import { activateValidation } from '../modules/validation'; @@ -8,10 +8,7 @@ import { editModeChanged } from '../modules/edit-mode'; import semver from 'semver'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { ActivateHelpers, AppRegistry } from 'hadron-app-registry'; -import type { - ConnectionInfoRef, - DataService, -} from '@mongodb-js/compass-connections/provider'; +import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; @@ -25,10 +22,7 @@ const MIN_VERSION = '3.2.0'; export type SchemaValidationServices = { globalAppRegistry: AppRegistry; - dataService: Pick< - DataService, - 'aggregate' | 'collectionInfo' | 'updateCollection' - >; + dataService: DataService; connectionInfoRef: ConnectionInfoRef; preferences: PreferencesAccess; instance: MongoDBInstance; diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 0620620895a..6f1662a5201 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -54,6 +54,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.5", "@mongodb-js/testing-library-compass": "^1.2.5", "@mongodb-js/tsconfig-compass": "^1.2.5", + "@mongodb-js/compass-schema-analysis": "^1.0.0", "@types/chai": "^4.2.21", "@types/leaflet": "^1.9.8", "@types/leaflet-draw": "^1.0.11", diff --git a/packages/compass-schema/src/components/schema-toolbar.tsx b/packages/compass-schema/src/components/schema-toolbar.tsx index 0fca62f0ef2..859a0ad1402 100644 --- a/packages/compass-schema/src/components/schema-toolbar.tsx +++ b/packages/compass-schema/src/components/schema-toolbar.tsx @@ -21,7 +21,7 @@ import { type SchemaAnalysisError, analysisErrorDismissed, } from '../stores/schema-analysis-reducer'; -import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '../modules/schema-analysis'; +import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '@mongodb-js/compass-schema-analysis'; import type { RootState } from '../stores/store'; import { openExportSchema } from '../stores/schema-export-reducer'; diff --git a/packages/compass-schema/src/stores/schema-analysis-reducer.ts b/packages/compass-schema/src/stores/schema-analysis-reducer.ts index 564afc2d4f7..210dcecafa4 100644 --- a/packages/compass-schema/src/stores/schema-analysis-reducer.ts +++ b/packages/compass-schema/src/stores/schema-analysis-reducer.ts @@ -13,7 +13,7 @@ import { addLayer, generateGeoQuery } from '../modules/geo'; import { analyzeSchema, calculateSchemaMetadata, -} from '../modules/schema-analysis'; +} from '@mongodb-js/compass-schema-analysis'; import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model/provider'; import type { Circle, Layer, LayerGroup, Polygon } from 'leaflet'; import { mongoLogId } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-schema/src/stores/schema-export-reducer.ts b/packages/compass-schema/src/stores/schema-export-reducer.ts index 8ed2c050a6d..e24af488253 100644 --- a/packages/compass-schema/src/stores/schema-export-reducer.ts +++ b/packages/compass-schema/src/stores/schema-export-reducer.ts @@ -11,7 +11,7 @@ import { openToast } from '@mongodb-js/compass-components'; import type { SchemaThunkAction } from './store'; import { isAction } from '../utils'; -import { calculateSchemaMetadata } from '../modules/schema-analysis'; +import { calculateSchemaMetadata } from '@mongodb-js/compass-schema-analysis'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; diff --git a/packages/schema-analysis/.depcheckrc b/packages/schema-analysis/.depcheckrc new file mode 100644 index 00000000000..ab0ef21b740 --- /dev/null +++ b/packages/schema-analysis/.depcheckrc @@ -0,0 +1,8 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' +ignore-patterns: + - 'dist' diff --git a/packages/schema-analysis/.eslintignore b/packages/schema-analysis/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/schema-analysis/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/schema-analysis/.eslintrc.js b/packages/schema-analysis/.eslintrc.js new file mode 100644 index 00000000000..e4cf824b6ac --- /dev/null +++ b/packages/schema-analysis/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/schema-analysis/.mocharc.js b/packages/schema-analysis/.mocharc.js new file mode 100644 index 00000000000..7e473d17b76 --- /dev/null +++ b/packages/schema-analysis/.mocharc.js @@ -0,0 +1 @@ +module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/schema-analysis/.prettierignore b/packages/schema-analysis/.prettierignore new file mode 100644 index 00000000000..4d28df6603a --- /dev/null +++ b/packages/schema-analysis/.prettierignore @@ -0,0 +1,3 @@ +.nyc_output +dist +coverage diff --git a/packages/schema-analysis/.prettierrc.json b/packages/schema-analysis/.prettierrc.json new file mode 100644 index 00000000000..18853d1532e --- /dev/null +++ b/packages/schema-analysis/.prettierrc.json @@ -0,0 +1 @@ +"@mongodb-js/prettier-config-compass" diff --git a/packages/schema-analysis/package.json b/packages/schema-analysis/package.json new file mode 100644 index 00000000000..63c5f61c284 --- /dev/null +++ b/packages/schema-analysis/package.json @@ -0,0 +1,72 @@ +{ + "name": "@mongodb-js/compass-schema-analysis", + "description": "Schema analysis", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/mongodb-js/compass.git" + }, + "files": [ + "dist" + ], + "license": "SSPL", + "main": "dist/index.js", + "compass:main": "src/index.ts", + "exports": { + "import": "./dist/.esm-wrapper.mjs", + "require": "./dist/index.js" + }, + "compass:exports": { + ".": "./src/index.ts" + }, + "types": "./dist/index.d.ts", + "scripts": { + "bootstrap": "npm run compile", + "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint", + "prettier": "prettier", + "lint": "npm run eslint . && npm run prettier -- --check .", + "depcheck": "compass-scripts check-peer-deps && depcheck", + "check": "npm run typecheck && npm run lint && npm run depcheck", + "check-ci": "npm run check", + "test": "mocha", + "test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", + "test-watch": "npm run test -- --watch", + "test-ci": "npm run test-cov", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "devDependencies": { + "@mongodb-js/compass-connections": "^1.50.3", + "@mongodb-js/eslint-config-compass": "^1.3.3", + "@mongodb-js/mocha-config-compass": "^1.6.3", + "@mongodb-js/prettier-config-compass": "^1.2.3", + "@mongodb-js/testing-library-compass": "^1.2.3", + "@mongodb-js/tsconfig-compass": "^1.2.3", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4" + } +} diff --git a/packages/schema-analysis/src/index.spec.tsx b/packages/schema-analysis/src/index.spec.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/schema-analysis/src/index.ts b/packages/schema-analysis/src/index.ts new file mode 100644 index 00000000000..3c5b8647e60 --- /dev/null +++ b/packages/schema-analysis/src/index.ts @@ -0,0 +1 @@ +export * from './schema-analysis'; diff --git a/packages/compass-schema/src/modules/schema-analysis.spec.ts b/packages/schema-analysis/src/schema-analysis.spec.ts similarity index 100% rename from packages/compass-schema/src/modules/schema-analysis.spec.ts rename to packages/schema-analysis/src/schema-analysis.spec.ts diff --git a/packages/compass-schema/src/modules/schema-analysis.ts b/packages/schema-analysis/src/schema-analysis.ts similarity index 93% rename from packages/compass-schema/src/modules/schema-analysis.ts rename to packages/schema-analysis/src/schema-analysis.ts index 6a1c3bc5012..f2bae25ea96 100644 --- a/packages/compass-schema/src/modules/schema-analysis.ts +++ b/packages/schema-analysis/src/schema-analysis.ts @@ -10,10 +10,14 @@ import type { PrimitiveSchemaType, SchemaParseOptions, } from 'mongodb-schema'; -import type { DataService } from '../stores/store'; +import type { + DataService as OriginalDataService, +} from '@mongodb-js/compass-connections/provider'; import type { Logger } from '@mongodb-js/compass-logging'; import type { PreferencesAccess } from 'compass-preferences-model'; +type DataService = Pick; + export const DISTINCT_FIELDS_ABORT_THRESHOLD = 1000; // hack for driver 3.6 not promoting error codes and @@ -46,7 +50,7 @@ export const analyzeSchema = async ( preferences: PreferencesAccess ): Promise => { try { - log.info(mongoLogId(1001000089), 'Schema', 'Starting schema analysis', { + log.info(mongoLogId(1_001_000_345), 'Schema', 'Starting schema analysis', { ns, }); @@ -73,12 +77,12 @@ export const analyzeSchema = async ( signal: abortSignal, }; const schemaAccessor = await analyzeDocuments(docs, schemaParseOptions); - log.info(mongoLogId(1001000090), 'Schema', 'Schema analysis completed', { + log.info(mongoLogId(1_001_000_346), 'Schema', 'Schema analysis completed', { ns, }); return schemaAccessor; } catch (err: any) { - log.error(mongoLogId(1001000091), 'Schema', 'Schema analysis failed', { + log.error(mongoLogId(1_001_000_347), 'Schema', 'Schema analysis failed', { ns, error: err.message, }); diff --git a/packages/schema-analysis/tsconfig-lint.json b/packages/schema-analysis/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/schema-analysis/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/schema-analysis/tsconfig.json b/packages/schema-analysis/tsconfig.json new file mode 100644 index 00000000000..ecd0a14474a --- /dev/null +++ b/packages/schema-analysis/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} From f623c0365c312197d63cac2364525bb5c6c41be6 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 6 Mar 2025 17:59:58 +0100 Subject: [PATCH 02/13] feat: validation rules generation COMPASS-8859 --- .../src/components/validation-states.tsx | 61 +++++++- .../src/modules/index.ts | 1 + .../src/modules/validation.ts | 140 ++++++++++++++++-- .../src/stores/store.ts | 10 +- 4 files changed, 191 insertions(+), 21 deletions(-) diff --git a/packages/compass-schema-validation/src/components/validation-states.tsx b/packages/compass-schema-validation/src/components/validation-states.tsx index b905e3ac3d5..ce54bf4846e 100644 --- a/packages/compass-schema-validation/src/components/validation-states.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.tsx @@ -3,7 +3,9 @@ import { Banner, Button, ButtonVariant, + CancelLoader, EmptyContent, + ErrorSummary, Link, WarningSummary, css, @@ -16,14 +18,27 @@ import type { RootState } from '../modules'; import ValidationEditor from './validation-editor'; import { SampleDocuments } from './sample-documents'; import { ZeroGraphic } from './zero-graphic'; +import { + clearRulesGenerationError, + generateValidationRules, +} from '../modules/validation'; -const validationStatesStyles = css({ padding: spacing[3] }); +const validationStatesStyles = css({ + padding: spacing[400], + height: '100%', +}); const contentContainerStyles = css({ height: '100%' }); const zeroStateButtonsStyles = css({ display: 'flex', gap: spacing[400], }); +const loaderStyles = css({ + height: '100%', + display: 'flex', + justifyContent: 'center', +}); + /** * Warnings for the banner. */ @@ -50,8 +65,12 @@ const DOC_UPGRADE_REVISION = type ValidationStatesProps = { isZeroState: boolean; + isRulesGenerationInProgress?: boolean; + rulesGenerationError?: string; isLoaded: boolean; changeZeroState: (value: boolean) => void; + generateValidationRules: () => void; + clearRulesGenerationError: () => void; editMode: { collectionTimeSeries?: boolean; collectionReadOnly?: boolean; @@ -108,10 +127,29 @@ function ValidationBanners({ return null; } +const GeneratingScreen: React.FunctionComponent<{ + onCancelClicked: () => void; +}> = ({ onCancelClicked }) => { + return ( +
+ +
+ ); +}; + export function ValidationStates({ isZeroState, + isRulesGenerationInProgress, + rulesGenerationError, isLoaded, changeZeroState, + generateValidationRules, + clearRulesGenerationError, editMode, }: ValidationStatesProps) { const { readOnly, enableExportSchema } = usePreferences([ @@ -131,10 +169,17 @@ export function ValidationStates({ className={validationStatesStyles} data-testid="schema-validation-states" > + {rulesGenerationError && ( + + )} {isLoaded && ( <> - {isZeroState ? ( + {isZeroState && !isRulesGenerationInProgress && ( changeZeroState(false)} + onClick={generateValidationRules} variant={ButtonVariant.Primary} size="small" > @@ -169,7 +214,11 @@ export function ValidationStates({ } /> - ) : ( + )} + {isZeroState && isRulesGenerationInProgress && ( + ({})} /> + )} + {!isZeroState && (
@@ -192,6 +241,8 @@ const mapStateToProps = (state: RootState) => ({ isZeroState: state.isZeroState, isLoaded: state.isLoaded, editMode: state.editMode, + isRulesGenerationInProgress: state.validation.isRulesGenerationInProgress, + rulesGenerationError: state.validation.rulesGenerationError, }); /** @@ -199,4 +250,6 @@ const mapStateToProps = (state: RootState) => ({ */ export default connect(mapStateToProps, { changeZeroState, + generateValidationRules, + clearRulesGenerationError, })(ValidationStates); diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index eabf4986217..fc7a1d4b8ca 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -77,6 +77,7 @@ export type SchemaValidationExtraArgs = { workspaces: WorkspacesService; logger: Logger; track: TrackFunction; + rulesGenerationAbortControllerRef: { current?: AbortController }; }; export type SchemaValidationThunkAction< diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index b082709cc5a..3c4a00b003b 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -7,12 +7,14 @@ import { zeroStateChanged } from './zero-state'; import { isLoadedChanged } from './is-loaded'; import { isEqual, pick } from 'lodash'; import type { ThunkDispatch } from 'redux-thunk'; -import { disableEditRules } from './edit-mode'; +import { disableEditRules, enableEditRules } from './edit-mode'; import { analyzeSchema } from '@mongodb-js/compass-schema-analysis'; export type ValidationServerAction = 'error' | 'warn'; export type ValidationLevel = 'off' | 'moderate' | 'strict'; +const SAMPLE_SIZE = 1000; + /** * The module action prefix. */ @@ -93,8 +95,20 @@ interface RulesGenerationStartedAction { export const RULES_GENERATION_FAILED = `${PREFIX}/RULES_GENERATION_FAILED` as const; interface RulesGenerationFailedAction { - type: typeof RULES_GENERATION_STARTED; - syntaxError: null | { message: string }; + type: typeof RULES_GENERATION_FAILED; + message: string; +} + +export const RULES_GENERATION_CLEAR_ERROR = + `${PREFIX}/RULES_GENERATION_CLEAR_ERROR` as const; +interface RulesGenerationClearErrorAction { + type: typeof RULES_GENERATION_CLEAR_ERROR; +} + +export const RULES_GENERATION_FINISHED = + `${PREFIX}/RULES_GENERATION_FINISHED` as const; +interface RulesGenerationFinishedAction { + type: typeof RULES_GENERATION_FINISHED; } export type ValidationAction = @@ -104,7 +118,11 @@ export type ValidationAction = | ValidationFetchedAction | ValidationActionChangedAction | ValidationLevelChangedAction - | SyntaxErrorOccurredAction; + | SyntaxErrorOccurredAction + | RulesGenerationStartedAction + | RulesGenerationFinishedAction + | RulesGenerationFailedAction + | RulesGenerationClearErrorAction; export interface Validation { validator: string; @@ -124,8 +142,8 @@ export interface ValidationState extends Validation { syntaxError: null | { message: string }; error: null | { message: string }; prevValidation?: Validation; - rulesGenerationStatus?: 'in-progress' | 'failed'; - rulesGenerationErrorMessage?: string; + isRulesGenerationInProgress?: boolean; + rulesGenerationError?: string; } /** @@ -205,6 +223,34 @@ const setSyntaxError = ( syntaxError: action.syntaxError, }); +const startRulesGeneration = (state: ValidationState): ValidationState => ({ + ...state, + isRulesGenerationInProgress: true, + rulesGenerationError: undefined, +}); + +const finishRulesGeneration = (state: ValidationState): ValidationState => ({ + ...state, + isRulesGenerationInProgress: undefined, + rulesGenerationError: undefined, +}); + +const markRulesGenerationFailure = ( + state: ValidationState, + action: RulesGenerationFailedAction +): ValidationState => ({ + ...state, + isRulesGenerationInProgress: undefined, + rulesGenerationError: action.message, +}); + +const unsetRulesGenerationError = ( + state: ValidationState +): ValidationState => ({ + ...state, + rulesGenerationError: undefined, +}); + /** * Set validation. */ @@ -307,6 +353,10 @@ const MAPPINGS: { [VALIDATION_ACTION_CHANGED]: changeValidationAction, [VALIDATION_LEVEL_CHANGED]: changeValidationLevel, [SYNTAX_ERROR_OCCURRED]: setSyntaxError, + [RULES_GENERATION_STARTED]: startRulesGeneration, + [RULES_GENERATION_FINISHED]: finishRulesGeneration, + [RULES_GENERATION_FAILED]: markRulesGenerationFailure, + [RULES_GENERATION_CLEAR_ERROR]: unsetRulesGenerationError, }; /** @@ -397,6 +447,11 @@ export const syntaxErrorOccurred = ( syntaxError, }); +export const clearRulesGenerationError = + (): RulesGenerationClearErrorAction => ({ + type: RULES_GENERATION_CLEAR_ERROR, + }); + export const fetchValidation = (namespace: { database: string; collection: string; @@ -553,6 +608,28 @@ export const activateValidation = (): SchemaValidationThunkAction => { }; }; +export const stopRulesGeneration = (): SchemaValidationThunkAction => { + return ( + dispatch, + getState, + { rulesGenerationAbortControllerRef, connectionInfoRef, track } + ) => { + if (!rulesGenerationAbortControllerRef.current) return; + // const analysisTime = + // Date.now() - (getState().schemaAnalysis.analysisStartTime ?? 0); + // track( + // 'Schema Analysis Cancelled', + // { + // analysis_time_ms: analysisTime, + // with_filter: Object.entries(query.filter ?? {}).length > 0, + // }, + // connectionInfoRef.current + // ); + + rulesGenerationAbortControllerRef.current?.abort('Analysis cancelled'); + }; +}; + /** * Get $jsonSchema from schema analysis * @returns @@ -560,32 +637,63 @@ export const activateValidation = (): SchemaValidationThunkAction => { export const generateValidationRules = (): SchemaValidationThunkAction< Promise > => { - return async (dispatch, getState, { dataService, logger, preferences }) => { + return async ( + dispatch, + getState, + { dataService, logger, preferences, rulesGenerationAbortControllerRef } + ) => { dispatch({ type: RULES_GENERATION_STARTED }); + console.log('START'); + + rulesGenerationAbortControllerRef.current = new AbortController(); + const abortSignal = rulesGenerationAbortControllerRef.current.signal; + + const { namespace } = getState(); + const { maxTimeMS } = preferences.getPreferences(); try { - ///// TODO - const samplingOptions = {}; - const driverOptions = {}; - const abortSignal = new AbortSignal(); - const namespace = ''; + const samplingOptions = { + query: {}, + size: SAMPLE_SIZE, + fields: undefined, + }; + const driverOptions = { + maxTimeMS, + }; + console.log('ANALYZING'); const schemaAccessor = await analyzeSchema( dataService, abortSignal, - namespace, + namespace.toString(), samplingOptions, driverOptions, logger, preferences ); + if (abortSignal?.aborted) { + throw new Error(abortSignal?.reason || new Error('Operation aborted')); + } - const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema(); + console.log('CONVERTING'); + const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema({ + signal: abortSignal, + }); + if (abortSignal?.aborted) { + throw new Error(abortSignal?.reason || new Error('Operation aborted')); + } + console.log('STRINGIFYING'); const validator = JSON.stringify(jsonSchema, undefined, 2); - + console.log('DONE'); dispatch(validationLevelChanged('moderate')); dispatch(validatorChanged(validator)); + dispatch(enableEditRules()); + dispatch({ type: RULES_GENERATION_FINISHED }); + dispatch(zeroStateChanged(false)); } catch (error) { - dispatch({ type: RULES_GENERATION_FAILED }); + dispatch({ + type: RULES_GENERATION_FAILED, + message: `Rules generation failed: ${(error as Error).message}`, + }); } }; }; diff --git a/packages/compass-schema-validation/src/stores/store.ts b/packages/compass-schema-validation/src/stores/store.ts index 402f288817a..08dfaa05d82 100644 --- a/packages/compass-schema-validation/src/stores/store.ts +++ b/packages/compass-schema-validation/src/stores/store.ts @@ -45,13 +45,21 @@ export function configureStore( | 'connectionInfoRef' > ) { + const rulesGenerationAbortControllerRef = { + current: undefined, + }; return createStore( reducer, { ...INITIAL_STATE, ...state, }, - applyMiddleware(thunk.withExtraArgument(services)) + applyMiddleware( + thunk.withExtraArgument({ + ...services, + rulesGenerationAbortControllerRef, + }) + ) ); } From 13d395b3e1c6da2bd6d081ab20a6abe7a2397a8f Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 6 Mar 2025 18:07:05 +0100 Subject: [PATCH 03/13] cleanup --- .../src/modules/validation.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index 3c4a00b003b..f6a3af18b2f 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -660,7 +660,6 @@ export const generateValidationRules = (): SchemaValidationThunkAction< const driverOptions = { maxTimeMS, }; - console.log('ANALYZING'); const schemaAccessor = await analyzeSchema( dataService, abortSignal, @@ -674,16 +673,17 @@ export const generateValidationRules = (): SchemaValidationThunkAction< throw new Error(abortSignal?.reason || new Error('Operation aborted')); } - console.log('CONVERTING'); const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema({ signal: abortSignal, }); if (abortSignal?.aborted) { throw new Error(abortSignal?.reason || new Error('Operation aborted')); } - console.log('STRINGIFYING'); - const validator = JSON.stringify(jsonSchema, undefined, 2); - console.log('DONE'); + const validator = JSON.stringify( + { $jsonSchema: jsonSchema }, + undefined, + 2 + ); dispatch(validationLevelChanged('moderate')); dispatch(validatorChanged(validator)); dispatch(enableEditRules()); From c1d15f6fd139cb217a8f608d32fa5edd05ae5419 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 6 Mar 2025 18:44:03 +0100 Subject: [PATCH 04/13] fixes --- .../src/components/validation-states.tsx | 14 +++++++-- .../src/modules/validation.ts | 29 ++++++------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/compass-schema-validation/src/components/validation-states.tsx b/packages/compass-schema-validation/src/components/validation-states.tsx index ce54bf4846e..34e9fafb4db 100644 --- a/packages/compass-schema-validation/src/components/validation-states.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.tsx @@ -21,6 +21,7 @@ import { ZeroGraphic } from './zero-graphic'; import { clearRulesGenerationError, generateValidationRules, + stopRulesGeneration, } from '../modules/validation'; const validationStatesStyles = css({ @@ -71,6 +72,7 @@ type ValidationStatesProps = { changeZeroState: (value: boolean) => void; generateValidationRules: () => void; clearRulesGenerationError: () => void; + stopRulesGeneration: () => void; editMode: { collectionTimeSeries?: boolean; collectionReadOnly?: boolean; @@ -150,6 +152,7 @@ export function ValidationStates({ changeZeroState, generateValidationRules, clearRulesGenerationError, + stopRulesGeneration, editMode, }: ValidationStatesProps) { const { readOnly, enableExportSchema } = usePreferences([ @@ -173,7 +176,7 @@ export function ValidationStates({ )} @@ -183,7 +186,11 @@ export function ValidationStates({ {enableExportSchema && ( @@ -216,7 +223,7 @@ export function ValidationStates({ /> )} {isZeroState && isRulesGenerationInProgress && ( - ({})} /> + )} {!isZeroState && (
@@ -252,4 +259,5 @@ export default connect(mapStateToProps, { changeZeroState, generateValidationRules, clearRulesGenerationError, + stopRulesGeneration, })(ValidationStates); diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index f6a3af18b2f..0311e120686 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -14,6 +14,7 @@ export type ValidationServerAction = 'error' | 'warn'; export type ValidationLevel = 'off' | 'moderate' | 'strict'; const SAMPLE_SIZE = 1000; +const ABORT_MESSAGE = 'Operation cancelled'; /** * The module action prefix. @@ -609,24 +610,9 @@ export const activateValidation = (): SchemaValidationThunkAction => { }; export const stopRulesGeneration = (): SchemaValidationThunkAction => { - return ( - dispatch, - getState, - { rulesGenerationAbortControllerRef, connectionInfoRef, track } - ) => { + return (dispatch, getState, { rulesGenerationAbortControllerRef }) => { if (!rulesGenerationAbortControllerRef.current) return; - // const analysisTime = - // Date.now() - (getState().schemaAnalysis.analysisStartTime ?? 0); - // track( - // 'Schema Analysis Cancelled', - // { - // analysis_time_ms: analysisTime, - // with_filter: Object.entries(query.filter ?? {}).length > 0, - // }, - // connectionInfoRef.current - // ); - - rulesGenerationAbortControllerRef.current?.abort('Analysis cancelled'); + rulesGenerationAbortControllerRef.current?.abort(ABORT_MESSAGE); }; }; @@ -643,7 +629,6 @@ export const generateValidationRules = (): SchemaValidationThunkAction< { dataService, logger, preferences, rulesGenerationAbortControllerRef } ) => { dispatch({ type: RULES_GENERATION_STARTED }); - console.log('START'); rulesGenerationAbortControllerRef.current = new AbortController(); const abortSignal = rulesGenerationAbortControllerRef.current.signal; @@ -670,14 +655,14 @@ export const generateValidationRules = (): SchemaValidationThunkAction< preferences ); if (abortSignal?.aborted) { - throw new Error(abortSignal?.reason || new Error('Operation aborted')); + throw new Error(ABORT_MESSAGE); } const jsonSchema = await schemaAccessor?.getMongoDBJsonSchema({ signal: abortSignal, }); if (abortSignal?.aborted) { - throw new Error(abortSignal?.reason || new Error('Operation aborted')); + throw new Error(ABORT_MESSAGE); } const validator = JSON.stringify( { $jsonSchema: jsonSchema }, @@ -690,6 +675,10 @@ export const generateValidationRules = (): SchemaValidationThunkAction< dispatch({ type: RULES_GENERATION_FINISHED }); dispatch(zeroStateChanged(false)); } catch (error) { + if (abortSignal.aborted) { + dispatch({ type: RULES_GENERATION_FINISHED }); + return; + } dispatch({ type: RULES_GENERATION_FAILED, message: `Rules generation failed: ${(error as Error).message}`, From b01d28fdc2b2d54804132b951a41e1bcbe585f58 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 10:27:35 +0100 Subject: [PATCH 05/13] dependencies and rename folder --- package-lock.json | 231 +++++++++++------- .../.depcheckrc | 0 .../.eslintignore | 0 .../.eslintrc.js | 0 .../.mocharc.js | 0 .../.prettierignore | 0 .../.prettierrc.json | 0 .../package.json | 11 +- .../src/index.spec.tsx | 0 .../src/index.ts | 0 .../src/schema-analysis.spec.ts | 0 .../src/schema-analysis.ts | 6 +- .../tsconfig-lint.json | 0 .../tsconfig.json | 0 14 files changed, 150 insertions(+), 98 deletions(-) rename packages/{schema-analysis => compass-schema-analysis}/.depcheckrc (100%) rename packages/{schema-analysis => compass-schema-analysis}/.eslintignore (100%) rename packages/{schema-analysis => compass-schema-analysis}/.eslintrc.js (100%) rename packages/{schema-analysis => compass-schema-analysis}/.mocharc.js (100%) rename packages/{schema-analysis => compass-schema-analysis}/.prettierignore (100%) rename packages/{schema-analysis => compass-schema-analysis}/.prettierrc.json (100%) rename packages/{schema-analysis => compass-schema-analysis}/package.json (88%) rename packages/{schema-analysis => compass-schema-analysis}/src/index.spec.tsx (100%) rename packages/{schema-analysis => compass-schema-analysis}/src/index.ts (100%) rename packages/{schema-analysis => compass-schema-analysis}/src/schema-analysis.spec.ts (100%) rename packages/{schema-analysis => compass-schema-analysis}/src/schema-analysis.ts (97%) rename packages/{schema-analysis => compass-schema-analysis}/tsconfig-lint.json (100%) rename packages/{schema-analysis => compass-schema-analysis}/tsconfig.json (100%) diff --git a/package-lock.json b/package-lock.json index f8c11e621c7..a9b9c855b6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7932,7 +7932,7 @@ "link": true }, "node_modules/@mongodb-js/compass-schema-analysis": { - "resolved": "packages/schema-analysis", + "resolved": "packages/compass-schema-analysis", "link": true }, "node_modules/@mongodb-js/compass-schema-validation": { @@ -46432,6 +46432,128 @@ "xvfb-maybe": "^0.2.1" } }, + "packages/compass-schema-analysis": { + "name": "@mongodb-js/compass-schema-analysis", + "version": "1.0.0", + "license": "SSPL", + "dependencies": { + "@mongodb-js/compass-connections": "^1.50.5", + "@mongodb-js/compass-logging": "^1.6.5", + "bson": "^6.10.3", + "compass-preferences-model": "^2.33.5", + "hadron-document": "^8.8.5", + "mongodb": "^6.14.1", + "mongodb-schema": "^12.4.0" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.3", + "@mongodb-js/mocha-config-compass": "^1.6.3", + "@mongodb-js/prettier-config-compass": "^1.2.3", + "@mongodb-js/testing-library-compass": "^1.2.3", + "@mongodb-js/tsconfig-compass": "^1.2.3", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4" + } + }, + "packages/compass-schema-analysis/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/compass-schema-analysis/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "packages/compass-schema-analysis/node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "packages/compass-schema-analysis/node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "packages/compass-schema-analysis/node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, + "packages/compass-schema-analysis/node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "packages/compass-schema-analysis/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "packages/compass-schema-analysis/node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-schema-validation": { "name": "@mongodb-js/compass-schema-validation", "version": "6.50.5", @@ -50490,7 +50612,15 @@ "packages/schema-analysis": { "name": "@mongodb-js/compass-schema-analysis", "version": "1.0.0", + "extraneous": true, "license": "SSPL", + "dependencies": { + "@mongodb-js/compass-connections": "^1.50.5", + "@mongodb-js/compass-logging": "^1.6.5", + "compass-preferences-model": "^2.33.5", + "mongodb": "^6.14.1", + "mongodb-schema": "^12.4.0" + }, "devDependencies": { "@mongodb-js/compass-connections": "^1.50.3", "@mongodb-js/eslint-config-compass": "^1.3.3", @@ -50512,95 +50642,6 @@ "typescript": "^5.0.4" } }, - "packages/schema-analysis/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "packages/schema-analysis/node_modules/@sinonjs/fake-timers": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", - "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "packages/schema-analysis/node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "packages/schema-analysis/node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "packages/schema-analysis/node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, - "packages/schema-analysis/node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "packages/schema-analysis/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "packages/schema-analysis/node_modules/sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, "packages/ssh-tunnel": { "name": "@mongodb-js/ssh-tunnel", "version": "2.3.3", @@ -58099,9 +58140,10 @@ } }, "@mongodb-js/compass-schema-analysis": { - "version": "file:packages/schema-analysis", + "version": "file:packages/compass-schema-analysis", "requires": { - "@mongodb-js/compass-connections": "^1.50.3", + "@mongodb-js/compass-connections": "^1.50.5", + "@mongodb-js/compass-logging": "^1.6.5", "@mongodb-js/eslint-config-compass": "^1.3.3", "@mongodb-js/mocha-config-compass": "^1.6.3", "@mongodb-js/prettier-config-compass": "^1.2.3", @@ -58110,11 +58152,16 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", + "bson": "^6.10.3", "chai": "^4.3.6", + "compass-preferences-model": "^2.33.5", "depcheck": "^1.4.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", + "hadron-document": "^8.8.5", "mocha": "^10.2.0", + "mongodb": "^6.14.1", + "mongodb-schema": "^12.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", "sinon": "^17.0.1", diff --git a/packages/schema-analysis/.depcheckrc b/packages/compass-schema-analysis/.depcheckrc similarity index 100% rename from packages/schema-analysis/.depcheckrc rename to packages/compass-schema-analysis/.depcheckrc diff --git a/packages/schema-analysis/.eslintignore b/packages/compass-schema-analysis/.eslintignore similarity index 100% rename from packages/schema-analysis/.eslintignore rename to packages/compass-schema-analysis/.eslintignore diff --git a/packages/schema-analysis/.eslintrc.js b/packages/compass-schema-analysis/.eslintrc.js similarity index 100% rename from packages/schema-analysis/.eslintrc.js rename to packages/compass-schema-analysis/.eslintrc.js diff --git a/packages/schema-analysis/.mocharc.js b/packages/compass-schema-analysis/.mocharc.js similarity index 100% rename from packages/schema-analysis/.mocharc.js rename to packages/compass-schema-analysis/.mocharc.js diff --git a/packages/schema-analysis/.prettierignore b/packages/compass-schema-analysis/.prettierignore similarity index 100% rename from packages/schema-analysis/.prettierignore rename to packages/compass-schema-analysis/.prettierignore diff --git a/packages/schema-analysis/.prettierrc.json b/packages/compass-schema-analysis/.prettierrc.json similarity index 100% rename from packages/schema-analysis/.prettierrc.json rename to packages/compass-schema-analysis/.prettierrc.json diff --git a/packages/schema-analysis/package.json b/packages/compass-schema-analysis/package.json similarity index 88% rename from packages/schema-analysis/package.json rename to packages/compass-schema-analysis/package.json index 63c5f61c284..cb854cbe03b 100644 --- a/packages/schema-analysis/package.json +++ b/packages/compass-schema-analysis/package.json @@ -49,16 +49,23 @@ "test-ci": "npm run test-cov", "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, + "dependencies": { + "@mongodb-js/compass-connections": "^1.50.5", + "mongodb": "^6.14.1", + "mongodb-schema": "^12.4.0", + "@mongodb-js/compass-logging": "^1.6.5", + "compass-preferences-model": "^2.33.5" + }, "devDependencies": { - "@mongodb-js/compass-connections": "^1.50.3", "@mongodb-js/eslint-config-compass": "^1.3.3", "@mongodb-js/mocha-config-compass": "^1.6.3", "@mongodb-js/prettier-config-compass": "^1.2.3", - "@mongodb-js/testing-library-compass": "^1.2.3", "@mongodb-js/tsconfig-compass": "^1.2.3", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", + "bson": "^6.10.3", + "hadron-document": "^8.8.5", "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", diff --git a/packages/schema-analysis/src/index.spec.tsx b/packages/compass-schema-analysis/src/index.spec.tsx similarity index 100% rename from packages/schema-analysis/src/index.spec.tsx rename to packages/compass-schema-analysis/src/index.spec.tsx diff --git a/packages/schema-analysis/src/index.ts b/packages/compass-schema-analysis/src/index.ts similarity index 100% rename from packages/schema-analysis/src/index.ts rename to packages/compass-schema-analysis/src/index.ts diff --git a/packages/schema-analysis/src/schema-analysis.spec.ts b/packages/compass-schema-analysis/src/schema-analysis.spec.ts similarity index 100% rename from packages/schema-analysis/src/schema-analysis.spec.ts rename to packages/compass-schema-analysis/src/schema-analysis.spec.ts diff --git a/packages/schema-analysis/src/schema-analysis.ts b/packages/compass-schema-analysis/src/schema-analysis.ts similarity index 97% rename from packages/schema-analysis/src/schema-analysis.ts rename to packages/compass-schema-analysis/src/schema-analysis.ts index f2bae25ea96..4006dd75c1b 100644 --- a/packages/schema-analysis/src/schema-analysis.ts +++ b/packages/compass-schema-analysis/src/schema-analysis.ts @@ -10,9 +10,7 @@ import type { PrimitiveSchemaType, SchemaParseOptions, } from 'mongodb-schema'; -import type { - DataService as OriginalDataService, -} from '@mongodb-js/compass-connections/provider'; +import type { DataService as OriginalDataService } from '@mongodb-js/compass-connections/provider'; import type { Logger } from '@mongodb-js/compass-logging'; import type { PreferencesAccess } from 'compass-preferences-model'; @@ -50,7 +48,7 @@ export const analyzeSchema = async ( preferences: PreferencesAccess ): Promise => { try { - log.info(mongoLogId(1_001_000_345), 'Schema', 'Starting schema analysis', { + log.info(mongoLogId(1_001_000_348), 'Schema', 'Starting schema analysis', { ns, }); diff --git a/packages/schema-analysis/tsconfig-lint.json b/packages/compass-schema-analysis/tsconfig-lint.json similarity index 100% rename from packages/schema-analysis/tsconfig-lint.json rename to packages/compass-schema-analysis/tsconfig-lint.json diff --git a/packages/schema-analysis/tsconfig.json b/packages/compass-schema-analysis/tsconfig.json similarity index 100% rename from packages/schema-analysis/tsconfig.json rename to packages/compass-schema-analysis/tsconfig.json From 4d326f7406acadb65bda14fac698c9e30d3af719 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 12:41:06 +0100 Subject: [PATCH 06/13] return schema-analysis --- README.md | 1 - package-lock.json | 205 +----------------- packages/compass-schema-analysis/.depcheckrc | 8 - .../compass-schema-analysis/.eslintignore | 2 - packages/compass-schema-analysis/.eslintrc.js | 8 - packages/compass-schema-analysis/.mocharc.js | 1 - .../compass-schema-analysis/.prettierignore | 3 - .../compass-schema-analysis/.prettierrc.json | 1 - packages/compass-schema-analysis/package.json | 79 ------- .../src/index.spec.tsx | 0 packages/compass-schema-analysis/src/index.ts | 1 - .../tsconfig-lint.json | 5 - .../compass-schema-analysis/tsconfig.json | 8 - .../compass-schema-validation/package.json | 1 - .../src/modules/validation.ts | 2 +- packages/compass-schema/package.json | 1 - .../src/components/schema-toolbar.tsx | 2 +- packages/compass-schema/src/index.ts | 2 + .../src/modules}/schema-analysis.spec.ts | 0 .../src/modules}/schema-analysis.ts | 10 +- .../src/stores/schema-analysis-reducer.ts | 2 +- .../src/stores/schema-export-reducer.ts | 2 +- 22 files changed, 11 insertions(+), 333 deletions(-) delete mode 100644 packages/compass-schema-analysis/.depcheckrc delete mode 100644 packages/compass-schema-analysis/.eslintignore delete mode 100644 packages/compass-schema-analysis/.eslintrc.js delete mode 100644 packages/compass-schema-analysis/.mocharc.js delete mode 100644 packages/compass-schema-analysis/.prettierignore delete mode 100644 packages/compass-schema-analysis/.prettierrc.json delete mode 100644 packages/compass-schema-analysis/package.json delete mode 100644 packages/compass-schema-analysis/src/index.spec.tsx delete mode 100644 packages/compass-schema-analysis/src/index.ts delete mode 100644 packages/compass-schema-analysis/tsconfig-lint.json delete mode 100644 packages/compass-schema-analysis/tsconfig.json rename packages/{compass-schema-analysis/src => compass-schema/src/modules}/schema-analysis.spec.ts (100%) rename packages/{compass-schema-analysis/src => compass-schema/src/modules}/schema-analysis.ts (93%) diff --git a/README.md b/README.md index 27a57f62fc8..b05d0667a2c 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,6 @@ Is there anything else you’d like to see in Compass? Let us know by submitting - [**@mongodb-js/compass-maybe-protect-connection-string**](packages/compass-maybe-protect-connection-string): Utility for protecting connection strings if requested - [**@mongodb-js/compass-settings**](packages/compass-settings): Settings for compass - [**@mongodb-js/compass-telemetry**](packages/compass-telemetry): Compass telemetry -- [**@mongodb-js/compass-schema-analysis**](packages/compass-schema-analysis): Compass schema analysis - [**@mongodb-js/compass-test-server**](packages/compass-test-server): Wrapper around mongodb-runner to manage test servers for Compass - [**@mongodb-js/compass-user-data**](packages/compass-user-data): undefined - [**@mongodb-js/compass-utils**](packages/compass-utils): Utilities for MongoDB Compass Development diff --git a/package-lock.json b/package-lock.json index a9b9c855b6f..e5cd70457b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7931,10 +7931,6 @@ "resolved": "packages/compass-schema", "link": true }, - "node_modules/@mongodb-js/compass-schema-analysis": { - "resolved": "packages/compass-schema-analysis", - "link": true - }, "node_modules/@mongodb-js/compass-schema-validation": { "resolved": "packages/compass-schema-validation", "link": true @@ -46407,7 +46403,6 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/eslint-config-compass": "^1.3.5", "@mongodb-js/mocha-config-compass": "^1.6.5", "@mongodb-js/my-queries-storage": "^0.22.5", @@ -46435,6 +46430,7 @@ "packages/compass-schema-analysis": { "name": "@mongodb-js/compass-schema-analysis", "version": "1.0.0", + "extraneous": true, "license": "SSPL", "dependencies": { "@mongodb-js/compass-connections": "^1.50.5", @@ -46465,95 +46461,6 @@ "typescript": "^5.0.4" } }, - "packages/compass-schema-analysis/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "packages/compass-schema-analysis/node_modules/@sinonjs/fake-timers": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", - "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "packages/compass-schema-analysis/node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "packages/compass-schema-analysis/node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "packages/compass-schema-analysis/node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, - "packages/compass-schema-analysis/node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "packages/compass-schema-analysis/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, - "packages/compass-schema-analysis/node_modules/sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, "packages/compass-schema-validation": { "name": "@mongodb-js/compass-schema-validation", "version": "6.50.5", @@ -46567,7 +46474,6 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", - "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "bson": "^6.10.3", @@ -58070,7 +57976,6 @@ "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", "@mongodb-js/compass-query-bar": "^8.51.5", - "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/connection-storage": "^0.26.5", "@mongodb-js/eslint-config-compass": "^1.3.5", @@ -58139,113 +58044,6 @@ } } }, - "@mongodb-js/compass-schema-analysis": { - "version": "file:packages/compass-schema-analysis", - "requires": { - "@mongodb-js/compass-connections": "^1.50.5", - "@mongodb-js/compass-logging": "^1.6.5", - "@mongodb-js/eslint-config-compass": "^1.3.3", - "@mongodb-js/mocha-config-compass": "^1.6.3", - "@mongodb-js/prettier-config-compass": "^1.2.3", - "@mongodb-js/testing-library-compass": "^1.2.3", - "@mongodb-js/tsconfig-compass": "^1.2.3", - "@types/chai": "^4.2.21", - "@types/mocha": "^9.0.0", - "@types/sinon-chai": "^3.2.5", - "bson": "^6.10.3", - "chai": "^4.3.6", - "compass-preferences-model": "^2.33.5", - "depcheck": "^1.4.1", - "eslint": "^7.25.0", - "gen-esm-wrapper": "^1.1.0", - "hadron-document": "^8.8.5", - "mocha": "^10.2.0", - "mongodb": "^6.14.1", - "mongodb-schema": "^12.4.0", - "nyc": "^15.1.0", - "prettier": "^2.7.1", - "sinon": "^17.0.1", - "typescript": "^5.0.4" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", - "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.1" - } - }, - "@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - }, - "dependencies": { - "type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true - } - } - }, - "just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, - "nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, - "sinon": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", - "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - } - } - } - }, "@mongodb-js/compass-schema-validation": { "version": "file:packages/compass-schema-validation", "requires": { @@ -58257,7 +58055,6 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", - "@mongodb-js/compass-schema-analysis": "^1.0.0", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "@mongodb-js/eslint-config-compass": "^1.3.5", diff --git a/packages/compass-schema-analysis/.depcheckrc b/packages/compass-schema-analysis/.depcheckrc deleted file mode 100644 index ab0ef21b740..00000000000 --- a/packages/compass-schema-analysis/.depcheckrc +++ /dev/null @@ -1,8 +0,0 @@ -ignores: - - '@mongodb-js/prettier-config-compass' - - '@mongodb-js/tsconfig-compass' - - '@types/chai' - - '@types/sinon-chai' - - 'sinon' -ignore-patterns: - - 'dist' diff --git a/packages/compass-schema-analysis/.eslintignore b/packages/compass-schema-analysis/.eslintignore deleted file mode 100644 index 85a8a75e68c..00000000000 --- a/packages/compass-schema-analysis/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -.nyc-output -dist diff --git a/packages/compass-schema-analysis/.eslintrc.js b/packages/compass-schema-analysis/.eslintrc.js deleted file mode 100644 index e4cf824b6ac..00000000000 --- a/packages/compass-schema-analysis/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - root: true, - extends: ['@mongodb-js/eslint-config-compass'], - parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig-lint.json'], - }, -}; diff --git a/packages/compass-schema-analysis/.mocharc.js b/packages/compass-schema-analysis/.mocharc.js deleted file mode 100644 index 7e473d17b76..00000000000 --- a/packages/compass-schema-analysis/.mocharc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-schema-analysis/.prettierignore b/packages/compass-schema-analysis/.prettierignore deleted file mode 100644 index 4d28df6603a..00000000000 --- a/packages/compass-schema-analysis/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -.nyc_output -dist -coverage diff --git a/packages/compass-schema-analysis/.prettierrc.json b/packages/compass-schema-analysis/.prettierrc.json deleted file mode 100644 index 18853d1532e..00000000000 --- a/packages/compass-schema-analysis/.prettierrc.json +++ /dev/null @@ -1 +0,0 @@ -"@mongodb-js/prettier-config-compass" diff --git a/packages/compass-schema-analysis/package.json b/packages/compass-schema-analysis/package.json deleted file mode 100644 index cb854cbe03b..00000000000 --- a/packages/compass-schema-analysis/package.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "name": "@mongodb-js/compass-schema-analysis", - "description": "Schema analysis", - "author": { - "name": "MongoDB Inc", - "email": "compass@mongodb.com" - }, - "publishConfig": { - "access": "public" - }, - "bugs": { - "url": "https://jira.mongodb.org/projects/COMPASS/issues", - "email": "compass@mongodb.com" - }, - "homepage": "https://github.com/mongodb-js/compass", - "version": "1.0.0", - "repository": { - "type": "git", - "url": "https://github.com/mongodb-js/compass.git" - }, - "files": [ - "dist" - ], - "license": "SSPL", - "main": "dist/index.js", - "compass:main": "src/index.ts", - "exports": { - "import": "./dist/.esm-wrapper.mjs", - "require": "./dist/index.js" - }, - "compass:exports": { - ".": "./src/index.ts" - }, - "types": "./dist/index.d.ts", - "scripts": { - "bootstrap": "npm run compile", - "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", - "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", - "typecheck": "tsc -p tsconfig-lint.json --noEmit", - "eslint": "eslint", - "prettier": "prettier", - "lint": "npm run eslint . && npm run prettier -- --check .", - "depcheck": "compass-scripts check-peer-deps && depcheck", - "check": "npm run typecheck && npm run lint && npm run depcheck", - "check-ci": "npm run check", - "test": "mocha", - "test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", - "test-watch": "npm run test -- --watch", - "test-ci": "npm run test-cov", - "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." - }, - "dependencies": { - "@mongodb-js/compass-connections": "^1.50.5", - "mongodb": "^6.14.1", - "mongodb-schema": "^12.4.0", - "@mongodb-js/compass-logging": "^1.6.5", - "compass-preferences-model": "^2.33.5" - }, - "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.3.3", - "@mongodb-js/mocha-config-compass": "^1.6.3", - "@mongodb-js/prettier-config-compass": "^1.2.3", - "@mongodb-js/tsconfig-compass": "^1.2.3", - "@types/chai": "^4.2.21", - "@types/mocha": "^9.0.0", - "@types/sinon-chai": "^3.2.5", - "bson": "^6.10.3", - "hadron-document": "^8.8.5", - "chai": "^4.3.6", - "depcheck": "^1.4.1", - "eslint": "^7.25.0", - "gen-esm-wrapper": "^1.1.0", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "prettier": "^2.7.1", - "sinon": "^17.0.1", - "typescript": "^5.0.4" - } -} diff --git a/packages/compass-schema-analysis/src/index.spec.tsx b/packages/compass-schema-analysis/src/index.spec.tsx deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/compass-schema-analysis/src/index.ts b/packages/compass-schema-analysis/src/index.ts deleted file mode 100644 index 3c5b8647e60..00000000000 --- a/packages/compass-schema-analysis/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './schema-analysis'; diff --git a/packages/compass-schema-analysis/tsconfig-lint.json b/packages/compass-schema-analysis/tsconfig-lint.json deleted file mode 100644 index 6bdef84f322..00000000000 --- a/packages/compass-schema-analysis/tsconfig-lint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/compass-schema-analysis/tsconfig.json b/packages/compass-schema-analysis/tsconfig.json deleted file mode 100644 index ecd0a14474a..00000000000 --- a/packages/compass-schema-analysis/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "@mongodb-js/tsconfig-compass/tsconfig.common.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": ["src/**/*"], - "exclude": ["./src/**/*.spec.*"] -} diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index e49495f12f9..d16513c5dfc 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -79,7 +79,6 @@ "@mongodb-js/compass-logging": "^1.6.5", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", - "@mongodb-js/compass-schema-analysis": "^1.0.0", "bson": "^6.10.3", "compass-preferences-model": "^2.33.5", "hadron-app-registry": "^9.4.5", diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index 0311e120686..2df1700199f 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -8,7 +8,7 @@ import { isLoadedChanged } from './is-loaded'; import { isEqual, pick } from 'lodash'; import type { ThunkDispatch } from 'redux-thunk'; import { disableEditRules, enableEditRules } from './edit-mode'; -import { analyzeSchema } from '@mongodb-js/compass-schema-analysis'; +import { analyzeSchema } from '@mongodb-js/compass-schema'; export type ValidationServerAction = 'error' | 'warn'; export type ValidationLevel = 'off' | 'moderate' | 'strict'; diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 6f1662a5201..0620620895a 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -54,7 +54,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.5", "@mongodb-js/testing-library-compass": "^1.2.5", "@mongodb-js/tsconfig-compass": "^1.2.5", - "@mongodb-js/compass-schema-analysis": "^1.0.0", "@types/chai": "^4.2.21", "@types/leaflet": "^1.9.8", "@types/leaflet-draw": "^1.0.11", diff --git a/packages/compass-schema/src/components/schema-toolbar.tsx b/packages/compass-schema/src/components/schema-toolbar.tsx index 859a0ad1402..e7d888bc9a7 100644 --- a/packages/compass-schema/src/components/schema-toolbar.tsx +++ b/packages/compass-schema/src/components/schema-toolbar.tsx @@ -21,8 +21,8 @@ import { type SchemaAnalysisError, analysisErrorDismissed, } from '../stores/schema-analysis-reducer'; -import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '@mongodb-js/compass-schema-analysis'; import type { RootState } from '../stores/store'; +import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '../modules/schema-analysis'; import { openExportSchema } from '../stores/schema-export-reducer'; const schemaToolbarStyles = css({ diff --git a/packages/compass-schema/src/index.ts b/packages/compass-schema/src/index.ts index 6f6d2870d39..b546ea37cd3 100644 --- a/packages/compass-schema/src/index.ts +++ b/packages/compass-schema/src/index.ts @@ -49,3 +49,5 @@ export const CompassSchemaPlugin = { content: CompassSchema as React.FunctionComponent /* reflux store */, header: SchemaTabTitle, }; + +export * from './modules/schema-analysis'; diff --git a/packages/compass-schema-analysis/src/schema-analysis.spec.ts b/packages/compass-schema/src/modules/schema-analysis.spec.ts similarity index 100% rename from packages/compass-schema-analysis/src/schema-analysis.spec.ts rename to packages/compass-schema/src/modules/schema-analysis.spec.ts diff --git a/packages/compass-schema-analysis/src/schema-analysis.ts b/packages/compass-schema/src/modules/schema-analysis.ts similarity index 93% rename from packages/compass-schema-analysis/src/schema-analysis.ts rename to packages/compass-schema/src/modules/schema-analysis.ts index 4006dd75c1b..6a1c3bc5012 100644 --- a/packages/compass-schema-analysis/src/schema-analysis.ts +++ b/packages/compass-schema/src/modules/schema-analysis.ts @@ -10,12 +10,10 @@ import type { PrimitiveSchemaType, SchemaParseOptions, } from 'mongodb-schema'; -import type { DataService as OriginalDataService } from '@mongodb-js/compass-connections/provider'; +import type { DataService } from '../stores/store'; import type { Logger } from '@mongodb-js/compass-logging'; import type { PreferencesAccess } from 'compass-preferences-model'; -type DataService = Pick; - export const DISTINCT_FIELDS_ABORT_THRESHOLD = 1000; // hack for driver 3.6 not promoting error codes and @@ -48,7 +46,7 @@ export const analyzeSchema = async ( preferences: PreferencesAccess ): Promise => { try { - log.info(mongoLogId(1_001_000_348), 'Schema', 'Starting schema analysis', { + log.info(mongoLogId(1001000089), 'Schema', 'Starting schema analysis', { ns, }); @@ -75,12 +73,12 @@ export const analyzeSchema = async ( signal: abortSignal, }; const schemaAccessor = await analyzeDocuments(docs, schemaParseOptions); - log.info(mongoLogId(1_001_000_346), 'Schema', 'Schema analysis completed', { + log.info(mongoLogId(1001000090), 'Schema', 'Schema analysis completed', { ns, }); return schemaAccessor; } catch (err: any) { - log.error(mongoLogId(1_001_000_347), 'Schema', 'Schema analysis failed', { + log.error(mongoLogId(1001000091), 'Schema', 'Schema analysis failed', { ns, error: err.message, }); diff --git a/packages/compass-schema/src/stores/schema-analysis-reducer.ts b/packages/compass-schema/src/stores/schema-analysis-reducer.ts index 210dcecafa4..564afc2d4f7 100644 --- a/packages/compass-schema/src/stores/schema-analysis-reducer.ts +++ b/packages/compass-schema/src/stores/schema-analysis-reducer.ts @@ -13,7 +13,7 @@ import { addLayer, generateGeoQuery } from '../modules/geo'; import { analyzeSchema, calculateSchemaMetadata, -} from '@mongodb-js/compass-schema-analysis'; +} from '../modules/schema-analysis'; import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model/provider'; import type { Circle, Layer, LayerGroup, Polygon } from 'leaflet'; import { mongoLogId } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-schema/src/stores/schema-export-reducer.ts b/packages/compass-schema/src/stores/schema-export-reducer.ts index e24af488253..8ed2c050a6d 100644 --- a/packages/compass-schema/src/stores/schema-export-reducer.ts +++ b/packages/compass-schema/src/stores/schema-export-reducer.ts @@ -11,7 +11,7 @@ import { openToast } from '@mongodb-js/compass-components'; import type { SchemaThunkAction } from './store'; import { isAction } from '../utils'; -import { calculateSchemaMetadata } from '@mongodb-js/compass-schema-analysis'; +import { calculateSchemaMetadata } from '../modules/schema-analysis'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; From a48c7fa39243091211a97675a8a17fce79081314 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 13:44:24 +0100 Subject: [PATCH 07/13] move rulesGeneration into separate module + align errors --- .../src/components/validation-states.tsx | 63 ++++- .../src/modules/index.ts | 10 +- .../src/modules/rules-generation.ts | 223 ++++++++++++++++++ .../src/modules/validation.ts | 154 +----------- .../src/components/schema-toolbar.tsx | 2 +- 5 files changed, 290 insertions(+), 162 deletions(-) create mode 100644 packages/compass-schema-validation/src/modules/rules-generation.ts diff --git a/packages/compass-schema-validation/src/components/validation-states.tsx b/packages/compass-schema-validation/src/components/validation-states.tsx index 34e9fafb4db..b02d552529d 100644 --- a/packages/compass-schema-validation/src/components/validation-states.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Banner, + BannerVariant, Button, ButtonVariant, CancelLoader, @@ -22,7 +23,9 @@ import { clearRulesGenerationError, generateValidationRules, stopRulesGeneration, -} from '../modules/validation'; + type RulesGenerationError, +} from '../modules/rules-generation'; +import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '@mongodb-js/compass-schema'; const validationStatesStyles = css({ padding: spacing[400], @@ -67,7 +70,7 @@ const DOC_UPGRADE_REVISION = type ValidationStatesProps = { isZeroState: boolean; isRulesGenerationInProgress?: boolean; - rulesGenerationError?: string; + rulesGenerationError?: RulesGenerationError; isLoaded: boolean; changeZeroState: (value: boolean) => void; generateValidationRules: () => void; @@ -144,6 +147,51 @@ const GeneratingScreen: React.FunctionComponent<{ ); }; +const RulesGenerationErrorBanner: React.FunctionComponent<{ + error: RulesGenerationError; + onDismissError: () => void; +}> = ({ error, onDismissError }) => { + if (error?.errorType === 'timeout') { + return ( + + ); + } + if (error?.errorType === 'highComplexity') { + return ( + + The rules generation was aborted because the number of fields exceeds{' '} + {DISTINCT_FIELDS_ABORT_THRESHOLD}. Consider breaking up your data into + more collections with smaller documents, and using references to + consolidate the data you need.  + + Learn more + + + ); + } + + return ( + + ); +}; + export function ValidationStates({ isZeroState, isRulesGenerationInProgress, @@ -173,10 +221,9 @@ export function ValidationStates({ data-testid="schema-validation-states" > {rulesGenerationError && ( - )} @@ -248,8 +295,8 @@ const mapStateToProps = (state: RootState) => ({ isZeroState: state.isZeroState, isLoaded: state.isLoaded, editMode: state.editMode, - isRulesGenerationInProgress: state.validation.isRulesGenerationInProgress, - rulesGenerationError: state.validation.rulesGenerationError, + isRulesGenerationInProgress: state.rulesGeneration.isInProgress, + rulesGenerationError: state.rulesGeneration.error, }); /** diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index fc7a1d4b8ca..df460142e3e 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -31,6 +31,11 @@ import type AppRegistry from 'hadron-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; +import type { RulesGenerationState } from './rules-generation'; +import { + INITIAL_STATE as RULES_GENERATION_STATE, + rulesGenerationReducer, +} from './rules-generation'; /** * Reset action constant. @@ -44,6 +49,7 @@ export interface RootState { namespace: NamespaceState; serverVersion: ServerVersionState; validation: ValidationState; + rulesGeneration: RulesGenerationState; sampleDocuments: SampleDocumentState; isZeroState: IsZeroStateState; isLoaded: IsLoadedState; @@ -92,6 +98,7 @@ export const INITIAL_STATE: RootState = { namespace: NS_INITIAL_STATE, serverVersion: SV_INITIAL_STATE, validation: VALIDATION_STATE, + rulesGeneration: RULES_GENERATION_STATE, sampleDocuments: SAMPLE_DOCUMENTS_STATE, isZeroState: IS_ZERO_STATE, isLoaded: IS_LOADED_STATE, @@ -101,7 +108,7 @@ export const INITIAL_STATE: RootState = { /** * The reducer. */ -const appReducer = combineReducers({ +const appReducer = combineReducers({ namespace, serverVersion, validation, @@ -109,6 +116,7 @@ const appReducer = combineReducers({ isZeroState, isLoaded, editMode, + rulesGeneration: rulesGenerationReducer, }); /** diff --git a/packages/compass-schema-validation/src/modules/rules-generation.ts b/packages/compass-schema-validation/src/modules/rules-generation.ts new file mode 100644 index 00000000000..b8a3c67a2f1 --- /dev/null +++ b/packages/compass-schema-validation/src/modules/rules-generation.ts @@ -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( + 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 = ( + state = INITIAL_STATE, + action +) => { + if ( + isAction( + action, + RulesGenerationActions.generationStarted + ) + ) { + return { + ...state, + isInProgress: true, + error: undefined, + }; + } + + if ( + isAction( + action, + RulesGenerationActions.generationFinished + ) + ) { + return { + ...state, + isInProgress: false, + }; + } + + if ( + isAction( + action, + RulesGenerationActions.generationFailed + ) + ) { + return { + ...state, + isInProgress: false, + error: getErrorDetails(action.error), + }; + } + + if ( + isAction( + action, + RulesGenerationActions.generationErrorCleared + ) + ) { + return { + ...state, + error: undefined, + }; + } + + return state; +}; + +export const clearRulesGenerationError = + (): SchemaValidationThunkAction => { + return (dispatch) => + dispatch({ type: RulesGenerationActions.generationErrorCleared }); + }; + +export const stopRulesGeneration = (): SchemaValidationThunkAction => { + return (dispatch, getState, { rulesGenerationAbortControllerRef }) => { + if (!rulesGenerationAbortControllerRef.current) return; + rulesGenerationAbortControllerRef.current?.abort(ABORT_MESSAGE); + }; +}; + +/** + * Get $jsonSchema from schema analysis + * @returns + */ +export const generateValidationRules = (): SchemaValidationThunkAction< + Promise +> => { + 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, + }); + } + }; +}; diff --git a/packages/compass-schema-validation/src/modules/validation.ts b/packages/compass-schema-validation/src/modules/validation.ts index 2df1700199f..c2059e38ba7 100644 --- a/packages/compass-schema-validation/src/modules/validation.ts +++ b/packages/compass-schema-validation/src/modules/validation.ts @@ -7,15 +7,11 @@ import { zeroStateChanged } from './zero-state'; import { isLoadedChanged } from './is-loaded'; import { isEqual, pick } from 'lodash'; import type { ThunkDispatch } from 'redux-thunk'; -import { disableEditRules, enableEditRules } from './edit-mode'; -import { analyzeSchema } from '@mongodb-js/compass-schema'; +import { disableEditRules } from './edit-mode'; export type ValidationServerAction = 'error' | 'warn'; export type ValidationLevel = 'off' | 'moderate' | 'strict'; -const SAMPLE_SIZE = 1000; -const ABORT_MESSAGE = 'Operation cancelled'; - /** * The module action prefix. */ @@ -87,31 +83,6 @@ interface SyntaxErrorOccurredAction { syntaxError: null | { message: string }; } -export const RULES_GENERATION_STARTED = - `${PREFIX}/RULES_GENERATION_STARTED` as const; -interface RulesGenerationStartedAction { - type: typeof RULES_GENERATION_STARTED; -} - -export const RULES_GENERATION_FAILED = - `${PREFIX}/RULES_GENERATION_FAILED` as const; -interface RulesGenerationFailedAction { - type: typeof RULES_GENERATION_FAILED; - message: string; -} - -export const RULES_GENERATION_CLEAR_ERROR = - `${PREFIX}/RULES_GENERATION_CLEAR_ERROR` as const; -interface RulesGenerationClearErrorAction { - type: typeof RULES_GENERATION_CLEAR_ERROR; -} - -export const RULES_GENERATION_FINISHED = - `${PREFIX}/RULES_GENERATION_FINISHED` as const; -interface RulesGenerationFinishedAction { - type: typeof RULES_GENERATION_FINISHED; -} - export type ValidationAction = | ValidatorChangedAction | ValidationCanceledAction @@ -119,11 +90,7 @@ export type ValidationAction = | ValidationFetchedAction | ValidationActionChangedAction | ValidationLevelChangedAction - | SyntaxErrorOccurredAction - | RulesGenerationStartedAction - | RulesGenerationFinishedAction - | RulesGenerationFailedAction - | RulesGenerationClearErrorAction; + | SyntaxErrorOccurredAction; export interface Validation { validator: string; @@ -143,8 +110,6 @@ export interface ValidationState extends Validation { syntaxError: null | { message: string }; error: null | { message: string }; prevValidation?: Validation; - isRulesGenerationInProgress?: boolean; - rulesGenerationError?: string; } /** @@ -224,34 +189,6 @@ const setSyntaxError = ( syntaxError: action.syntaxError, }); -const startRulesGeneration = (state: ValidationState): ValidationState => ({ - ...state, - isRulesGenerationInProgress: true, - rulesGenerationError: undefined, -}); - -const finishRulesGeneration = (state: ValidationState): ValidationState => ({ - ...state, - isRulesGenerationInProgress: undefined, - rulesGenerationError: undefined, -}); - -const markRulesGenerationFailure = ( - state: ValidationState, - action: RulesGenerationFailedAction -): ValidationState => ({ - ...state, - isRulesGenerationInProgress: undefined, - rulesGenerationError: action.message, -}); - -const unsetRulesGenerationError = ( - state: ValidationState -): ValidationState => ({ - ...state, - rulesGenerationError: undefined, -}); - /** * Set validation. */ @@ -354,10 +291,6 @@ const MAPPINGS: { [VALIDATION_ACTION_CHANGED]: changeValidationAction, [VALIDATION_LEVEL_CHANGED]: changeValidationLevel, [SYNTAX_ERROR_OCCURRED]: setSyntaxError, - [RULES_GENERATION_STARTED]: startRulesGeneration, - [RULES_GENERATION_FINISHED]: finishRulesGeneration, - [RULES_GENERATION_FAILED]: markRulesGenerationFailure, - [RULES_GENERATION_CLEAR_ERROR]: unsetRulesGenerationError, }; /** @@ -448,11 +381,6 @@ export const syntaxErrorOccurred = ( syntaxError, }); -export const clearRulesGenerationError = - (): RulesGenerationClearErrorAction => ({ - type: RULES_GENERATION_CLEAR_ERROR, - }); - export const fetchValidation = (namespace: { database: string; collection: string; @@ -608,81 +536,3 @@ export const activateValidation = (): SchemaValidationThunkAction => { dispatch(fetchValidation(namespace)); }; }; - -export const stopRulesGeneration = (): SchemaValidationThunkAction => { - return (dispatch, getState, { rulesGenerationAbortControllerRef }) => { - if (!rulesGenerationAbortControllerRef.current) return; - rulesGenerationAbortControllerRef.current?.abort(ABORT_MESSAGE); - }; -}; - -/** - * Get $jsonSchema from schema analysis - * @returns - */ -export const generateValidationRules = (): SchemaValidationThunkAction< - Promise -> => { - return async ( - dispatch, - getState, - { dataService, logger, preferences, rulesGenerationAbortControllerRef } - ) => { - dispatch({ type: RULES_GENERATION_STARTED }); - - 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: RULES_GENERATION_FINISHED }); - dispatch(zeroStateChanged(false)); - } catch (error) { - if (abortSignal.aborted) { - dispatch({ type: RULES_GENERATION_FINISHED }); - return; - } - dispatch({ - type: RULES_GENERATION_FAILED, - message: `Rules generation failed: ${(error as Error).message}`, - }); - } - }; -}; diff --git a/packages/compass-schema/src/components/schema-toolbar.tsx b/packages/compass-schema/src/components/schema-toolbar.tsx index e7d888bc9a7..0fca62f0ef2 100644 --- a/packages/compass-schema/src/components/schema-toolbar.tsx +++ b/packages/compass-schema/src/components/schema-toolbar.tsx @@ -21,8 +21,8 @@ import { type SchemaAnalysisError, analysisErrorDismissed, } from '../stores/schema-analysis-reducer'; -import type { RootState } from '../stores/store'; import { DISTINCT_FIELDS_ABORT_THRESHOLD } from '../modules/schema-analysis'; +import type { RootState } from '../stores/store'; import { openExportSchema } from '../stores/schema-export-reducer'; const schemaToolbarStyles = css({ From 93a9103b3c0c409050818c5d1797923fb109bf1c Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 16:02:29 +0100 Subject: [PATCH 08/13] add tests --- .../compass-e2e-tests/helpers/selectors.ts | 2 + .../tests/collection-validation-tab.test.ts | 38 +++++ .../src/components/validation-states.spec.tsx | 13 +- .../src/modules/index.ts | 2 + .../src/modules/rules-generation.ts | 9 +- .../src/stores/store.spec.ts | 135 +++++++++++++++++- .../src/stores/store.ts | 11 +- 7 files changed, 198 insertions(+), 12 deletions(-) diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 48aec8a66a4..31ff13c4d76 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1161,6 +1161,8 @@ export const ValidationActionSelector = '[data-testid="validation-action-selector"]'; export const ValidationLevelSelector = '[data-testid="validation-level-selector"]'; +export const GenerateValidationRulesButton = + '[data-testid="generate-rules-button"]'; // Find (Documents and Schema tabs) export const queryBar = (tabName: string): string => { diff --git a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts index 65f00b477d8..80d7c28965e 100644 --- a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts @@ -8,6 +8,7 @@ import { import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; import { createNumbersCollection } from '../helpers/insert-data'; +import { expect } from 'chai'; const NO_PREVIEW_DOCUMENTS = 'No Preview Documents'; const PASSING_VALIDATOR = '{ $jsonSchema: {} }'; @@ -52,6 +53,43 @@ describe('Collection validation tab', function () { await browser.setValidation(validation); } + context('when the schema validation is empty', function () { + before(async function () { + await browser.setFeature('enableExportSchema', true); + }); + + it.only('provides users with a button to generate rules', async function () { + await browser.clickVisible(Selectors.GenerateValidationRulesButton); + const editor = browser.$(Selectors.ValidationEditor); + await editor.waitForDisplayed(); + + // rules are generated + const generatedRules = await browser.getCodemirrorEditorText( + Selectors.ValidationEditor + ); + expect(JSON.parse(generatedRules)).to.deep.equal({ + $jsonSchema: { + bsonType: 'object', + required: ['_id', 'i', 'j'], + properties: { + _id: { + bsonType: 'objectId', + }, + i: { + bsonType: 'int', + }, + j: { + bsonType: 'int', + }, + }, + }, + }); + + // generated rules can be edited and saved + await browser.setValidation('{ $jsonSchema: { "blah": "string" } }'); + }); + }); + context('when the schema validation is set or modified', function () { it('provides users with a single button to load sample documents', async function () { await addValidation(PASSING_VALIDATOR); diff --git a/packages/compass-schema-validation/src/components/validation-states.spec.tsx b/packages/compass-schema-validation/src/components/validation-states.spec.tsx index bc4edc5fe75..cdf149c3d9e 100644 --- a/packages/compass-schema-validation/src/components/validation-states.spec.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.spec.tsx @@ -31,8 +31,8 @@ const { renderWithConnections } = createPluginTestHelpers( describe('ValidationStates [Component]', function () { let props: any; - const render = (props: any) => { - return renderWithConnections(); + const render = (props: any, options: any = {}) => { + return renderWithConnections(, options); }; beforeEach(function () { @@ -255,13 +255,18 @@ describe('ValidationStates [Component]', function () { props.isZeroState = true; props.isLoaded = true; props.serverVersion = '3.2.0'; - - render(props); }); it('renders the zero state', function () { + render(props); expect(screen.getByTestId('empty-content')).to.exist; }); + + it('when enableExportSchema is set, shows button for rules generation', function () { + render(props, { preferences: { enableExportSchema: true } }); + const btn = screen.getByRole('button', { name: 'Generate rules' }); + expect(btn).to.be.visible; + }); }); context('when it is not in the zero state and not loaded', function () { diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index df460142e3e..09fb5cf926e 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -36,6 +36,7 @@ import { INITIAL_STATE as RULES_GENERATION_STATE, rulesGenerationReducer, } from './rules-generation'; +import type { analyzeSchema } from '@mongodb-js/compass-schema'; /** * Reset action constant. @@ -84,6 +85,7 @@ export type SchemaValidationExtraArgs = { logger: Logger; track: TrackFunction; rulesGenerationAbortControllerRef: { current?: AbortController }; + analyzeSchema: typeof analyzeSchema; }; export type SchemaValidationThunkAction< diff --git a/packages/compass-schema-validation/src/modules/rules-generation.ts b/packages/compass-schema-validation/src/modules/rules-generation.ts index b8a3c67a2f1..352a611febd 100644 --- a/packages/compass-schema-validation/src/modules/rules-generation.ts +++ b/packages/compass-schema-validation/src/modules/rules-generation.ts @@ -1,7 +1,6 @@ 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'; @@ -161,7 +160,13 @@ export const generateValidationRules = (): SchemaValidationThunkAction< return async ( dispatch, getState, - { dataService, logger, preferences, rulesGenerationAbortControllerRef } + { + dataService, + logger, + preferences, + rulesGenerationAbortControllerRef, + analyzeSchema, + } ) => { dispatch({ type: RulesGenerationActions.generationStarted }); diff --git a/packages/compass-schema-validation/src/stores/store.spec.ts b/packages/compass-schema-validation/src/stores/store.spec.ts index 32a661d85d4..dbab08b4731 100644 --- a/packages/compass-schema-validation/src/stores/store.spec.ts +++ b/packages/compass-schema-validation/src/stores/store.spec.ts @@ -20,6 +20,11 @@ import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; import Sinon from 'sinon'; +import { + generateValidationRules, + stopRulesGeneration, +} from '../modules/rules-generation'; +import { waitFor } from '@mongodb-js/testing-library-compass'; const topologyDescription = { type: 'Unknown', @@ -39,6 +44,8 @@ const fakeDataService = { new Promise(() => { /* never resolves */ }), + isCancelError: () => false, + sample: () => [{ prop1: 'abc' }], } as any; const fakeWorkspaces = { @@ -46,7 +53,7 @@ const fakeWorkspaces = { onTabClose: () => {}, } as unknown as WorkspacesService; -const getMockedStore = async () => { +const getMockedStore = async (analyzeSchema: any) => { const globalAppRegistry = new AppRegistry(); const connectionInfoRef = { current: {}, @@ -63,11 +70,16 @@ const getMockedStore = async () => { track: createNoopTrack(), connectionInfoRef, }, - createActivateHelpers() + createActivateHelpers(), + analyzeSchema ); return activateResult; }; +const schemaAccessor = { + getMongoDBJsonSchema: () => ({ required: ['prop1'] }), +}; + describe('Schema Validation Store', function () { let store: Store; let deactivate: null | (() => void) = null; @@ -77,7 +89,8 @@ describe('Schema Validation Store', function () { sandbox = Sinon.createSandbox(); fakeWorkspaces.onTabClose = sandbox.stub(); fakeWorkspaces.onTabReplace = sandbox.stub(); - const activateResult = await getMockedStore(); + const fakeAnalyzeSchema = sandbox.fake.resolves(schemaAccessor); + const activateResult = await getMockedStore(fakeAnalyzeSchema); store = activateResult.store; deactivate = activateResult.deactivate; }); @@ -278,5 +291,121 @@ describe('Schema Validation Store', function () { store.dispatch(validationLevelChanged(validationLevel)); }); }); + + context('when the action is generateValidationRules', function () { + it('executes rules generation', async function () { + store.dispatch(generateValidationRules() as any); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal(true); + }); + await waitFor(() => { + expect( + JSON.parse(store.getState().validation.validator) + ).to.deep.equal({ + $jsonSchema: { + required: ['prop1'], + }, + }); + expect(store.getState().rulesGeneration.isInProgress).to.equal(false); + expect(store.getState().rulesGeneration.error).to.be.undefined; + }); + }); + + it('rules generation can be aborted', async function () { + store.dispatch(generateValidationRules() as any); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal(true); + }); + + store.dispatch(stopRulesGeneration() as any); + await waitFor(() => { + expect(store.getState().validation.validator).to.equal(''); + expect(store.getState().rulesGeneration.isInProgress).to.equal(false); + expect(store.getState().rulesGeneration.error).to.be.undefined; + }); + }); + + context('rules generation failure', function () { + it('handles general error', async function () { + const fakeAnalyzeSchema = sandbox.fake.rejects( + new Error('Such a failure') + ); + const activateResult = await getMockedStore(fakeAnalyzeSchema); + store = activateResult.store; + deactivate = activateResult.deactivate; + store.dispatch(generateValidationRules() as any); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + true + ); + }); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + false + ); + expect(store.getState().rulesGeneration.error).to.deep.equal({ + errorMessage: 'Such a failure', + errorType: 'general', + }); + }); + }); + + it('handles complexity error', async function () { + const fakeAnalyzeSchema = sandbox.fake.rejects( + new Error('Schema analysis aborted: Fields count above 1000') + ); + const activateResult = await getMockedStore(fakeAnalyzeSchema); + store = activateResult.store; + deactivate = activateResult.deactivate; + store.dispatch(generateValidationRules() as any); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + true + ); + }); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + false + ); + expect(store.getState().rulesGeneration.error).to.deep.equal({ + errorMessage: 'Schema analysis aborted: Fields count above 1000', + errorType: 'highComplexity', + }); + }); + }); + + it('handles timeout error', async function () { + const timeoutError: any = new Error('Too long, didnt execute'); + timeoutError.code = 50; + const fakeAnalyzeSchema = sandbox.fake.rejects(timeoutError); + const activateResult = await getMockedStore(fakeAnalyzeSchema); + store = activateResult.store; + deactivate = activateResult.deactivate; + store.dispatch(generateValidationRules() as any); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + true + ); + }); + + await waitFor(() => { + expect(store.getState().rulesGeneration.isInProgress).to.equal( + false + ); + expect(store.getState().rulesGeneration.error).to.deep.equal({ + errorMessage: 'Too long, didnt execute', + errorType: 'timeout', + }); + }); + }); + }); + }); }); }); diff --git a/packages/compass-schema-validation/src/stores/store.ts b/packages/compass-schema-validation/src/stores/store.ts index 08dfaa05d82..0bcea1a7b95 100644 --- a/packages/compass-schema-validation/src/stores/store.ts +++ b/packages/compass-schema-validation/src/stores/store.ts @@ -14,6 +14,7 @@ import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; +import { analyzeSchema as compassAnalyzeSchema } from '@mongodb-js/compass-schema'; /** * The lowest supported version. @@ -43,7 +44,8 @@ export function configureStore( | 'logger' | 'track' | 'connectionInfoRef' - > + >, + analyzeSchema = compassAnalyzeSchema ) { const rulesGenerationAbortControllerRef = { current: undefined, @@ -58,6 +60,7 @@ export function configureStore( thunk.withExtraArgument({ ...services, rulesGenerationAbortControllerRef, + analyzeSchema, }) ) ); @@ -78,7 +81,8 @@ export function onActivated( workspaces, track, }: SchemaValidationServices, - { on, cleanup, addCleanup }: ActivateHelpers + { on, cleanup, addCleanup }: ActivateHelpers, + analyzeSchema?: typeof compassAnalyzeSchema ) { const store = configureStore( { @@ -100,7 +104,8 @@ export function onActivated( workspaces, logger, track, - } + }, + analyzeSchema ); // isWritable can change later From dcdb7106af3b965d373f18dcffa7e47ba8ebc97d Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 16:45:47 +0100 Subject: [PATCH 09/13] cleanup only --- .../compass-e2e-tests/tests/collection-validation-tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts index 80d7c28965e..25c155a2629 100644 --- a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts @@ -58,7 +58,7 @@ describe('Collection validation tab', function () { await browser.setFeature('enableExportSchema', true); }); - it.only('provides users with a button to generate rules', async function () { + it('provides users with a button to generate rules', async function () { await browser.clickVisible(Selectors.GenerateValidationRulesButton); const editor = browser.$(Selectors.ValidationEditor); await editor.waitForDisplayed(); From a4b51c17c1130677965ecad2c3cc69c3101e9b3b Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 7 Mar 2025 16:56:08 +0100 Subject: [PATCH 10/13] fix deps and tests --- package-lock.json | 4 ++++ packages/compass-schema-validation/package.json | 2 ++ packages/compass-schema-validation/src/stores/store.spec.ts | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index e5cd70457b2..dca3b88211c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46474,6 +46474,7 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", + "@mongodb-js/compass-schema": "^6.51.5", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "bson": "^6.10.3", @@ -46481,6 +46482,7 @@ "hadron-app-registry": "^9.4.5", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", + "mongodb": "^6.14.1", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "react": "^17.0.2", @@ -58055,6 +58057,7 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", + "@mongodb-js/compass-schema": "^6.51.5", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "@mongodb-js/eslint-config-compass": "^1.3.5", @@ -58076,6 +58079,7 @@ "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", + "mongodb": "^6.14.1", "mongodb-instance-model": "^12.26.5", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index d16513c5dfc..62ed6d995b1 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -77,6 +77,7 @@ "@mongodb-js/compass-editor": "^0.36.5", "@mongodb-js/compass-field-store": "^9.25.5", "@mongodb-js/compass-logging": "^1.6.5", + "@mongodb-js/compass-schema": "^6.51.5", "@mongodb-js/compass-telemetry": "^1.4.5", "@mongodb-js/compass-workspaces": "^0.31.5", "bson": "^6.10.3", @@ -84,6 +85,7 @@ "hadron-app-registry": "^9.4.5", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", + "mongodb": "^6.14.1", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "react": "^17.0.2", diff --git a/packages/compass-schema-validation/src/stores/store.spec.ts b/packages/compass-schema-validation/src/stores/store.spec.ts index dbab08b4731..07b25d15a20 100644 --- a/packages/compass-schema-validation/src/stores/store.spec.ts +++ b/packages/compass-schema-validation/src/stores/store.spec.ts @@ -77,7 +77,11 @@ const getMockedStore = async (analyzeSchema: any) => { }; const schemaAccessor = { - getMongoDBJsonSchema: () => ({ required: ['prop1'] }), + getMongoDBJsonSchema: () => { + return new Promise((resolve) => { + setTimeout(() => resolve({ required: ['prop1'] }), 100); // waiting to give abort a chance + }); + }, }; describe('Schema Validation Store', function () { From 06d68c4aa2f925a84fdf5ee0878fee8be3b375f4 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 10 Mar 2025 12:25:12 +0100 Subject: [PATCH 11/13] fix e2e test --- .../compass-e2e-tests/tests/collection-validation-tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts index 25c155a2629..97d10d3c9f7 100644 --- a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts @@ -86,7 +86,7 @@ describe('Collection validation tab', function () { }); // generated rules can be edited and saved - await browser.setValidation('{ $jsonSchema: { "blah": "string" } }'); + await browser.setValidation(PASSING_VALIDATOR); }); }); From eb9c038c8122d87f1c3d02f525e6a9f7b55178a5 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 10 Mar 2025 12:27:54 +0100 Subject: [PATCH 12/13] update timeout message --- .../src/components/validation-states.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-schema-validation/src/components/validation-states.tsx b/packages/compass-schema-validation/src/components/validation-states.tsx index b02d552529d..ca31f5f47da 100644 --- a/packages/compass-schema-validation/src/components/validation-states.tsx +++ b/packages/compass-schema-validation/src/components/validation-states.tsx @@ -156,7 +156,7 @@ const RulesGenerationErrorBanner: React.FunctionComponent<{ Date: Mon, 10 Mar 2025 12:58:01 +0100 Subject: [PATCH 13/13] only set preferences for desktop tests --- .../tests/collection-validation-tab.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts index 97d10d3c9f7..79c5358cc38 100644 --- a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts @@ -9,6 +9,7 @@ import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; import { createNumbersCollection } from '../helpers/insert-data'; import { expect } from 'chai'; +import { isTestingDesktop } from '../helpers/test-runner-context'; const NO_PREVIEW_DOCUMENTS = 'No Preview Documents'; const PASSING_VALIDATOR = '{ $jsonSchema: {} }'; @@ -55,7 +56,9 @@ describe('Collection validation tab', function () { context('when the schema validation is empty', function () { before(async function () { - await browser.setFeature('enableExportSchema', true); + if (isTestingDesktop()) { + await browser.setFeature('enableExportSchema', true); + } }); it('provides users with a button to generate rules', async function () {