diff --git a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx index 1f98a73199d..8b737157663 100644 --- a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx +++ b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx @@ -13,26 +13,16 @@ import { import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { PreferencesProvider } from 'compass-preferences-model/provider'; -import { ExperimentTestName } from '@mongodb-js/compass-telemetry/provider'; -import { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry'; + import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import CollectionHeaderActions from '../collection-header-actions'; describe('CollectionHeaderActions [Component]', function () { let preferences: PreferencesAccess; - let mockUseAssignment: sinon.SinonStub; beforeEach(async function () { preferences = await createSandboxFromDefaultPreferences(); - mockUseAssignment = sinon.stub(); - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorControl', - }, - }, - }); }); afterEach(function () { @@ -45,27 +35,19 @@ describe('CollectionHeaderActions [Component]', function () { connectionInfo?: ConnectionInfo ) => { return renderWithActiveConnection( - - - - - - - , + + + + + , connectionInfo ); }; @@ -225,34 +207,15 @@ describe('CollectionHeaderActions [Component]', function () { }, }; - it('should call useAssignment with correct parameters', async function () { - await renderCollectionHeaderActions({ - namespace: 'test.collection', - isReadonly: false, - }); - - expect(mockUseAssignment).to.have.been.calledWith( - ExperimentTestName.mockDataGenerator, - true // trackIsInSample - Experiment viewed analytics event - ); - }); - it('should call onOpenMockDataModal when CTA button is clicked', async function () { const onOpenMockDataModal = sinon.stub(); - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', - }, - }, - }); - await renderCollectionHeaderActions( { namespace: 'test.collection', isReadonly: false, onOpenMockDataModal, + schemaAnalysisStatus: 'complete', }, {}, atlasConnectionInfo @@ -267,14 +230,6 @@ describe('CollectionHeaderActions [Component]', function () { }); it('should disable button for deeply nested collections', async function () { - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', // Treatment variant - }, - }, - }); - await renderCollectionHeaderActions( { namespace: 'test.collection', diff --git a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx index e8204823d5c..c078c220731 100644 --- a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx +++ b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx @@ -12,14 +12,10 @@ import React from 'react'; import { usePreferences } from 'compass-preferences-model/provider'; import toNS from 'mongodb-ns'; import { wrapField } from '@mongodb-js/mongodb-constants'; -import { - useTelemetry, - useAssignment, - ExperimentTestName, - ExperimentTestGroup, -} from '@mongodb-js/compass-telemetry/provider'; +import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; import { SCHEMA_ANALYSIS_STATE_ANALYZING, + SCHEMA_ANALYSIS_STATE_COMPLETE, type SchemaAnalysisStatus, } from '../../schema-analysis-types'; @@ -82,21 +78,13 @@ const CollectionHeaderActions: React.FunctionComponent< usePreferences(['readOnly', 'enableShell']); const track = useTelemetry(); - // Get experiment assignment for Mock Data Generator - const mockDataGeneratorAssignment = useAssignment( - ExperimentTestName.mockDataGenerator, - true // trackIsInSample - this will fire the "Experiment Viewed" event - ); - const { database, collection } = toNS(namespace); - // Check if user is in treatment group for Mock Data Generator experiment - const isInMockDataTreatmentVariant = - mockDataGeneratorAssignment?.assignment?.assignmentData?.variant === - ExperimentTestGroup.mockDataGeneratorVariant; - + // Show Mock Data Generator button if schema analysis has been initiated (indicating user is in treatment variant) + // Schema analysis only runs for users in the treatment variant const shouldShowMockDataButton = - isInMockDataTreatmentVariant && + (schemaAnalysisStatus === SCHEMA_ANALYSIS_STATE_ANALYZING || + schemaAnalysisStatus === SCHEMA_ANALYSIS_STATE_COMPLETE) && atlasMetadata && // Only show in Atlas !isReadonly && // Don't show for readonly collections (views) !sourceName; // sourceName indicates it's a view diff --git a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx index 82553cc22af..890bda8467e 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx @@ -17,8 +17,10 @@ import { type WorkspacesService, } from '@mongodb-js/compass-workspaces/provider'; import { MockDataGeneratorStep } from '../mock-data-generator-modal/types'; -import { SCHEMA_ANALYSIS_STATE_COMPLETE } from '../../schema-analysis-types'; -import { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry'; +import { + SCHEMA_ANALYSIS_STATE_COMPLETE, + SCHEMA_ANALYSIS_STATE_INITIAL, +} from '../../schema-analysis-types'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import Sinon from 'sinon'; @@ -26,7 +28,8 @@ import Sinon from 'sinon'; function renderCollectionHeader( props: Partial> = {}, workspaceService: Partial = {}, - stateOverrides: any = {} + stateOverrides: Record = {}, + connectionInfo?: ConnectionInfo ) { const defaultState = { mockDataGenerator: { @@ -38,7 +41,7 @@ function renderCollectionHeader( const mockStore = createStore(() => defaultState); - return renderWithConnections( + return renderWithActiveConnection( - + , + connectionInfo ); } @@ -59,8 +63,8 @@ describe('CollectionHeader [Component]', function () { afterEach(cleanup); context('when the collection is not readonly', function () { - beforeEach(function () { - renderCollectionHeader(); + beforeEach(async function () { + await renderCollectionHeader(); }); it('renders the correct root classname', function () { @@ -89,8 +93,11 @@ describe('CollectionHeader [Component]', function () { }); context('when the collection is readonly', function () { - beforeEach(function () { - renderCollectionHeader({ isReadonly: true, sourceName: 'orig.coll' }); + beforeEach(async function () { + await renderCollectionHeader({ + isReadonly: true, + sourceName: 'orig.coll', + }); }); afterEach(cleanup); @@ -113,8 +120,8 @@ describe('CollectionHeader [Component]', function () { }); context('when the collection is readonly but not a view', function () { - beforeEach(function () { - renderCollectionHeader({ isReadonly: true, sourceName: undefined }); + beforeEach(async function () { + await renderCollectionHeader({ isReadonly: true, sourceName: undefined }); }); it('renders the readonly badge', function () { @@ -127,8 +134,8 @@ describe('CollectionHeader [Component]', function () { }); context('when the collection is a time-series collection', function () { - beforeEach(function () { - renderCollectionHeader({ isTimeSeries: true }); + beforeEach(async function () { + await renderCollectionHeader({ isTimeSeries: true }); }); it('does not render the readonly badge', function () { @@ -141,8 +148,8 @@ describe('CollectionHeader [Component]', function () { }); context('when the collection is a clustered collection', function () { - beforeEach(function () { - renderCollectionHeader({ isClustered: true }); + beforeEach(async function () { + await renderCollectionHeader({ isClustered: true }); }); it('does not render the readonly badge', function () { @@ -159,8 +166,8 @@ describe('CollectionHeader [Component]', function () { }); context('when the collection is a fle collection', function () { - beforeEach(function () { - renderCollectionHeader({ isFLE: true }); + beforeEach(async function () { + await renderCollectionHeader({ isFLE: true }); }); it('renders the fle badge', function () { @@ -169,8 +176,8 @@ describe('CollectionHeader [Component]', function () { }); describe('insights', function () { - it('should show an insight when $text is used in the pipeline source', function () { - renderCollectionHeader({ + it('should show an insight when $text is used in the pipeline source', async function () { + await renderCollectionHeader({ sourcePipeline: [{ $match: { $text: {} } }], }); expect(screen.getByTestId('insight-badge-button')).to.exist; @@ -179,8 +186,8 @@ describe('CollectionHeader [Component]', function () { .exist; }); - it('should show an insight when $regex is used in the pipeline source', function () { - renderCollectionHeader({ + it('should show an insight when $regex is used in the pipeline source', async function () { + await renderCollectionHeader({ sourcePipeline: [{ $match: { $regex: {} } }], }); expect(screen.getByTestId('insight-badge-button')).to.exist; @@ -189,8 +196,8 @@ describe('CollectionHeader [Component]', function () { .exist; }); - it('should show an insight when $lookup is used in the pipeline source', function () { - renderCollectionHeader({ + it('should show an insight when $lookup is used in the pipeline source', async function () { + await renderCollectionHeader({ sourcePipeline: [{ $lookup: {} }], }); expect(screen.getByTestId('insight-badge-button')).to.exist; @@ -209,7 +216,7 @@ describe('CollectionHeader [Component]', function () { }); function assertBreadcrumbText(items: string[]) { - const crumbs: any[] = []; + const crumbs: (string | null)[] = []; screen.getByTestId('breadcrumbs').childNodes.forEach((item) => { crumbs.push(item.textContent); }); @@ -230,13 +237,13 @@ describe('CollectionHeader [Component]', function () { } context('renders correclty', function () { - it('for a collection', function () { - renderCollectionHeader({ namespace: 'db.coll1' }); + it('for a collection', async function () { + await renderCollectionHeader({ namespace: 'db.coll1' }); assertBreadcrumbText(['db', 'coll1']); }); - it('for a view', function () { - renderCollectionHeader({ + it('for a view', async function () { + await renderCollectionHeader({ namespace: 'db.coll1', sourceName: 'db.coll2', }); @@ -244,8 +251,8 @@ describe('CollectionHeader [Component]', function () { assertBreadcrumbText(['db', 'coll2', 'coll1']); }); - it('for a view when its being edited', function () { - renderCollectionHeader({ + it('for a view when its being edited', async function () { + await renderCollectionHeader({ namespace: 'db.coll3', editViewName: 'db.coll1', }); @@ -255,10 +262,10 @@ describe('CollectionHeader [Component]', function () { }); context('calls onClick correclty', function () { - it('for a collection', function () { + it('for a collection', async function () { const openCollectionsWorkspaceStub = sandbox.stub(); const openCollectionWorkspaceStub = sandbox.stub(); - renderCollectionHeader( + await renderCollectionHeader( { namespace: 'db.coll1' }, { openCollectionsWorkspace: openCollectionsWorkspaceStub, @@ -279,9 +286,9 @@ describe('CollectionHeader [Component]', function () { ]); }); - it('for a view, opens source collection', function () { + it('for a view, opens source collection', async function () { const openCollectionWorkspaceStub = sandbox.stub(); - renderCollectionHeader( + await renderCollectionHeader( { namespace: 'db.coll1', sourceName: 'db.coll2' }, { openCollectionWorkspace: openCollectionWorkspaceStub, @@ -335,23 +342,6 @@ describe('CollectionHeader [Component]', function () { }); describe('Mock Data Generator integration', function () { - let mockUseAssignment: Sinon.SinonStub; - - beforeEach(function () { - // Mock the useAssignment hook from compass-experimentation - mockUseAssignment = Sinon.stub().returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', - }, - }, - }); - }); - - afterEach(function () { - Sinon.restore(); - }); - const atlasConnectionInfo: ConnectionInfo = { id: 'test-atlas-connection', connectionOptions: { @@ -375,50 +365,8 @@ describe('CollectionHeader [Component]', function () { }, }; - function renderCollectionHeaderWithExperimentation( - props: Partial> = {}, - workspaceService: Partial = {}, - stateOverrides: any = {}, - connectionInfo?: ConnectionInfo - ) { - const defaultState = { - mockDataGenerator: { - isModalOpen: false, - currentStep: MockDataGeneratorStep.SCHEMA_CONFIRMATION, - }, - ...stateOverrides, - }; - - const mockStore = createStore(() => defaultState); - - return renderWithActiveConnection( - - - - - - - , - connectionInfo - ); - } - it('should show Mock Data Generator button when all conditions are met', async function () { - await renderCollectionHeaderWithExperimentation( + await renderCollectionHeader( { isAtlas: true, // Atlas environment isReadonly: false, // Not readonly @@ -447,7 +395,7 @@ describe('CollectionHeader [Component]', function () { }); it('should disable Mock Data Generator button when collection has no schema analysis data', async function () { - await renderCollectionHeaderWithExperimentation( + await renderCollectionHeader( { isAtlas: true, isReadonly: false, @@ -474,7 +422,7 @@ describe('CollectionHeader [Component]', function () { }); it('should disable Mock Data Generator button for collections with excessive nesting depth', async function () { - await renderCollectionHeaderWithExperimentation( + await renderCollectionHeader( { isAtlas: true, isReadonly: false, @@ -503,7 +451,7 @@ describe('CollectionHeader [Component]', function () { }); it('should not show Mock Data Generator button for readonly collections (views)', async function () { - await renderCollectionHeaderWithExperimentation( + await renderCollectionHeader( { isAtlas: true, isReadonly: true, // Readonly (view) @@ -530,7 +478,7 @@ describe('CollectionHeader [Component]', function () { }); it('should not show Mock Data Generator button in non-Atlas environments', async function () { - await renderCollectionHeaderWithExperimentation( + await renderCollectionHeader( { isAtlas: false, // Not Atlas isReadonly: false, @@ -556,16 +504,8 @@ describe('CollectionHeader [Component]', function () { ).to.not.exist; }); - it('should not show Mock Data Generator button when not in treatment variant', async function () { - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'control', - }, - }, - }); - - await renderCollectionHeaderWithExperimentation( + it('should not show Mock Data Generator button when schema analysis has not run', async function () { + await renderCollectionHeader( { isAtlas: true, isReadonly: false, @@ -574,13 +514,7 @@ describe('CollectionHeader [Component]', function () { {}, { schemaAnalysis: { - status: SCHEMA_ANALYSIS_STATE_COMPLETE, - processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, - }, - schemaMetadata: { - maxNestingDepth: 2, - }, + status: SCHEMA_ANALYSIS_STATE_INITIAL, // No schema analysis has run }, }, atlasConnectionInfo diff --git a/packages/compass-collection/src/modules/collection-tab.ts b/packages/compass-collection/src/modules/collection-tab.ts index e1f3afa2eac..82e913d4865 100644 --- a/packages/compass-collection/src/modules/collection-tab.ts +++ b/packages/compass-collection/src/modules/collection-tab.ts @@ -39,8 +39,6 @@ import type { MockDataGeneratorState } from '../components/mock-data-generator-m const DEFAULT_SAMPLE_SIZE = 100; -const NO_DOCUMENTS_ERROR = 'No documents found in the collection to analyze.'; - function isAction( action: AnyAction, type: A['type'] @@ -590,10 +588,17 @@ export const analyzeCollectionSchema = (): CollectionThunkAction< return; } if (sampleDocuments.length === 0) { - logger.debug(NO_DOCUMENTS_ERROR); + logger.debug( + 'Collection is empty, completing schema analysis with empty schema' + ); dispatch({ - type: CollectionActions.SchemaAnalysisFailed, - error: new Error(NO_DOCUMENTS_ERROR), + type: CollectionActions.SchemaAnalysisFinished, + processedSchema: {}, + sampleDocument: {}, + schemaMetadata: { + maxNestingDepth: 0, + validationRules: null, + }, }); return; }