From 4dd81167dd05f7f3fcdf4326a4b5acd64b55e05a Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 23 Jan 2025 13:54:12 +0100 Subject: [PATCH 01/10] wip --- .../src/components/shard-zones-table.tsx | 6 +- .../compass-schema/src/actions/index.spec.ts | 9 - packages/compass-schema/src/actions/index.ts | 38 -- .../src/modules/schema-analysis.ts | 2 + packages/compass-schema/src/stores/reducer.ts | 343 +++++++++++++++++ packages/compass-schema/src/stores/store.ts | 356 ++---------------- packages/data-service/src/data-service.ts | 8 +- 7 files changed, 378 insertions(+), 384 deletions(-) delete mode 100644 packages/compass-schema/src/actions/index.spec.ts delete mode 100644 packages/compass-schema/src/actions/index.ts create mode 100644 packages/compass-schema/src/stores/reducer.ts diff --git a/packages/compass-global-writes/src/components/shard-zones-table.tsx b/packages/compass-global-writes/src/components/shard-zones-table.tsx index f30352f4c44..61042b62233 100644 --- a/packages/compass-global-writes/src/components/shard-zones-table.tsx +++ b/packages/compass-global-writes/src/components/shard-zones-table.tsx @@ -104,10 +104,14 @@ export function ShardZonesTable({ expanded, }, onGlobalFilterChange: setSearchText, - onExpandedChange: setExpanded, + onExpandedChange: (expanded) => { + setExpanded(expanded); + console.log('new expanded', expanded); + }, enableGlobalFilter: true, getFilteredRowModel: getFilteredRowModel(), getIsRowExpanded: (row) => { + console.log('getIsRowExpanded', hasFilteredChildren(row)); return ( (searchText && hasFilteredChildren(row)) || (typeof expanded !== 'boolean' && expanded[row.id]) diff --git a/packages/compass-schema/src/actions/index.spec.ts b/packages/compass-schema/src/actions/index.spec.ts deleted file mode 100644 index bfaf8dab6a2..00000000000 --- a/packages/compass-schema/src/actions/index.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { expect } from 'chai'; - -import { configureActions } from '.'; - -describe('#configureActions', function () { - it('returns a new instance of the reflux actions', function () { - expect(configureActions().startAnalysis).to.not.equal(undefined); - }); -}); diff --git a/packages/compass-schema/src/actions/index.ts b/packages/compass-schema/src/actions/index.ts deleted file mode 100644 index 41c90013e98..00000000000 --- a/packages/compass-schema/src/actions/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Reflux from 'reflux'; - -/** - * Need to create an instance of actions for each store. - * - * @returns {Actions} - The actions. - */ -export const configureActions = () => { - return Reflux.createActions({ - /** - * starts schema analysis with the current query - */ - startAnalysis: { sync: false }, - /** - * stops schema analysis - */ - stopAnalysis: { sync: true }, - /** - * Reset store - */ - reset: { sync: false }, - /** - * set new maxTimeMS value - */ - setMaxTimeMS: { sync: true }, - /** - * reset maxTimeMS value to default - */ - resetMaxTimeMS: { sync: true }, - /** - * Resize the minicharts. - */ - resizeMiniCharts: { sync: true }, - geoLayerAdded: { sync: true }, - geoLayersEdited: { sync: true }, - geoLayersDeleted: { sync: true }, - }); -}; diff --git a/packages/compass-schema/src/modules/schema-analysis.ts b/packages/compass-schema/src/modules/schema-analysis.ts index a2c5ca1aa75..2247bc9f158 100644 --- a/packages/compass-schema/src/modules/schema-analysis.ts +++ b/packages/compass-schema/src/modules/schema-analysis.ts @@ -55,6 +55,8 @@ export const analyzeSchema = async ( ns, }); + console.log({ aggregateOptions }); + const docs = await dataService.sample( ns, query, diff --git a/packages/compass-schema/src/stores/reducer.ts b/packages/compass-schema/src/stores/reducer.ts new file mode 100644 index 00000000000..dffe741a6bc --- /dev/null +++ b/packages/compass-schema/src/stores/reducer.ts @@ -0,0 +1,343 @@ +import type { Schema } from 'mongodb-schema'; +import type { Action, AnyAction, Reducer } from 'redux'; +import type { ThunkAction } from 'redux-thunk'; +import { type AnalysisState } from '../constants/analysis-states'; +import { + ANALYSIS_STATE_ANALYZING, + ANALYSIS_STATE_COMPLETE, + ANALYSIS_STATE_ERROR, + ANALYSIS_STATE_INITIAL, + ANALYSIS_STATE_TIMEOUT, +} from '../constants/analysis-states'; + +import type { Query } from '@mongodb-js/compass-query-bar'; +import type { InternalLayer } from '../modules/geo'; +import { addLayer, generateGeoQuery } from '../modules/geo'; +import { + analyzeSchema, + calculateSchemaDepth, + schemaContainsGeoData, +} from '../modules/schema-analysis'; +import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model/provider'; +import { openToast } from '@mongodb-js/compass-components'; +import type { Circle, Layer, LayerGroup, Polygon } from 'leaflet'; +import { mongoLogId } from '@mongodb-js/compass-logging/provider'; + +const DEFAULT_SAMPLE_SIZE = 1000; + +const ERROR_CODE_MAX_TIME_MS_EXPIRED = 50; + +export function isAction( + action: AnyAction, + type: A['type'] +): action is A { + return action.type === type; +} + +export type SchemaState = { + analysisState: AnalysisState; + errorMessage: string; + schema: Schema | null; + resultId: number; + abortController: undefined | AbortController; + ns: string; + geoLayers: Record; +}; + +export type SchemaThunkAction = ThunkAction< + R, + SchemaState, + any, + A +>; // TODO - any are services + +export const enum SchemaActions { + analysisStarted = 'schema-service/schema/analysisStarted', + analysisFinished = 'schema-service/schema/analysisFinished', + analysisFailed = 'schema-service/schema/analysisFailed', +} + +export type AnalysisStartedAction = { + type: SchemaActions.analysisStarted; +}; + +export type AnalysisFinishedAction = { + type: SchemaActions.analysisFinished; + schema: Schema | null; +}; + +export type AnalysisFailedAction = { + type: SchemaActions.analysisFailed; + error: Error; +}; + +const reducer: Reducer = ( + state = getInitialState(), + action +) => { + if (isAction(action, SchemaActions.analysisStarted)) { + return { + ...state, + analysisState: ANALYSIS_STATE_ANALYZING, + errorMessage: '', + schema: null, + }; + } + + if ( + isAction(action, SchemaActions.analysisFinished) + ) { + return { + ...state, + analysisState: action.schema + ? ANALYSIS_STATE_COMPLETE + : ANALYSIS_STATE_INITIAL, + schema: action.schema, + resultId: resultId(), + }; + } + + if (isAction(action, SchemaActions.analysisFailed)) { + return { + ...state, + ...getErrorState(action.error), + resultId: resultId(), + }; + } + + return state; +}; + +function getErrorState(err: Error & { code?: number }) { + const errorMessage = (err && err.message) || 'Unknown error'; + const errorCode = err && err.code; + + let analysisState: AnalysisState; + + if (errorCode === ERROR_CODE_MAX_TIME_MS_EXPIRED) { + analysisState = ANALYSIS_STATE_TIMEOUT; + } else { + analysisState = ANALYSIS_STATE_ERROR; + } + + return { analysisState, errorMessage }; +} + +function resultId(): number { + return Math.floor(Math.random() * 2 ** 53); +} + +export const handleSchemaShare = (): SchemaThunkAction => { + return (dispatch, getState) => { + const { schema, ns } = getState(); + void navigator.clipboard.writeText(JSON.stringify(schema, null, ' ')); + const hasSchema = schema !== null; + dispatch(_trackSchemaShared(hasSchema)); + openToast( + 'share-schema', + hasSchema + ? { + variant: 'success', + title: 'Schema Copied', + description: `The schema definition of ${ns} has been copied to your clipboard in JSON format.`, + timeout: 5_000, + } + : { + variant: 'warning', + title: 'Analyze Schema First', + description: 'Please Analyze the Schema First from the Schema Tab.', + timeout: 5_000, + } + ); + }; +}; + +export const _trackSchemaShared = ( + hasSchema: boolean +): SchemaThunkAction => { + return (dispatch, getState, { track, connectionInfoRef }) => { + const { schema } = getState(); + // Use a function here to a) ensure that the calculations here + // are only made when telemetry is enabled and b) that errors from + // those calculations are caught and logged rather than displayed to + // users as errors from the core schema sharing logic. + const trackEvent = () => ({ + has_schema: hasSchema, + schema_width: schema?.fields?.length ?? 0, + schema_depth: schema ? calculateSchemaDepth(schema) : 0, + geo_data: schema ? schemaContainsGeoData(schema) : false, + }); + track('Schema Exported', trackEvent, connectionInfoRef.current); + }; +}; + +/** + * @return {Object} initial schema state. + */ +const getInitialState = (): SchemaState => ({ + analysisState: ANALYSIS_STATE_INITIAL, + errorMessage: '', + schema: null, + resultId: resultId(), + ns: undefined, // TODO: where is NS coming from + geoLayers: {}, +}); + +// // TODO: what to do with layers?? +// onSchemaSampled (this: SchemaStore) { +// this.geoLayers = {}; +// } + +// geoLayerAdded( +// this: SchemaStore, +// field: string, +// layer: Layer, +// // NB: reflux doesn't return values from actions so we have to pass +// // component onChage as a callback +// onAdded: (geoQuery: ReturnType) => void +// ) { +// this.geoLayers = addLayer( +// field, +// layer as Circle | Polygon, +// this.geoLayers +// ); +// onAdded(generateGeoQuery(this.geoLayers)); +// }, + +// geoLayersEdited( +// this: SchemaStore, +// field: string, +// layers: LayerGroup, +// // NB: reflux doesn't return values from actions so we have to pass +// // component onChage as a callback +// onEdited: (geoQuery: ReturnType) => void +// ) { +// layers.eachLayer((layer) => { +// this.geoLayerAdded(field, layer, () => { +// // noop, we will call `onEdited` when we're done with updates +// }); +// }); +// onEdited(generateGeoQuery(this.geoLayers)); +// }, + +// geoLayersDeleted( +// this: SchemaStore, +// layers: LayerGroup, +// // NB: reflux doesn't return values from actions so we have to pass +// // component onChage as a callback +// onDeleted: (geoQuery: ReturnType) => void +// ) { +// layers.eachLayer((layer) => { +// delete this.geoLayers[(layer as any)._leaflet_id]; +// }); +// onDeleted(generateGeoQuery(this.geoLayers)); +// }, + +// stopAnalysis(this: SchemaStore) { +// this.state.abortController?.abort(); +// }, + +export const _trackSchemaAnalyzed = ( + analysisTimeMS: number, + query: Query +): SchemaThunkAction => { + return (dispatch, getState, { track, connectionInfoRef }) => { + const { schema } = getState(); + // Use a function here to a) ensure that the calculations here + // are only made when telemetry is enabled and b) that errors from + // those calculations are caught and logged rather than displayed to + // users as errors from the core schema analysis logic. + const trackEvent = () => ({ + with_filter: Object.entries(query.filter ?? {}).length > 0, + schema_width: schema?.fields?.length ?? 0, + schema_depth: schema ? calculateSchemaDepth(schema) : 0, + geo_data: schema ? schemaContainsGeoData(schema) : false, + analysis_time_ms: analysisTimeMS, + }); + track('Schema Analyzed', trackEvent, connectionInfoRef.current); + }; +}; + +export const startAnalysis = (): SchemaThunkAction< + Promise, + AnalysisStartedAction | AnalysisFinishedAction | AnalysisFailedAction +> => { + return async ( + dispatch, + getState, + { + queryBar, + preferences, + debug, + dataService, + logger, + fieldStoreService, + log, + } + ) => { + const query = queryBar.getLastAppliedQuery('schema'); + const { ns } = getState(); + + const sampleSize = query.limit + ? Math.min(DEFAULT_SAMPLE_SIZE, query.limit) + : DEFAULT_SAMPLE_SIZE; + + const samplingOptions = { + query: query.filter ?? {}, + size: sampleSize, + fields: query.project ?? undefined, + }; + + const driverOptions = { + maxTimeMS: capMaxTimeMSAtPreferenceLimit(preferences, query.maxTimeMS), + }; + + try { + debug('analysis started'); + + const abortController = new AbortController(); + const abortSignal = abortController.signal; + + dispatch({ type: SchemaActions.analysisStarted }); + + abortSignal?.addEventListener('abort', () => { + dispatch(stopAnalysis(abortSignal.reason)); + }); + + const analysisStartTime = Date.now(); + const schema = await analyzeSchema( + dataService, + abortSignal, + ns, + samplingOptions, + driverOptions, + logger + ); + const analysisTime = Date.now() - analysisStartTime; + + if (schema !== null) { + fieldStoreService.updateFieldsFromSchema(ns, schema); + } + + dispatch({ type: SchemaActions.analysisFinished, schema }); + + _trackSchemaAnalyzed(analysisTime, query); + + // this.onSchemaSampled(); // TODO: geoLayers + } catch (err: any) { + log.error( + mongoLogId(1_001_000_188), + 'Schema analysis', + 'Error sampling schema', + { + error: err.stack, + } + ); + dispatch({ type: SchemaActions.analysisFailed, error: err as Error }); + } finally { + // this.setState({ abortController: undefined }); // TODO: Abort controller + } + }; +}; + +export default reducer; diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index 9377d80a032..2947fb8198f 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -1,24 +1,6 @@ -import Reflux from 'reflux'; -import type { StoreWithStateMixin } from '@mongodb-js/reflux-state-mixin'; -import StateMixin from '@mongodb-js/reflux-state-mixin'; import type { Logger } from '@mongodb-js/compass-logging'; -import type { InternalLayer } from '../modules/geo'; -import { addLayer, generateGeoQuery } from '../modules/geo'; -import { - analyzeSchema, - calculateSchemaDepth, - schemaContainsGeoData, -} from '../modules/schema-analysis'; -import type { AnalysisState } from '../constants/analysis-states'; -import { - ANALYSIS_STATE_ANALYZING, - ANALYSIS_STATE_COMPLETE, - ANALYSIS_STATE_ERROR, - ANALYSIS_STATE_INITIAL, - ANALYSIS_STATE_TIMEOUT, -} from '../constants/analysis-states'; -import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model/provider'; -import { openToast } from '@mongodb-js/compass-components'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { ConnectionInfoRef, @@ -26,36 +8,11 @@ import type { } from '@mongodb-js/compass-connections/provider'; import type { ActivateHelpers } from 'hadron-app-registry'; import type AppRegistry from 'hadron-app-registry'; -import { configureActions } from '../actions'; -import type { Circle, Layer, LayerGroup, Polygon } from 'leaflet'; -import type { Schema } from 'mongodb-schema'; import type { PreferencesAccess } from 'compass-preferences-model/provider'; import type { FieldStoreService } from '@mongodb-js/compass-field-store'; -import type { Query, QueryBarService } from '@mongodb-js/compass-query-bar'; +import type { QueryBarService } from '@mongodb-js/compass-query-bar'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; - -const DEFAULT_SAMPLE_SIZE = 1000; - -const ERROR_CODE_MAX_TIME_MS_EXPIRED = 50; - -function getErrorState(err: Error & { code?: number }) { - const errorMessage = (err && err.message) || 'Unknown error'; - const errorCode = err && err.code; - - let analysisState; - - if (errorCode === ERROR_CODE_MAX_TIME_MS_EXPIRED) { - analysisState = ANALYSIS_STATE_TIMEOUT; - } else { - analysisState = ANALYSIS_STATE_ERROR; - } - - return { analysisState, errorMessage }; -} - -function resultId(): number { - return Math.floor(Math.random() * 2 ** 53); -} +import { reducer } from './reducer'; export type DataService = Pick; export type SchemaPluginServices = { @@ -69,312 +26,43 @@ export type SchemaPluginServices = { fieldStoreService: FieldStoreService; queryBar: QueryBarService; }; - -type SchemaState = { - analysisState: AnalysisState; - errorMessage: string; - schema: Schema | null; - resultId: number; - abortController: undefined | AbortController; -}; - -export type SchemaStore = StoreWithStateMixin & { - localAppRegistry: SchemaPluginServices['localAppRegistry']; - globalAppRegistry: SchemaPluginServices['globalAppRegistry']; - fieldStoreService: SchemaPluginServices['fieldStoreService']; - ns: string; - geoLayers: Record; - dataService: DataService; - - handleSchemaShare(): void; - _trackSchemaShared(hasSchema: boolean): void; - - onSchemaSampled(): void; - geoLayerAdded( - field: string, - layer: Layer, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onAdded: (geoQuery: ReturnType) => void - ): void; - geoLayersEdited( - field: string, - layers: LayerGroup, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onEdited: (geoQuery: ReturnType) => void - ): void; - geoLayersDeleted( - layers: LayerGroup, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onDeleted: (geoQuery: ReturnType) => void - ): void; - stopAnalysis(): void; - _trackSchemaAnalyzed(analysisTimeMS: number, query: any): void; - startAnalysis(): void; -}; - /** * Configure a store with the provided options. * * @param {Object} options - The options. * - * @returns {Store} The reflux store. + * @returns {Store} The redux store. */ export function activateSchemaPlugin( options: Pick, - { - dataService, - localAppRegistry, - globalAppRegistry, - logger, - track, - preferences, - fieldStoreService, - queryBar, - connectionInfoRef, - }: SchemaPluginServices, + services: SchemaPluginServices, { on, cleanup }: ActivateHelpers ) { - const { debug, log, mongoLogId } = logger; - const actions = configureActions(); - - /** - * The reflux store for the schema. - */ - const store: SchemaStore = Reflux.createStore({ - mixins: [StateMixin.store()], - listenables: actions, - - /** - * Initialize the document list store. - */ - init: function (this: SchemaStore) { - this.ns = options.namespace; - this.geoLayers = {}; - this.dataService = dataService; - this.localAppRegistry = localAppRegistry; - this.globalAppRegistry = globalAppRegistry; - this.fieldStoreService = fieldStoreService; - }, - - handleSchemaShare(this: SchemaStore) { - void navigator.clipboard.writeText( - JSON.stringify(this.state.schema, null, ' ') - ); - const hasSchema = this.state.schema !== null; - this._trackSchemaShared(hasSchema); - openToast( - 'share-schema', - hasSchema - ? { - variant: 'success', - title: 'Schema Copied', - description: `The schema definition of ${this.ns} has been copied to your clipboard in JSON format.`, - timeout: 5_000, - } - : { - variant: 'warning', - title: 'Analyze Schema First', - description: - 'Please Analyze the Schema First from the Schema Tab.', - timeout: 5_000, - } - ); - }, - - _trackSchemaShared(this: SchemaStore, hasSchema: boolean) { - const { schema } = this.state; - // Use a function here to a) ensure that the calculations here - // are only made when telemetry is enabled and b) that errors from - // those calculations are caught and logged rather than displayed to - // users as errors from the core schema sharing logic. - const trackEvent = () => ({ - has_schema: hasSchema, - schema_width: schema?.fields?.length ?? 0, - schema_depth: schema ? calculateSchemaDepth(schema) : 0, - geo_data: schema ? schemaContainsGeoData(schema) : false, - }); - track('Schema Exported', trackEvent, connectionInfoRef.current); - }, - - /** - * Initialize the schema store. - * - * @return {Object} initial schema state. - */ - getInitialState(this: SchemaStore): SchemaState { - return { - analysisState: ANALYSIS_STATE_INITIAL, - errorMessage: '', - schema: null, - resultId: resultId(), - abortController: undefined, - }; - }, - - onSchemaSampled(this: SchemaStore) { - this.geoLayers = {}; - }, - - geoLayerAdded( - this: SchemaStore, - field: string, - layer: Layer, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onAdded: (geoQuery: ReturnType) => void - ) { - this.geoLayers = addLayer( - field, - layer as Circle | Polygon, - this.geoLayers - ); - onAdded(generateGeoQuery(this.geoLayers)); - }, - - geoLayersEdited( - this: SchemaStore, - field: string, - layers: LayerGroup, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onEdited: (geoQuery: ReturnType) => void - ) { - layers.eachLayer((layer) => { - this.geoLayerAdded(field, layer, () => { - // noop, we will call `onEdited` when we're done with updates - }); - }); - onEdited(generateGeoQuery(this.geoLayers)); - }, - - geoLayersDeleted( - this: SchemaStore, - layers: LayerGroup, - // NB: reflux doesn't return values from actions so we have to pass - // component onChage as a callback - onDeleted: (geoQuery: ReturnType) => void - ) { - layers.eachLayer((layer) => { - delete this.geoLayers[(layer as any)._leaflet_id]; - }); - onDeleted(generateGeoQuery(this.geoLayers)); - }, - - stopAnalysis(this: SchemaStore) { - this.state.abortController?.abort(); - }, - - _trackSchemaAnalyzed( - this: SchemaStore, - analysisTimeMS: number, - query: Query - ) { - const { schema } = this.state; - // Use a function here to a) ensure that the calculations here - // are only made when telemetry is enabled and b) that errors from - // those calculations are caught and logged rather than displayed to - // users as errors from the core schema analysis logic. - const trackEvent = () => ({ - with_filter: Object.entries(query.filter ?? {}).length > 0, - schema_width: schema?.fields?.length ?? 0, - schema_depth: schema ? calculateSchemaDepth(schema) : 0, - geo_data: schema ? schemaContainsGeoData(schema) : false, - analysis_time_ms: analysisTimeMS, - }); - track('Schema Analyzed', trackEvent, connectionInfoRef.current); - }, - - startAnalysis: async function (this: SchemaStore) { - const query = queryBar.getLastAppliedQuery('schema'); - - const sampleSize = query.limit - ? Math.min(DEFAULT_SAMPLE_SIZE, query.limit) - : DEFAULT_SAMPLE_SIZE; - - const samplingOptions = { - query: query.filter ?? {}, - size: sampleSize, - fields: query.project ?? undefined, - }; - - const driverOptions = { - maxTimeMS: capMaxTimeMSAtPreferenceLimit(preferences, query.maxTimeMS), - }; - - try { - debug('analysis started'); - - const abortController = new AbortController(); - const abortSignal = abortController.signal; - - this.setState({ - analysisState: ANALYSIS_STATE_ANALYZING, - errorMessage: '', - schema: null, - abortController, - }); - - const analysisStartTime = Date.now(); - const schema = await analyzeSchema( - this.dataService, - abortSignal, - this.ns, - samplingOptions, - driverOptions, - logger - ); - const analysisTime = Date.now() - analysisStartTime; - - if (schema !== null) { - this.fieldStoreService.updateFieldsFromSchema(this.ns, schema); - } - - this.setState({ - analysisState: schema - ? ANALYSIS_STATE_COMPLETE - : ANALYSIS_STATE_INITIAL, - schema: schema, - resultId: resultId(), - }); - - this._trackSchemaAnalyzed(analysisTime, query); - - this.onSchemaSampled(); - } catch (err: any) { - log.error( - mongoLogId(1_001_000_188), - 'Schema analysis', - 'Error sampling schema', - { - error: err.stack, - } - ); - this.setState({ ...getErrorState(err), resultId: resultId() }); - } finally { - this.setState({ abortController: undefined }); - } - }, - - storeDidUpdate(this: SchemaStore, prevState: SchemaState) { - debug('schema store changed from', prevState, 'to', this.state); - }, - }) as SchemaStore; - + const store = configureStore(services); /** * When `Share Schema as JSON` clicked in menu show a dialog message. */ - on(localAppRegistry, 'menu-share-schema-json', () => - store.handleSchemaShare() + + on( + services.localAppRegistry, + 'menu-share-schema-json', + () => store.dispatch(handleSchemaShare()) // TODO: get the action ); return { store, - actions, deactivate() { cleanup(); }, }; } + +export function configureStore(services: SchemaPluginServices) { + const store = createStore( + reducer, + applyMiddleware(thunk.withExtraArgument(services)) + ); + return store; +} + +export type AtlasServiceStore = ReturnType; diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index 195046b6776..60965253dd4 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -3,7 +3,7 @@ import type { Tunnel, } from '@mongodb-js/devtools-proxy-support'; import { EventEmitter } from 'events'; -import { ExplainVerbosity, ClientEncryption } from 'mongodb'; +import { ExplainVerbosity, ClientEncryption, ReadPreference } from 'mongodb'; import type { AggregateOptions, AggregationCursor, @@ -1782,6 +1782,7 @@ class DataServiceImpl extends WithLogContext implements DataService { let cursor: AggregationCursor; return this._cancellableOperation( async (session?: ClientSession) => { + console.log({ options }); cursor = this._collection(ns, 'CRUD').aggregate(pipeline, { ...options, session, @@ -2204,6 +2205,7 @@ class DataServiceImpl extends WithLogContext implements DataService { }); } + // TODO now return this.aggregate( ns, pipeline, @@ -2622,7 +2624,9 @@ class DataServiceImpl extends WithLogContext implements DataService { private _collection(ns: string, type: ClientType): Collection { return this._initializedClient(type) .db(this._databaseName(ns)) - .collection(this._collectionName(ns)); + .collection(this._collectionName(ns), { + readPreference: ReadPreference.SECONDARY_PREFERRED, + }); } /** From 9598f2621739ce6c3cf88cb9eaac6e073150d670 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 23 Jan 2025 17:26:39 +0100 Subject: [PATCH 02/10] wip --- .../src/components/compass-schema.tsx | 55 +++--- .../coordinates-minichart.jsx | 28 +++- .../compass-schema/src/components/field.tsx | 7 +- .../src/components/minichart/minichart.jsx | 7 +- .../src/modules/schema-analysis.ts | 2 - packages/compass-schema/src/stores/reducer.ts | 157 +++++++++--------- packages/compass-schema/src/stores/store.ts | 45 ++++- 7 files changed, 171 insertions(+), 130 deletions(-) diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index 3f2cee3b180..a03bf029aee 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -1,5 +1,6 @@ import React, { useCallback } from 'react'; - +import type { Schema as MongodbSchema } from 'mongodb-schema'; +import { connect } from 'react-redux'; import type { AnalysisState } from '../constants/analysis-states'; import { ANALYSIS_STATE_INITIAL, @@ -29,12 +30,13 @@ import { Badge, Icon, } from '@mongodb-js/compass-components'; -import type { configureActions } from '../actions'; import { usePreference } from 'compass-preferences-model/provider'; import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasPerformanceAdvisorLink } from '../utils'; import { useIsLastAppliedQueryOutdated } from '@mongodb-js/compass-query-bar'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; +import type { RootState } from '../stores/store'; +import { startAnalysis, stopAnalysis } from '../stores/reducer'; const rootStyles = css({ width: '100%', @@ -296,10 +298,9 @@ const AnalyzingScreen: React.FunctionComponent<{ }; const FieldList: React.FunctionComponent<{ - schema: any; + schema: MongodbSchema | null; analysisState: AnalysisState; - actions: Record; -}> = ({ schema, analysisState, actions }) => { +}> = ({ schema, analysisState }) => { const darkMode = useDarkMode(); if (analysisState !== ANALYSIS_STATE_COMPLETE) { @@ -327,7 +328,7 @@ const FieldList: React.FunctionComponent<{ >
{fields.map((field: any) => ( - + ))}
@@ -366,25 +367,26 @@ const PerformanceAdvisorBanner = () => { }; const Schema: React.FunctionComponent<{ - actions: ReturnType; analysisState: AnalysisState; errorMessage?: string; maxTimeMS?: number; - schema?: any; + schema: MongodbSchema | null; count?: number; - resultId?: string; -}> = ({ actions, analysisState, errorMessage, schema, resultId }) => { + resultId?: number; + startAnalysis: () => Promise; + stopAnalysis: () => void; +}> = ({ analysisState, errorMessage, schema, resultId }) => { const onApplyClicked = useCallback(() => { - actions.startAnalysis(); - }, [actions]); + startAnalysis(); + }, []); const onCancelClicked = useCallback(() => { - actions.stopAnalysis(); - }, [actions]); + stopAnalysis(); + }, []); const onResetClicked = useCallback(() => { - actions.startAnalysis(); - }, [actions]); + startAnalysis(); + }, []); const outdated = useIsLastAppliedQueryOutdated('schema'); @@ -403,7 +405,7 @@ const Schema: React.FunctionComponent<{ errorMessage={errorMessage || ''} isOutdated={!!outdated} sampleSize={schema ? schema.count : 0} - schemaResultId={resultId || ''} + schemaResultId={String(resultId) || ''} /> } > @@ -416,11 +418,7 @@ const Schema: React.FunctionComponent<{ )} {analysisState === ANALYSIS_STATE_COMPLETE && ( - + )} @@ -428,4 +426,15 @@ const Schema: React.FunctionComponent<{ ); }; -export default Schema; +export default connect( + (state: RootState) => ({ + analysisState: state.analysisState, + errorMessage: state.errorMessage, + schema: state.schema, + resultId: state.resultId, + }), + { + onStartAnalysis: startAnalysis, + onStopAnalysis: stopAnalysis, + } +)(Schema); diff --git a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx index 77378b443cf..45536eeeaee 100644 --- a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx +++ b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx @@ -1,5 +1,6 @@ import React, { PureComponent, useCallback } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; import L from 'leaflet'; @@ -17,6 +18,11 @@ import GeoscatterMapItem from './marker'; import { LIGHTMODE_TILE_URL, DARKMODE_TILE_URL } from './constants'; import { getHereAttributionMessage } from './utils'; import { debounce } from 'lodash'; +import { + geoLayerAdded, + geoLayersDeleted, + geoLayersEdited, +} from '../../stores/reducer'; // TODO: Disable boxZoom handler for circle lasso. // @@ -124,10 +130,12 @@ class UnthemedCoordinatesMinichart extends PureComponent { unique: PropTypes.number, values: PropTypes.array, }), - actions: PropTypes.object.isRequired, fieldName: PropTypes.string.isRequired, darkMode: PropTypes.bool, onGeoQueryChanged: PropTypes.func.isRequired, + geoLayerAdded: PropTypes.func.isRequired, + geoLayersEdited: PropTypes.func.isRequired, + geoLayersDeleted: PropTypes.func.isRequired, }; state = { @@ -239,7 +247,7 @@ class UnthemedCoordinatesMinichart extends PureComponent { } onCreated = (evt) => { - this.props.actions.geoLayerAdded( + this.props.geoLayerAdded( this.props.fieldName, evt.layer, this.props.onGeoQueryChanged @@ -247,7 +255,7 @@ class UnthemedCoordinatesMinichart extends PureComponent { }; onEdited = (evt) => { - this.props.actions.geoLayersEdited( + this.props.geoLayersEdited( this.props.fieldName, evt.layers, this.props.onGeoQueryChanged @@ -255,10 +263,7 @@ class UnthemedCoordinatesMinichart extends PureComponent { }; onDeleted = (evt) => { - this.props.actions.geoLayersDeleted( - evt.layers, - this.props.onGeoQueryChanged - ); + this.props.geoLayersDeleted(evt.layers, this.props.onGeoQueryChanged); }; /** @@ -328,5 +333,10 @@ CoordinatesMinichart.propTypes = { onQueryChanged: PropTypes.func, }; -export default CoordinatesMinichart; -export { CoordinatesMinichart }; +const ConnectedCoordinatesMinichart = connect(() => {}, { + geoLayerAdded, + geoLayersEdited, + geoLayersDeleted, +})(CoordinatesMinichart); + +export default ConnectedCoordinatesMinichart; diff --git a/packages/compass-schema/src/components/field.tsx b/packages/compass-schema/src/components/field.tsx index 20f46a39f42..ac7fe4f40a2 100644 --- a/packages/compass-schema/src/components/field.tsx +++ b/packages/compass-schema/src/components/field.tsx @@ -21,7 +21,6 @@ import type { import { FieldType, sortTypes } from './type'; import Minichart from './minichart'; import detectCoordinates from '../modules/detect-coordinates'; -import type { configureActions } from '../actions'; import { useQueryBarQuery } from '@mongodb-js/compass-query-bar'; import { useChangeQueryBarQuery } from '@mongodb-js/compass-query-bar'; @@ -110,7 +109,6 @@ const nestedFieldStyles = css({ }); type FieldProps = { - actions: ReturnType; name: string; path: string[]; types: SchemaType[]; @@ -196,7 +194,7 @@ export function shouldShowUnboundArrayInsight( ); } -function Field({ actions, name, path, types, enableMaps }: FieldProps) { +function Field({ name, path, types, enableMaps }: FieldProps) { const query = useQueryBarQuery(); const changeQuery = useChangeQueryBarQuery(); const [isExpanded, setIsExpanded] = useState(false); @@ -308,7 +306,6 @@ function Field({ actions, name, path, types, enableMaps }: FieldProps) { fieldValue={query.filter?.[fieldName]} type={activeType} nestedDocType={nestedDocType} - actions={actions} onQueryChanged={debouncedChangeQuery} /> @@ -326,7 +323,7 @@ function Field({ actions, name, path, types, enableMaps }: FieldProps) { {(getNestedDocType(types)?.fields || []).map( (field: SchemaField) => (
- +
) )} diff --git a/packages/compass-schema/src/components/minichart/minichart.jsx b/packages/compass-schema/src/components/minichart/minichart.jsx index e782db7e699..56f6099d48b 100644 --- a/packages/compass-schema/src/components/minichart/minichart.jsx +++ b/packages/compass-schema/src/components/minichart/minichart.jsx @@ -7,6 +7,7 @@ import CoordinatesMinichart from '../coordinates-minichart'; import D3Component from '../d3-component'; import vizFns from '../../modules'; import CONSTANTS from '../../constants/schema'; +import { connect } from 'react-redux'; class MiniChart extends PureComponent { static displayName = 'MiniChartComponent'; @@ -34,8 +35,8 @@ class MiniChart extends PureComponent { // but it is not noticable to the user. this.resizeListener(); window.addEventListener('resize', this.resizeListener); - this.unsubscribeMiniChartResize = - this.props.actions.resizeMiniCharts.listen(this.resizeListener); + // this.unsubscribeMiniChartResize = + // this.props.actions.resizeMiniCharts.listen(this.resizeListener); } componentWillUnmount() { @@ -149,4 +150,4 @@ class MiniChart extends PureComponent { } } -export default MiniChart; +export default connect(() => {}, {})(MiniChart); diff --git a/packages/compass-schema/src/modules/schema-analysis.ts b/packages/compass-schema/src/modules/schema-analysis.ts index 2247bc9f158..a2c5ca1aa75 100644 --- a/packages/compass-schema/src/modules/schema-analysis.ts +++ b/packages/compass-schema/src/modules/schema-analysis.ts @@ -55,8 +55,6 @@ export const analyzeSchema = async ( ns, }); - console.log({ aggregateOptions }); - const docs = await dataService.sample( ns, query, diff --git a/packages/compass-schema/src/stores/reducer.ts b/packages/compass-schema/src/stores/reducer.ts index dffe741a6bc..ff82349465e 100644 --- a/packages/compass-schema/src/stores/reducer.ts +++ b/packages/compass-schema/src/stores/reducer.ts @@ -1,6 +1,5 @@ import type { Schema } from 'mongodb-schema'; import type { Action, AnyAction, Reducer } from 'redux'; -import type { ThunkAction } from 'redux-thunk'; import { type AnalysisState } from '../constants/analysis-states'; import { ANALYSIS_STATE_ANALYZING, @@ -11,7 +10,6 @@ import { } from '../constants/analysis-states'; import type { Query } from '@mongodb-js/compass-query-bar'; -import type { InternalLayer } from '../modules/geo'; import { addLayer, generateGeoQuery } from '../modules/geo'; import { analyzeSchema, @@ -22,6 +20,7 @@ import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model/provide import { openToast } from '@mongodb-js/compass-components'; import type { Circle, Layer, LayerGroup, Polygon } from 'leaflet'; import { mongoLogId } from '@mongodb-js/compass-logging/provider'; +import type { SchemaThunkAction } from './store'; const DEFAULT_SAMPLE_SIZE = 1000; @@ -39,22 +38,13 @@ export type SchemaState = { errorMessage: string; schema: Schema | null; resultId: number; - abortController: undefined | AbortController; - ns: string; - geoLayers: Record; }; -export type SchemaThunkAction = ThunkAction< - R, - SchemaState, - any, - A ->; // TODO - any are services - export const enum SchemaActions { analysisStarted = 'schema-service/schema/analysisStarted', analysisFinished = 'schema-service/schema/analysisFinished', analysisFailed = 'schema-service/schema/analysisFailed', + analysisCancelled = 'schema-service/schema/analysisCancelled', } export type AnalysisStartedAction = { @@ -71,6 +61,10 @@ export type AnalysisFailedAction = { error: Error; }; +export type analysisCancelled = { + type: SchemaActions.analysisCancelled; +}; + const reducer: Reducer = ( state = getInitialState(), action @@ -105,6 +99,13 @@ const reducer: Reducer = ( }; } + if (isAction(action, SchemaActions.analysisCancelled)) { + return { + ...state, + analysisState: ANALYSIS_STATE_INITIAL, + }; + } + return state; }; @@ -128,8 +129,8 @@ function resultId(): number { } export const handleSchemaShare = (): SchemaThunkAction => { - return (dispatch, getState) => { - const { schema, ns } = getState(); + return (dispatch, getState, { namespace }) => { + const { schema } = getState(); void navigator.clipboard.writeText(JSON.stringify(schema, null, ' ')); const hasSchema = schema !== null; dispatch(_trackSchemaShared(hasSchema)); @@ -139,7 +140,7 @@ export const handleSchemaShare = (): SchemaThunkAction => { ? { variant: 'success', title: 'Schema Copied', - description: `The schema definition of ${ns} has been copied to your clipboard in JSON format.`, + description: `The schema definition of ${namespace} has been copied to your clipboard in JSON format.`, timeout: 5_000, } : { @@ -179,68 +180,62 @@ const getInitialState = (): SchemaState => ({ errorMessage: '', schema: null, resultId: resultId(), - ns: undefined, // TODO: where is NS coming from - geoLayers: {}, }); -// // TODO: what to do with layers?? -// onSchemaSampled (this: SchemaStore) { -// this.geoLayers = {}; -// } - -// geoLayerAdded( -// this: SchemaStore, -// field: string, -// layer: Layer, -// // NB: reflux doesn't return values from actions so we have to pass -// // component onChage as a callback -// onAdded: (geoQuery: ReturnType) => void -// ) { -// this.geoLayers = addLayer( -// field, -// layer as Circle | Polygon, -// this.geoLayers -// ); -// onAdded(generateGeoQuery(this.geoLayers)); -// }, - -// geoLayersEdited( -// this: SchemaStore, -// field: string, -// layers: LayerGroup, -// // NB: reflux doesn't return values from actions so we have to pass -// // component onChage as a callback -// onEdited: (geoQuery: ReturnType) => void -// ) { -// layers.eachLayer((layer) => { -// this.geoLayerAdded(field, layer, () => { -// // noop, we will call `onEdited` when we're done with updates -// }); -// }); -// onEdited(generateGeoQuery(this.geoLayers)); -// }, - -// geoLayersDeleted( -// this: SchemaStore, -// layers: LayerGroup, -// // NB: reflux doesn't return values from actions so we have to pass -// // component onChage as a callback -// onDeleted: (geoQuery: ReturnType) => void -// ) { -// layers.eachLayer((layer) => { -// delete this.geoLayers[(layer as any)._leaflet_id]; -// }); -// onDeleted(generateGeoQuery(this.geoLayers)); -// }, - -// stopAnalysis(this: SchemaStore) { -// this.state.abortController?.abort(); -// }, +export const onSchemaSampled = (): SchemaThunkAction => { + return (dispatch, getState, { geoLayersRef }) => { + geoLayersRef.current = {}; + }; +}; + +export const geoLayerAdded = ( + field: string, + layer: Layer +): SchemaThunkAction> => { + return (dispatch, getState, { geoLayersRef }) => { + geoLayersRef.current = addLayer( + field, + layer as Circle | Polygon, + geoLayersRef.current + ); + return generateGeoQuery(geoLayersRef.current); + }; +}; + +export const geoLayersEdited = ( + field: string, + layers: LayerGroup +): SchemaThunkAction> => { + return (dispatch, getState, { geoLayersRef }) => { + layers.eachLayer((layer) => { + dispatch(geoLayerAdded(field, layer)); + }); + return generateGeoQuery(geoLayersRef.current); + }; +}; + +export const geoLayersDeleted = ( + layers: LayerGroup +): SchemaThunkAction> => { + return (dispatch, getState, { geoLayersRef }) => { + layers.eachLayer((layer) => { + delete geoLayersRef.current[(layer as any)._leaflet_id]; + }); + return generateGeoQuery(geoLayersRef.current); + }; +}; + +export const stopAnalysis = (): SchemaThunkAction => { + return (dispatch, getState, { abortControllerRef }) => { + abortControllerRef.current?.abort(); + dispatch({ type: SchemaActions.analysisCancelled }); + }; +}; export const _trackSchemaAnalyzed = ( analysisTimeMS: number, query: Query -): SchemaThunkAction => { +): SchemaThunkAction => { return (dispatch, getState, { track, connectionInfoRef }) => { const { schema } = getState(); // Use a function here to a) ensure that the calculations here @@ -268,15 +263,15 @@ export const startAnalysis = (): SchemaThunkAction< { queryBar, preferences, - debug, + logger: { debug, log }, dataService, logger, fieldStoreService, - log, + abortControllerRef, + namespace, } ) => { const query = queryBar.getLastAppliedQuery('schema'); - const { ns } = getState(); const sampleSize = query.limit ? Math.min(DEFAULT_SAMPLE_SIZE, query.limit) @@ -295,20 +290,20 @@ export const startAnalysis = (): SchemaThunkAction< try { debug('analysis started'); - const abortController = new AbortController(); - const abortSignal = abortController.signal; + abortControllerRef.current = new AbortController(); + const abortSignal = abortControllerRef.current.signal; dispatch({ type: SchemaActions.analysisStarted }); abortSignal?.addEventListener('abort', () => { - dispatch(stopAnalysis(abortSignal.reason)); + dispatch(stopAnalysis()); }); const analysisStartTime = Date.now(); const schema = await analyzeSchema( dataService, abortSignal, - ns, + namespace, samplingOptions, driverOptions, logger @@ -316,14 +311,14 @@ export const startAnalysis = (): SchemaThunkAction< const analysisTime = Date.now() - analysisStartTime; if (schema !== null) { - fieldStoreService.updateFieldsFromSchema(ns, schema); + fieldStoreService.updateFieldsFromSchema(namespace, schema); } dispatch({ type: SchemaActions.analysisFinished, schema }); _trackSchemaAnalyzed(analysisTime, query); - // this.onSchemaSampled(); // TODO: geoLayers + dispatch(onSchemaSampled()); } catch (err: any) { log.error( mongoLogId(1_001_000_188), @@ -335,7 +330,7 @@ export const startAnalysis = (): SchemaThunkAction< ); dispatch({ type: SchemaActions.analysisFailed, error: err as Error }); } finally { - // this.setState({ abortController: undefined }); // TODO: Abort controller + abortControllerRef.current = undefined; } }; }; diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index 2947fb8198f..910d7c724de 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -1,6 +1,6 @@ import type { Logger } from '@mongodb-js/compass-logging'; -import { createStore, applyMiddleware } from 'redux'; -import thunk from 'redux-thunk'; +import { createStore, applyMiddleware, type AnyAction } from 'redux'; +import thunk, { type ThunkAction } from 'redux-thunk'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { ConnectionInfoRef, @@ -12,7 +12,8 @@ import type { PreferencesAccess } from 'compass-preferences-model/provider'; import type { FieldStoreService } from '@mongodb-js/compass-field-store'; import type { QueryBarService } from '@mongodb-js/compass-query-bar'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; -import { reducer } from './reducer'; +import reducer, { handleSchemaShare } from './reducer'; +import type { InternalLayer } from '../modules/geo'; export type DataService = Pick; export type SchemaPluginServices = { @@ -26,6 +27,20 @@ export type SchemaPluginServices = { fieldStoreService: FieldStoreService; queryBar: QueryBarService; }; + +export type RootState = ReturnType; +export type SchemaExtraArgs = SchemaPluginServices & { + abortControllerRef: { current?: AbortController }; + geoLayersRef: { current: Record }; + namespace: string; +}; +export type SchemaThunkAction = ThunkAction< + R, + RootState, + SchemaExtraArgs, + A +>; + /** * Configure a store with the provided options. * @@ -34,11 +49,11 @@ export type SchemaPluginServices = { * @returns {Store} The redux store. */ export function activateSchemaPlugin( - options: Pick, + { namespace }: Pick, services: SchemaPluginServices, { on, cleanup }: ActivateHelpers ) { - const store = configureStore(services); + const store = configureStore(services, namespace); /** * When `Share Schema as JSON` clicked in menu show a dialog message. */ @@ -57,10 +72,26 @@ export function activateSchemaPlugin( }; } -export function configureStore(services: SchemaPluginServices) { +export function configureStore( + services: SchemaPluginServices, + namespace: string +) { + const abortControllerRef = { + current: undefined, + }; + const geoLayersRef: { current: Record } = { + current: {}, + }; const store = createStore( reducer, - applyMiddleware(thunk.withExtraArgument(services)) + applyMiddleware( + thunk.withExtraArgument({ + ...services, + abortControllerRef, + geoLayersRef, + namespace, + }) + ) ); return store; } From 1b4c3e57838d4f452df2eade13c82e33d2770de2 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 23 Jan 2025 22:41:08 +0100 Subject: [PATCH 03/10] refactor(compass-schema): replace reflux with redux COMPASS-8797 --- .../src/components/compass-schema.tsx | 27 ++++++++++++------- .../coordinates-minichart.jsx | 2 +- .../src/components/minichart/minichart.jsx | 7 +++-- packages/compass-schema/src/stores/reducer.ts | 2 +- packages/compass-schema/src/stores/store.ts | 4 ++- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index a03bf029aee..bb0822a1be9 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -35,7 +35,7 @@ import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasPerformanceAdvisorLink } from '../utils'; import { useIsLastAppliedQueryOutdated } from '@mongodb-js/compass-query-bar'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; -import type { RootState } from '../stores/store'; +import type { RootState, SchemaThunkDispatch } from '../stores/store'; import { startAnalysis, stopAnalysis } from '../stores/reducer'; const rootStyles = css({ @@ -373,20 +373,27 @@ const Schema: React.FunctionComponent<{ schema: MongodbSchema | null; count?: number; resultId?: number; - startAnalysis: () => Promise; - stopAnalysis: () => void; -}> = ({ analysisState, errorMessage, schema, resultId }) => { + onStartAnalysis: () => Promise; + onStopAnalysis: () => void; +}> = ({ + analysisState, + errorMessage, + schema, + resultId, + onStartAnalysis, + onStopAnalysis, +}) => { const onApplyClicked = useCallback(() => { - startAnalysis(); - }, []); + void onStartAnalysis(); + }, [onStartAnalysis]); const onCancelClicked = useCallback(() => { - stopAnalysis(); - }, []); + onStopAnalysis(); + }, [onStopAnalysis]); const onResetClicked = useCallback(() => { - startAnalysis(); - }, []); + onStopAnalysis(); + }, [onStopAnalysis]); const outdated = useIsLastAppliedQueryOutdated('schema'); diff --git a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx index 45536eeeaee..a89afb2bb43 100644 --- a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx +++ b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx @@ -333,7 +333,7 @@ CoordinatesMinichart.propTypes = { onQueryChanged: PropTypes.func, }; -const ConnectedCoordinatesMinichart = connect(() => {}, { +const ConnectedCoordinatesMinichart = connect(() => ({}), { geoLayerAdded, geoLayersEdited, geoLayersDeleted, diff --git a/packages/compass-schema/src/components/minichart/minichart.jsx b/packages/compass-schema/src/components/minichart/minichart.jsx index 56f6099d48b..4fc1e510028 100644 --- a/packages/compass-schema/src/components/minichart/minichart.jsx +++ b/packages/compass-schema/src/components/minichart/minichart.jsx @@ -7,7 +7,6 @@ import CoordinatesMinichart from '../coordinates-minichart'; import D3Component from '../d3-component'; import vizFns from '../../modules'; import CONSTANTS from '../../constants/schema'; -import { connect } from 'react-redux'; class MiniChart extends PureComponent { static displayName = 'MiniChartComponent'; @@ -36,12 +35,12 @@ class MiniChart extends PureComponent { this.resizeListener(); window.addEventListener('resize', this.resizeListener); // this.unsubscribeMiniChartResize = - // this.props.actions.resizeMiniCharts.listen(this.resizeListener); + // this.props.actions.resizeMiniCharts.listen(this.resizeListener); // TODO: what was this doing? } componentWillUnmount() { window.removeEventListener('resize', this.resizeListener); - this.unsubscribeMiniChartResize(); + // this.unsubscribeMiniChartResize(); } /** @@ -150,4 +149,4 @@ class MiniChart extends PureComponent { } } -export default connect(() => {}, {})(MiniChart); +export default MiniChart; diff --git a/packages/compass-schema/src/stores/reducer.ts b/packages/compass-schema/src/stores/reducer.ts index ff82349465e..b71b3060d4c 100644 --- a/packages/compass-schema/src/stores/reducer.ts +++ b/packages/compass-schema/src/stores/reducer.ts @@ -155,7 +155,7 @@ export const handleSchemaShare = (): SchemaThunkAction => { export const _trackSchemaShared = ( hasSchema: boolean -): SchemaThunkAction => { +): SchemaThunkAction => { return (dispatch, getState, { track, connectionInfoRef }) => { const { schema } = getState(); // Use a function here to a) ensure that the calculations here diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index 910d7c724de..bf52273517e 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -1,6 +1,6 @@ import type { Logger } from '@mongodb-js/compass-logging'; import { createStore, applyMiddleware, type AnyAction } from 'redux'; -import thunk, { type ThunkAction } from 'redux-thunk'; +import thunk, { type ThunkDispatch, type ThunkAction } from 'redux-thunk'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { ConnectionInfoRef, @@ -40,6 +40,8 @@ export type SchemaThunkAction = ThunkAction< SchemaExtraArgs, A >; +export type SchemaThunkDispatch
= + ThunkDispatch; /** * Configure a store with the provided options. From 39eae8099b9a11ce0a2b1aff47f412accc52e4cb Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Thu, 23 Jan 2025 22:44:21 +0100 Subject: [PATCH 04/10] cleanup --- .../src/components/shard-zones-table.tsx | 6 +----- packages/data-service/src/data-service.ts | 8 ++------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/compass-global-writes/src/components/shard-zones-table.tsx b/packages/compass-global-writes/src/components/shard-zones-table.tsx index 61042b62233..f30352f4c44 100644 --- a/packages/compass-global-writes/src/components/shard-zones-table.tsx +++ b/packages/compass-global-writes/src/components/shard-zones-table.tsx @@ -104,14 +104,10 @@ export function ShardZonesTable({ expanded, }, onGlobalFilterChange: setSearchText, - onExpandedChange: (expanded) => { - setExpanded(expanded); - console.log('new expanded', expanded); - }, + onExpandedChange: setExpanded, enableGlobalFilter: true, getFilteredRowModel: getFilteredRowModel(), getIsRowExpanded: (row) => { - console.log('getIsRowExpanded', hasFilteredChildren(row)); return ( (searchText && hasFilteredChildren(row)) || (typeof expanded !== 'boolean' && expanded[row.id]) diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index 60965253dd4..195046b6776 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -3,7 +3,7 @@ import type { Tunnel, } from '@mongodb-js/devtools-proxy-support'; import { EventEmitter } from 'events'; -import { ExplainVerbosity, ClientEncryption, ReadPreference } from 'mongodb'; +import { ExplainVerbosity, ClientEncryption } from 'mongodb'; import type { AggregateOptions, AggregationCursor, @@ -1782,7 +1782,6 @@ class DataServiceImpl extends WithLogContext implements DataService { let cursor: AggregationCursor; return this._cancellableOperation( async (session?: ClientSession) => { - console.log({ options }); cursor = this._collection(ns, 'CRUD').aggregate(pipeline, { ...options, session, @@ -2205,7 +2204,6 @@ class DataServiceImpl extends WithLogContext implements DataService { }); } - // TODO now return this.aggregate( ns, pipeline, @@ -2624,9 +2622,7 @@ class DataServiceImpl extends WithLogContext implements DataService { private _collection(ns: string, type: ClientType): Collection { return this._initializedClient(type) .db(this._databaseName(ns)) - .collection(this._collectionName(ns), { - readPreference: ReadPreference.SECONDARY_PREFERRED, - }); + .collection(this._collectionName(ns)); } /** From 102deead1bf7ed343d1a5ed2c3cbd7931ddfa724 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 24 Jan 2025 11:01:01 +0100 Subject: [PATCH 05/10] . --- packages/compass-query-bar/src/stores/query-bar-store.ts | 5 ----- .../coordinates-minichart/coordinates-minichart.jsx | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/compass-query-bar/src/stores/query-bar-store.ts b/packages/compass-query-bar/src/stores/query-bar-store.ts index 52425866cc2..e43c63a5bfc 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -86,11 +86,6 @@ export type QueryBarExtraArgs = { export type QueryBarThunkDispatch = ThunkDispatch; -export type QueryBarThunkAction< - R, - A extends AnyAction = AnyAction -> = ThunkAction; - export function configureStore( initialState: Partial = {}, services: QueryBarExtraArgs diff --git a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx index a89afb2bb43..79f70b24aeb 100644 --- a/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx +++ b/packages/compass-schema/src/components/coordinates-minichart/coordinates-minichart.jsx @@ -333,7 +333,7 @@ CoordinatesMinichart.propTypes = { onQueryChanged: PropTypes.func, }; -const ConnectedCoordinatesMinichart = connect(() => ({}), { +const ConnectedCoordinatesMinichart = connect(undefined, { geoLayerAdded, geoLayersEdited, geoLayersDeleted, From 6d67280ca39b5c053c5c6b5dca732be50738a903 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 24 Jan 2025 16:45:14 +0100 Subject: [PATCH 06/10] more cleanup, reformat and tests --- .../src/components/compass-schema.tsx | 18 ++--- .../src/components/field.spec.tsx | 2 - .../src/components/minichart/minichart.jsx | 3 - packages/compass-schema/src/stores/reducer.ts | 67 +++++-------------- .../compass-schema/src/stores/store.spec.ts | 60 +++++++++++------ packages/compass-schema/src/stores/store.ts | 2 +- 6 files changed, 62 insertions(+), 90 deletions(-) diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index bb0822a1be9..aaab57f1cef 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -35,7 +35,7 @@ import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasPerformanceAdvisorLink } from '../utils'; import { useIsLastAppliedQueryOutdated } from '@mongodb-js/compass-query-bar'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; -import type { RootState, SchemaThunkDispatch } from '../stores/store'; +import type { RootState } from '../stores/store'; import { startAnalysis, stopAnalysis } from '../stores/reducer'; const rootStyles = css({ @@ -372,7 +372,7 @@ const Schema: React.FunctionComponent<{ maxTimeMS?: number; schema: MongodbSchema | null; count?: number; - resultId?: number; + resultId?: string; onStartAnalysis: () => Promise; onStopAnalysis: () => void; }> = ({ @@ -387,14 +387,6 @@ const Schema: React.FunctionComponent<{ void onStartAnalysis(); }, [onStartAnalysis]); - const onCancelClicked = useCallback(() => { - onStopAnalysis(); - }, [onStopAnalysis]); - - const onResetClicked = useCallback(() => { - onStopAnalysis(); - }, [onStopAnalysis]); - const outdated = useIsLastAppliedQueryOutdated('schema'); const enablePerformanceAdvisorBanner = usePreference( @@ -407,12 +399,12 @@ const Schema: React.FunctionComponent<{ toolbar={ } > @@ -422,7 +414,7 @@ const Schema: React.FunctionComponent<{ )} {analysisState === ANALYSIS_STATE_ANALYZING && ( - + )} {analysisState === ANALYSIS_STATE_COMPLETE && ( diff --git a/packages/compass-schema/src/components/field.spec.tsx b/packages/compass-schema/src/components/field.spec.tsx index 68c2ca09196..e891c711634 100644 --- a/packages/compass-schema/src/components/field.spec.tsx +++ b/packages/compass-schema/src/components/field.spec.tsx @@ -13,7 +13,6 @@ import { type SchemaType, } from 'mongodb-schema'; import { BSON, Decimal128 } from 'bson'; -import { configureActions } from '../actions'; import Field, { shouldShowUnboundArrayInsight } from './field'; import QueryBarPlugin from '@mongodb-js/compass-query-bar'; import { @@ -44,7 +43,6 @@ function renderField( = ( state = getInitialState(), action @@ -99,13 +94,6 @@ const reducer: Reducer = ( }; } - if (isAction(action, SchemaActions.analysisCancelled)) { - return { - ...state, - analysisState: ANALYSIS_STATE_INITIAL, - }; - } - return state; }; @@ -124,8 +112,8 @@ function getErrorState(err: Error & { code?: number }) { return { analysisState, errorMessage }; } -function resultId(): number { - return Math.floor(Math.random() * 2 ** 53); +function resultId(): string { + return new UUID().toString(); } export const handleSchemaShare = (): SchemaThunkAction => { @@ -182,12 +170,6 @@ const getInitialState = (): SchemaState => ({ resultId: resultId(), }); -export const onSchemaSampled = (): SchemaThunkAction => { - return (dispatch, getState, { geoLayersRef }) => { - geoLayersRef.current = {}; - }; -}; - export const geoLayerAdded = ( field: string, layer: Layer @@ -228,28 +210,6 @@ export const geoLayersDeleted = ( export const stopAnalysis = (): SchemaThunkAction => { return (dispatch, getState, { abortControllerRef }) => { abortControllerRef.current?.abort(); - dispatch({ type: SchemaActions.analysisCancelled }); - }; -}; - -export const _trackSchemaAnalyzed = ( - analysisTimeMS: number, - query: Query -): SchemaThunkAction => { - return (dispatch, getState, { track, connectionInfoRef }) => { - const { schema } = getState(); - // Use a function here to a) ensure that the calculations here - // are only made when telemetry is enabled and b) that errors from - // those calculations are caught and logged rather than displayed to - // users as errors from the core schema analysis logic. - const trackEvent = () => ({ - with_filter: Object.entries(query.filter ?? {}).length > 0, - schema_width: schema?.fields?.length ?? 0, - schema_depth: schema ? calculateSchemaDepth(schema) : 0, - geo_data: schema ? schemaContainsGeoData(schema) : false, - analysis_time_ms: analysisTimeMS, - }); - track('Schema Analyzed', trackEvent, connectionInfoRef.current); }; }; @@ -269,6 +229,9 @@ export const startAnalysis = (): SchemaThunkAction< fieldStoreService, abortControllerRef, namespace, + geoLayersRef, + connectionInfoRef, + track, } ) => { const query = queryBar.getLastAppliedQuery('schema'); @@ -295,10 +258,6 @@ export const startAnalysis = (): SchemaThunkAction< dispatch({ type: SchemaActions.analysisStarted }); - abortSignal?.addEventListener('abort', () => { - dispatch(stopAnalysis()); - }); - const analysisStartTime = Date.now(); const schema = await analyzeSchema( dataService, @@ -316,9 +275,17 @@ export const startAnalysis = (): SchemaThunkAction< dispatch({ type: SchemaActions.analysisFinished, schema }); - _trackSchemaAnalyzed(analysisTime, query); + // track schema analyzed + const trackEvent = () => ({ + with_filter: Object.entries(query.filter ?? {}).length > 0, + schema_width: schema?.fields?.length ?? 0, + schema_depth: schema ? calculateSchemaDepth(schema) : 0, + geo_data: schema ? schemaContainsGeoData(schema) : false, + analysis_time_ms: analysisTime, + }); + track('Schema Analyzed', trackEvent, connectionInfoRef.current); - dispatch(onSchemaSampled()); + geoLayersRef.current = {}; } catch (err: any) { log.error( mongoLogId(1_001_000_188), diff --git a/packages/compass-schema/src/stores/store.spec.ts b/packages/compass-schema/src/stores/store.spec.ts index e6cf05f7ba5..85d04f0d9ca 100644 --- a/packages/compass-schema/src/stores/store.spec.ts +++ b/packages/compass-schema/src/stores/store.spec.ts @@ -1,5 +1,4 @@ -import type { SchemaStore } from './store'; -import { activateSchemaPlugin } from './store'; +import { activateSchemaPlugin, type SchemaStore } from './store'; import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; import { expect } from 'chai'; @@ -9,6 +8,8 @@ import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import type { FieldStoreService } from '@mongodb-js/compass-field-store'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import { startAnalysis, stopAnalysis } from './reducer'; +import Sinon from 'sinon'; const dummyLogger = createNoopLogger('TEST'); const dummyTrack = createNoopTrack(); @@ -28,15 +29,24 @@ describe('Schema Store', function () { describe('#configureStore', function () { let store: SchemaStore; let deactivate: () => void; + let sandbox: Sinon.SinonSandbox; const localAppRegistry = new AppRegistry(); const globalAppRegistry = new AppRegistry(); - const dataService = 'test'; const namespace = 'db.coll'; const connectionInfoRef = { current: {}, } as ConnectionInfoRef; + let sampleStub: Sinon.SinonStub; + let isCancelErrorStub: Sinon.SinonStub; beforeEach(async function () { + sandbox = Sinon.createSandbox(); + sampleStub = sandbox.stub(); + isCancelErrorStub = sandbox.stub(); + const dataService = { + sample: sampleStub, + isCancelError: isCancelErrorStub, + }; const plugin = activateSchemaPlugin( { namespace: namespace, @@ -60,34 +70,42 @@ describe('Schema Store', function () { afterEach(function () { deactivate(); + sandbox.reset(); }); - it('sets the local app registry', function () { - expect(store.localAppRegistry).to.equal(localAppRegistry); - }); - - it('sets the global app registry', function () { - expect(store.globalAppRegistry).to.equal(globalAppRegistry); - }); - - it('sets the data provider', function () { - expect(store.dataService).to.equal(dataService); + it('defaults analysis state to initial', function () { + expect(store.getState().analysisState).to.equal(ANALYSIS_STATE_INITIAL); }); - it('sets the namespace', function () { - expect(store.ns).to.equal(namespace); + it('defaults the error to empty', function () { + expect(store.getState().errorMessage).to.equal(''); }); - it('defaults analysis state to initial', function () { - expect(store.state.analysisState).to.equal(ANALYSIS_STATE_INITIAL); + it('defaults the schema to null', function () { + expect(store.getState().schema).to.equal(null); }); - it('defaults the error to empty', function () { - expect(store.state.errorMessage).to.equal(''); + it('runs analysis', async function () { + const oldResultId = store.getState().resultId; + sampleStub.resolves([{ name: 'Hans' }, { name: 'Greta' }]); + await store.dispatch(startAnalysis()); + expect(sampleStub).to.have.been.called; + const { analysisState, errorMessage, schema, resultId } = + store.getState(); + expect(analysisState).to.equal('complete'); + expect(!!errorMessage).to.be.false; + expect(schema).not.to.be.null; + expect(resultId).not.to.equal(oldResultId); }); - it('defaults the schema to null', function () { - expect(store.state.schema).to.equal(null); + it('analysis can be aborted', async function () { + const analysisPromise = store.dispatch(startAnalysis()); + expect(store.getState().analysisState).to.equal('analyzing'); + sampleStub.rejects(new Error('abort')); + store.dispatch(stopAnalysis()); + isCancelErrorStub.returns(true); + await analysisPromise; + expect(store.getState().analysisState).to.equal('initial'); }); }); }); diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index bf52273517e..aa6e88a9e46 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -98,4 +98,4 @@ export function configureStore( return store; } -export type AtlasServiceStore = ReturnType; +export type SchemaStore = ReturnType; From 84ca2760dfcc64ef3ab60e0918fea011dd38feaa Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Fri, 24 Jan 2025 16:56:11 +0100 Subject: [PATCH 07/10] must've been an accident --- packages/compass-query-bar/src/stores/query-bar-store.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/compass-query-bar/src/stores/query-bar-store.ts b/packages/compass-query-bar/src/stores/query-bar-store.ts index e43c63a5bfc..52425866cc2 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -86,6 +86,11 @@ export type QueryBarExtraArgs = { export type QueryBarThunkDispatch = ThunkDispatch; +export type QueryBarThunkAction< + R, + A extends AnyAction = AnyAction +> = ThunkAction; + export function configureStore( initialState: Partial = {}, services: QueryBarExtraArgs From c61a63b2df0b5750a0a9fd70e352c868803ca5de Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Mon, 27 Jan 2025 13:20:26 +0100 Subject: [PATCH 08/10] fix --- package-lock.json | 10 ++++++---- packages/compass-schema/package.json | 5 +++-- .../compass-schema/src/components/compass-schema.tsx | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3ee0bc831e..351678c6e92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45958,7 +45958,6 @@ "@mongodb-js/compass-query-bar": "^8.50.5", "@mongodb-js/compass-telemetry": "^1.3.5", "@mongodb-js/connection-storage": "^0.25.5", - "@mongodb-js/reflux-state-mixin": "^1.1.5", "bson": "^6.10.1", "compass-preferences-model": "^2.32.5", "d3": "^3.5.17", @@ -45976,7 +45975,9 @@ "react": "^17.0.2", "react-leaflet": "^2.4.0", "react-leaflet-draw": "^0.19.0", - "reflux": "^0.4.1" + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.2.5", @@ -56909,7 +56910,6 @@ "@mongodb-js/mocha-config-compass": "^1.5.5", "@mongodb-js/my-queries-storage": "^0.21.5", "@mongodb-js/prettier-config-compass": "^1.1.5", - "@mongodb-js/reflux-state-mixin": "^1.1.5", "@mongodb-js/testing-library-compass": "^1.1.5", "@mongodb-js/tsconfig-compass": "^1.1.5", "@types/chai": "^4.2.21", @@ -56942,7 +56942,9 @@ "react-dom": "^17.0.2", "react-leaflet": "^2.4.0", "react-leaflet-draw": "^0.19.0", - "reflux": "^0.4.1", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", "sinon": "^9.2.3", "typescript": "^5.0.4", "xvfb-maybe": "^0.2.1" diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 728734d9b25..5bd2bd03510 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -97,8 +97,9 @@ "react": "^17.0.2", "react-leaflet": "^2.4.0", "react-leaflet-draw": "^0.19.0", - "reflux": "^0.4.1", - "@mongodb-js/reflux-state-mixin": "^1.1.5" + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "is_compass_plugin": true } diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index aaab57f1cef..f783a259f1e 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -399,7 +399,7 @@ const Schema: React.FunctionComponent<{ toolbar={ Date: Mon, 27 Jan 2025 18:14:36 +0100 Subject: [PATCH 09/10] address PR comments --- .../compass-schema/src/components/compass-schema.tsx | 2 +- packages/compass-schema/src/stores/reducer.ts | 11 +++++++++-- packages/compass-schema/src/stores/store.ts | 6 ++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index f783a259f1e..01f9abd716d 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -399,7 +399,7 @@ const Schema: React.FunctionComponent<{ toolbar={ => { return (dispatch, getState, { namespace }) => { const { schema } = getState(); - void navigator.clipboard.writeText(JSON.stringify(schema, null, ' ')); const hasSchema = schema !== null; + if (hasSchema) { + void navigator.clipboard.writeText(JSON.stringify(schema, null, ' ')); + } dispatch(_trackSchemaShared(hasSchema)); openToast( 'share-schema', @@ -209,6 +210,7 @@ export const geoLayersDeleted = ( export const stopAnalysis = (): SchemaThunkAction => { return (dispatch, getState, { abortControllerRef }) => { + if (!abortControllerRef) return; abortControllerRef.current?.abort(); }; }; @@ -234,6 +236,11 @@ export const startAnalysis = (): SchemaThunkAction< track, } ) => { + const { analysisState } = getState(); + if (analysisState === ANALYSIS_STATE_ANALYZING) { + debug('analysis already in progress. ignoring subsequent start'); + return; + } const query = queryBar.getLastAppliedQuery('schema'); const sampleSize = query.limit diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index aa6e88a9e46..14f5b209756 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -12,7 +12,7 @@ import type { PreferencesAccess } from 'compass-preferences-model/provider'; import type { FieldStoreService } from '@mongodb-js/compass-field-store'; import type { QueryBarService } from '@mongodb-js/compass-query-bar'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; -import reducer, { handleSchemaShare } from './reducer'; +import reducer, { handleSchemaShare, stopAnalysis } from './reducer'; import type { InternalLayer } from '../modules/geo'; export type DataService = Pick; @@ -53,7 +53,7 @@ export type SchemaThunkDispatch = export function activateSchemaPlugin( { namespace }: Pick, services: SchemaPluginServices, - { on, cleanup }: ActivateHelpers + { on, cleanup, addCleanup }: ActivateHelpers ) { const store = configureStore(services, namespace); /** @@ -66,6 +66,8 @@ export function activateSchemaPlugin( () => store.dispatch(handleSchemaShare()) // TODO: get the action ); + addCleanup(() => store.dispatch(stopAnalysis())); + return { store, deactivate() { From f8cfda2036c1ef5e481ae4db6653c75cec6d4558 Mon Sep 17 00:00:00 2001 From: Paula Stachova Date: Tue, 28 Jan 2025 13:13:43 +0100 Subject: [PATCH 10/10] cleanup --- packages/compass-schema/src/stores/reducer.ts | 5 +---- packages/compass-schema/src/stores/store.ts | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/compass-schema/src/stores/reducer.ts b/packages/compass-schema/src/stores/reducer.ts index 5818be8bafc..1824a86be00 100644 --- a/packages/compass-schema/src/stores/reducer.ts +++ b/packages/compass-schema/src/stores/reducer.ts @@ -161,9 +161,6 @@ export const _trackSchemaShared = ( }; }; -/** - * @return {Object} initial schema state. - */ const getInitialState = (): SchemaState => ({ analysisState: ANALYSIS_STATE_INITIAL, errorMessage: '', @@ -210,7 +207,7 @@ export const geoLayersDeleted = ( export const stopAnalysis = (): SchemaThunkAction => { return (dispatch, getState, { abortControllerRef }) => { - if (!abortControllerRef) return; + if (!abortControllerRef.current) return; abortControllerRef.current?.abort(); }; }; diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index 14f5b209756..243a8117b06 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -60,10 +60,8 @@ export function activateSchemaPlugin( * When `Share Schema as JSON` clicked in menu show a dialog message. */ - on( - services.localAppRegistry, - 'menu-share-schema-json', - () => store.dispatch(handleSchemaShare()) // TODO: get the action + on(services.localAppRegistry, 'menu-share-schema-json', () => + store.dispatch(handleSchemaShare()) ); addCleanup(() => store.dispatch(stopAnalysis()));