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 7d3951af8cc..1450a7566d2 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 @@ -3,6 +3,7 @@ import React, { type ComponentProps } from 'react'; import { renderWithActiveConnection, screen, + waitFor, } from '@mongodb-js/testing-library-compass'; import sinon from 'sinon'; import { @@ -14,6 +15,7 @@ import { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import CollectionHeaderActions from '../collection-header-actions'; +import { MAX_COLLECTION_NESTING_DEPTH } from '../mock-data-generator-modal/utils'; describe('CollectionHeaderActions [Component]', function () { let mockUseAssignment: sinon.SinonStub; @@ -230,94 +232,132 @@ describe('CollectionHeaderActions [Component]', function () { ); }); - it('should call onOpenMockDataModal when CTA button is clicked', async function () { - const onOpenMockDataModal = sinon.stub(); + context('when in the mock data generator treatment variant', function () { + beforeEach(function () { + mockUseAssignment.returns({ + assignment: { + assignmentData: { + variant: 'mockDataGeneratorVariant', + }, + }, + }); + }); - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', + it('should send a track event when the button is viewed', async function () { + const result = await renderCollectionHeaderActions( + { + namespace: 'test.collection', + isReadonly: false, }, - }, + {}, + atlasConnectionInfo + ); + + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator CTA Button Viewed', + { + button_enabled: true, + gen_ai_features_enabled: false, + send_sample_values_enabled: false, + } + ); + }); }); - await renderCollectionHeaderActions( - { - namespace: 'test.collection', - isReadonly: false, - onOpenMockDataModal, - }, - {}, - atlasConnectionInfo - ); + it('should call onOpenMockDataModal when CTA button is clicked', async function () { + const onOpenMockDataModal = sinon.stub(); + await renderCollectionHeaderActions( + { + namespace: 'test.collection', + isReadonly: false, + onOpenMockDataModal, + }, + {}, + atlasConnectionInfo + ); - const button = screen.getByTestId( - 'collection-header-generate-mock-data-button' - ); - button.click(); + const button = screen.getByTestId( + 'collection-header-generate-mock-data-button' + ); + button.click(); - expect(onOpenMockDataModal).to.have.been.calledOnce; - }); + expect(onOpenMockDataModal).to.have.been.calledOnce; + }); - it('should disable button for deeply nested collections', async function () { - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', // Treatment variant + it('sends a track event when CTA button is clicked', async function () { + const onOpenMockDataModal = sinon.stub(); + + const result = await renderCollectionHeaderActions( + { + namespace: 'test.collection', + isReadonly: false, + onOpenMockDataModal, }, - }, - }); + {}, + atlasConnectionInfo + ); - await renderCollectionHeaderActions( - { - namespace: 'test.collection', - isReadonly: false, - hasSchemaAnalysisData: true, - analyzedSchemaDepth: 8, // Exceeds MAX_COLLECTION_NESTING_DEPTH (7) - schemaAnalysisStatus: 'complete', - onOpenMockDataModal: sinon.stub(), - }, - {}, - atlasConnectionInfo - ); + const button = screen.getByTestId( + 'collection-header-generate-mock-data-button' + ); + button.click(); - const button = screen.getByTestId( - 'collection-header-generate-mock-data-button' - ); - expect(button).to.exist; - expect(button).to.have.attribute('aria-disabled', 'true'); - }); + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator Opened', + { + gen_ai_features_enabled: false, + send_sample_values_enabled: false, + } + ); + }); + }); - it('should show an error banner when the schema is in an unsupported state', async function () { - mockUseAssignment.returns({ - assignment: { - assignmentData: { - variant: 'mockDataGeneratorVariant', + it('should disable button for deeply nested collections', async function () { + await renderCollectionHeaderActions( + { + namespace: 'test.collection', + isReadonly: false, + hasSchemaAnalysisData: true, + analyzedSchemaDepth: MAX_COLLECTION_NESTING_DEPTH + 1, + schemaAnalysisStatus: 'complete', + onOpenMockDataModal: sinon.stub(), }, - }, + {}, + atlasConnectionInfo + ); + + const button = screen.getByTestId( + 'collection-header-generate-mock-data-button' + ); + expect(button).to.exist; + expect(button).to.have.attribute('aria-disabled', 'true'); }); - await renderCollectionHeaderActions( - { - namespace: 'test.collection', - isReadonly: false, - hasSchemaAnalysisData: false, - schemaAnalysisStatus: 'error', - schemaAnalysisError: { - errorType: 'unsupportedState', - errorMessage: 'Unsupported state', + it('should show an error banner when the schema is in an unsupported state', async function () { + await renderCollectionHeaderActions( + { + namespace: 'test.collection', + isReadonly: false, + hasSchemaAnalysisData: false, + schemaAnalysisStatus: 'error', + schemaAnalysisError: { + errorType: 'unsupportedState', + errorMessage: 'Unsupported state', + }, + onOpenMockDataModal: sinon.stub(), }, - onOpenMockDataModal: sinon.stub(), - }, - {}, - atlasConnectionInfo - ); + {}, + atlasConnectionInfo + ); - const button = screen.getByTestId( - 'collection-header-generate-mock-data-button' - ); - expect(button).to.exist; - expect(button).to.have.attribute('aria-disabled', 'true'); + const button = screen.getByTestId( + 'collection-header-generate-mock-data-button' + ); + expect(button).to.exist; + expect(button).to.have.attribute('aria-disabled', 'true'); + }); }); }); }); 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 517233aaaa6..772724035f3 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 @@ -8,8 +8,12 @@ import { } from '@mongodb-js/compass-components'; import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; -import React from 'react'; -import { usePreferences } from 'compass-preferences-model/provider'; +import React, { useCallback } from 'react'; +import { + useIsAIFeatureEnabled, + usePreference, + usePreferences, +} from 'compass-preferences-model/provider'; import toNS from 'mongodb-ns'; import { wrapField } from '@mongodb-js/mongodb-constants'; import { @@ -17,17 +21,15 @@ import { useAssignment, ExperimentTestName, ExperimentTestGroup, + useTrackOnChange, + type TrackFunction, } from '@mongodb-js/compass-telemetry/provider'; import { SCHEMA_ANALYSIS_STATE_ANALYZING, type SchemaAnalysisStatus, type SchemaAnalysisError, } from '../../schema-analysis-types'; - -/** - * Maximum allowed nesting depth for collections to show Mock Data Generator - */ -const MAX_COLLECTION_NESTING_DEPTH = 7; +import { MAX_COLLECTION_NESTING_DEPTH } from '../mock-data-generator-modal/utils'; const collectionHeaderActionsStyles = css({ display: 'flex', @@ -92,6 +94,10 @@ const CollectionHeaderActions: React.FunctionComponent< const { readWrite: preferencesReadWrite, enableShell: showOpenShellButton } = usePreferences(['readWrite', 'enableShell']); const track = useTelemetry(); + const isAIFeatureEnabled = useIsAIFeatureEnabled(); + const isSampleDocumentPassingEnabled = usePreference( + 'enableGenAISampleDocumentPassing' + ); // Get experiment assignment for Mock Data Generator const mockDataGeneratorAssignment = useAssignment( @@ -122,6 +128,39 @@ const CollectionHeaderActions: React.FunctionComponent< const isView = isReadonly && sourceName && !editViewName; const showViewEdit = isView && !preferencesReadWrite; + const shouldDisableMockDataButton = + !hasSchemaAnalysisData || exceedsMaxNestingDepth; + + const onMockDataGeneratorCtaButtonClicked = useCallback(() => { + track('Mock Data Generator Opened', { + gen_ai_features_enabled: isAIFeatureEnabled, + send_sample_values_enabled: isSampleDocumentPassingEnabled, + }); + onOpenMockDataModal(); + }, [ + track, + isAIFeatureEnabled, + isSampleDocumentPassingEnabled, + onOpenMockDataModal, + ]); + + useTrackOnChange( + (track: TrackFunction) => { + if (shouldShowMockDataButton) { + track('Mock Data Generator CTA Button Viewed', { + button_enabled: !shouldDisableMockDataButton, + gen_ai_features_enabled: isAIFeatureEnabled, + send_sample_values_enabled: isSampleDocumentPassingEnabled, + }); + } + }, + [ + shouldShowMockDataButton, + shouldDisableMockDataButton, + isAIFeatureEnabled, + isSampleDocumentPassingEnabled, + ] + ); return (
} > Generate Mock Data diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/constants.ts b/packages/compass-collection/src/components/mock-data-generator-modal/constants.ts index caf6bced639..648dd564097 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/constants.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/constants.ts @@ -9,6 +9,20 @@ export const StepButtonLabelMap = { [MockDataGeneratorStep.GENERATE_DATA]: 'Done', } as const; +// Map of the current mock data generator step to the next step or 'finish' if the user is on the last step. +// For the purposes of telemetry tracking the step progression in the modal. +export const MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP: Record< + MockDataGeneratorStep, + MockDataGeneratorStep | 'finish' +> = { + [MockDataGeneratorStep.SCHEMA_CONFIRMATION]: + MockDataGeneratorStep.SCHEMA_EDITOR, + [MockDataGeneratorStep.SCHEMA_EDITOR]: MockDataGeneratorStep.DOCUMENT_COUNT, + [MockDataGeneratorStep.DOCUMENT_COUNT]: MockDataGeneratorStep.PREVIEW_DATA, + [MockDataGeneratorStep.PREVIEW_DATA]: MockDataGeneratorStep.GENERATE_DATA, + [MockDataGeneratorStep.GENERATE_DATA]: 'finish', +}; + export const DEFAULT_DOCUMENT_COUNT = 1000; export const MAX_DOCUMENT_COUNT = 100000; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx index f17816f34c8..cae442c81a4 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import type { CollectionState } from '../../modules/collection-tab'; import type { SchemaAnalysisState } from '../../schema-analysis-types'; import { DEFAULT_DOCUMENT_COUNT, MAX_DOCUMENT_COUNT } from './constants'; +import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; const BYTE_PRECISION_THRESHOLD = 1000; @@ -67,6 +68,7 @@ const DocumentCountScreen = ({ onDocumentCountChange, schemaAnalysisState, }: Props) => { + const track = useTelemetry(); const estimatedDiskSize = useMemo( () => schemaAnalysisState.status === 'complete' && @@ -98,6 +100,9 @@ const DocumentCountScreen = ({ const value = parseInt(event.target.value, 10); if (!isNaN(value)) { onDocumentCountChange(value); + track('Mock Data Document Count Changed', { + document_count: value, + }); } }; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx index c4b5a6cfd20..d4d402a424f 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx @@ -20,6 +20,7 @@ import type { MockDataGeneratorState, } from './types'; import type { MongoDBFieldType } from '../../schema-analysis-types'; +import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; const containerStyles = css({ display: 'flex', @@ -62,6 +63,7 @@ const FakerSchemaEditorContent = ({ fakerSchema: FakerSchema; onSchemaConfirmed: (isConfirmed: boolean) => void; }) => { + const track = useTelemetry(); const [fakerSchemaFormValues, setFakerSchemaFormValues] = React.useState(fakerSchema); @@ -93,6 +95,9 @@ const FakerSchemaEditorContent = ({ const originalLlmMapping = originalLlmMappings.current[activeField]; if (currentMapping) { + const previousJsonType = currentMapping.mongoType; + const previousFakerMethod = currentMapping.fakerMethod; + const isSwitchingToOriginalType = originalLlmMapping && newJsonType === originalLlmMapping.mongoType; @@ -105,6 +110,16 @@ const FakerSchemaEditorContent = ({ fakerArgs: [], }; + const newFakerMethod = newMapping.fakerMethod; + + track('Mock Data JSON Type Changed', { + field_name: activeField, + previous_json_type: previousJsonType, + new_json_type: newJsonType, + previous_faker_method: previousFakerMethod, + new_faker_method: newFakerMethod, + }); + setFakerSchemaFormValues({ ...fakerSchemaFormValues, [activeField]: newMapping, @@ -118,6 +133,8 @@ const FakerSchemaEditorContent = ({ const originalLlmMapping = originalLlmMappings.current[activeField]; if (currentMapping) { + const previousFakerMethod = currentMapping.fakerMethod; + const isSwitchingToLlmSuggestion = originalLlmMapping && currentMapping.mongoType === originalLlmMapping.mongoType && @@ -131,6 +148,13 @@ const FakerSchemaEditorContent = ({ fakerArgs: [], }; + track('Mock Data Faker Method Changed', { + field_name: activeField, + json_type: currentMapping.mongoType, + previous_faker_method: previousFakerMethod, + new_faker_method: newFakerFunction, + }); + setFakerSchemaFormValues({ ...fakerSchemaFormValues, [activeField]: newMapping, diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx index 5c359f5da15..4aa1e833a83 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx @@ -12,15 +12,16 @@ import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import MockDataGeneratorModal from './mock-data-generator-modal'; -import { MockDataGeneratorStep } from './types'; +import { DataGenerationStep, MockDataGeneratorStep } from './types'; import { DEFAULT_CONNECTION_STRING_FALLBACK, + MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP, StepButtonLabelMap, } from './constants'; import type { CollectionState } from '../../modules/collection-tab'; import { default as collectionTabReducer } from '../../modules/collection-tab'; import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; +import { type MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; import type { SchemaAnalysisState } from '../../schema-analysis-types'; import * as scriptGenerationUtils from './script-generation-utils'; @@ -149,6 +150,21 @@ describe('MockDataGeneratorModal', () => { ); }); + it('fires a track event when the close button is clicked', async () => { + const result = await renderModal(); + userEvent.click(screen.getByLabelText('Close modal')); + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator Dismissed', + { + screen: MockDataGeneratorStep.SCHEMA_CONFIRMATION, + gen_ai_features_enabled: false, + send_sample_values_enabled: false, + } + ); + }); + }); + it('closes the modal when the cancel button is clicked', async () => { await renderModal(); @@ -160,6 +176,21 @@ describe('MockDataGeneratorModal', () => { ); }); + it('fires a track event when the cancel button is clicked', async () => { + const result = await renderModal(); + userEvent.click(screen.getByText('Cancel')); + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator Dismissed', + { + screen: MockDataGeneratorStep.SCHEMA_CONFIRMATION, + gen_ai_features_enabled: false, + send_sample_values_enabled: false, + } + ); + }); + }); + function createMockServicesWithSlowAiRequest() { let abortSignalReceived = false; let rejectPromise: (reason?: any) => void; @@ -329,21 +360,25 @@ describe('MockDataGeneratorModal', () => { fields: [ { fieldPath: 'name', + mongoType: 'String', fakerMethod: 'person.firstName', fakerArgs: [], }, { fieldPath: 'age', + mongoType: 'Int32', fakerMethod: 'number.int', fakerArgs: [], }, { fieldPath: 'email', + mongoType: 'String', fakerMethod: 'internet', fakerArgs: [], }, { fieldPath: 'username', + mongoType: 'String', fakerMethod: 'noSuchMethod', fakerArgs: [], }, @@ -438,6 +473,7 @@ describe('MockDataGeneratorModal', () => { fields: [ { fieldPath: 'name', + mongoType: 'String', fakerMethod: 'person.firstName', fakerArgs: [], isArray: false, @@ -445,6 +481,7 @@ describe('MockDataGeneratorModal', () => { }, { fieldPath: 'email', + mongoType: 'String', fakerMethod: 'internet.email', fakerArgs: [], isArray: false, @@ -474,6 +511,7 @@ describe('MockDataGeneratorModal', () => { fields: [ { fieldPath: 'name', + mongoType: 'String', fakerMethod: 'person.firstName', fakerArgs: [], isArray: false, @@ -481,6 +519,7 @@ describe('MockDataGeneratorModal', () => { }, { fieldPath: 'age', + mongoType: 'Int32', fakerMethod: 'number.int', fakerArgs: [], isArray: false, @@ -548,6 +587,7 @@ describe('MockDataGeneratorModal', () => { fields: [ { fieldPath: 'name', + mongoType: 'String', fakerMethod: 'person.firstName', fakerArgs: largeLengthArgs, isArray: false, @@ -555,6 +595,7 @@ describe('MockDataGeneratorModal', () => { }, { fieldPath: 'age', + mongoType: 'Int32', fakerMethod: 'number.int', fakerArgs: [ { @@ -568,6 +609,7 @@ describe('MockDataGeneratorModal', () => { }, { fieldPath: 'username', + mongoType: 'String', fakerMethod: 'string.alpha', // large string fakerArgs: ['a'.repeat(1001)], @@ -576,6 +618,7 @@ describe('MockDataGeneratorModal', () => { }, { fieldPath: 'avatar', + mongoType: 'String', fakerMethod: 'image.url', fakerArgs: [ { @@ -731,6 +774,92 @@ describe('MockDataGeneratorModal', () => { screen.getByTestId('next-step-button').getAttribute('aria-disabled') ).to.equal('false'); }); + + it('fires a track event when the user changes the JSON field type', async () => { + const result = await renderModal({ + mockServices: mockServicesWithMockDataResponse, + schemaAnalysis: mockSchemaAnalysis, + }); + + // advance to the schema editor step + userEvent.click(screen.getByText('Confirm')); + await waitFor(() => { + expect(screen.getByTestId('faker-schema-editor')).to.exist; + }); + + const jsonTypeSelect = screen.getByLabelText('JSON Type'); + userEvent.click(jsonTypeSelect); + const numberOption = await screen.findByRole('option', { + name: 'Number', + }); + userEvent.click(numberOption); + + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data JSON Type Changed', + { + field_name: 'name', + previous_json_type: 'String', + new_json_type: 'Number', + previous_faker_method: 'person.firstName', + new_faker_method: 'number.int', + } + ); + }); + }); + + it('fires a track event when the user changes the faker method', async () => { + const result = await renderModal({ + mockServices: mockServicesWithMockDataResponse, + schemaAnalysis: mockSchemaAnalysis, + }); + + // advance to the schema editor step + userEvent.click(screen.getByText('Confirm')); + await waitFor(() => { + expect(screen.getByTestId('faker-schema-editor')).to.exist; + }); + + const fakerMethodSelect = screen.getByLabelText('Faker Function'); + userEvent.click(fakerMethodSelect); + const emailOption = await screen.findByRole('option', { + name: 'internet.email', + }); + userEvent.click(emailOption); + + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Faker Method Changed', + { + field_name: 'name', + json_type: 'String', + previous_faker_method: 'person.firstName', + new_faker_method: 'internet.email', + } + ); + }); + }); + + it('fires a track event when the user proceeds to the next step', async () => { + const result = await renderModal({ + mockServices: mockServicesWithMockDataResponse, + }); + + userEvent.click(screen.getByText('Confirm')); + + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator Screen Proceeded', + { + from_screen: MockDataGeneratorStep.SCHEMA_CONFIRMATION, + to_screen: + MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP[ + MockDataGeneratorStep.SCHEMA_CONFIRMATION + ], + } + ); + }); + }); }); describe('on the document count step', () => { @@ -810,6 +939,33 @@ describe('MockDataGeneratorModal', () => { userEvent.type(documentCountInput, '2000'); expect(screen.getByText('200.0 kB')).to.exist; }); + + it('fires a track event when the document count is changed', async () => { + const result = await renderModal({ + currentStep: MockDataGeneratorStep.DOCUMENT_COUNT, + schemaAnalysis: { + ...defaultSchemaAnalysisState, + schemaMetadata: { + ...defaultSchemaAnalysisState.schemaMetadata, + avgDocumentSize: 100, // 100 bytes + }, + }, + }); + + const documentCountInput = screen.getByLabelText( + 'Documents to generate in current collection' + ); + userEvent.clear(documentCountInput); + userEvent.type(documentCountInput, '2222'); + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Document Count Changed', + { + document_count: 2222, + } + ); + }); + }); }); describe('on the generate data step', () => { @@ -1075,6 +1231,106 @@ describe('MockDataGeneratorModal', () => { expect(screen.getByText('insertMany')).to.exist; }); + it('fires a track event when the script is generated', async () => { + const result = await renderModal({ + currentStep: MockDataGeneratorStep.GENERATE_DATA, + fakerSchemaGeneration: { + status: 'completed', + originalLlmResponse: { + name: { + fakerMethod: 'person.firstName', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + email: { + fakerMethod: 'internet.email', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + }, + editedFakerSchema: { + name: { + fakerMethod: 'person.firstName', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + email: { + fakerMethod: 'internet.email', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + }, + requestId: 'test-request-id', + }, + }); + + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Script Generated', + { + field_count: 2, + output_docs_count: 100, + } + ); + }); + }); + + it('fires a track event when the mongosh script is copied', async () => { + const result = await renderModal({ + currentStep: MockDataGeneratorStep.GENERATE_DATA, + fakerSchemaGeneration: { + status: 'completed', + originalLlmResponse: { + name: { + fakerMethod: 'person.firstName', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + email: { + fakerMethod: 'internet.email', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + }, + editedFakerSchema: { + name: { + fakerMethod: 'person.firstName', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + email: { + fakerMethod: 'internet.email', + fakerArgs: [], + probability: 1.0, + mongoType: 'String', + }, + }, + requestId: 'test-request-id', + }, + }); + + const codeCopyButtons = screen.getAllByTestId('lg-code-copy_button'); + const mongoshCopyButton = codeCopyButtons[1]; + + expect(codeCopyButtons).to.have.length(2); + userEvent.click(mongoshCopyButton); + await waitFor(() => { + expect(result.track).to.have.been.calledWith( + 'Mock Data Script Copied', + { + step: DataGenerationStep.RUN_SCRIPT, + } + ); + }); + }); + it('shows userConnectionString in the mongosh command when available', async () => { const atlasConnectionInfo: ConnectionInfo = { id: 'test-atlas-connection', @@ -1169,7 +1425,6 @@ describe('MockDataGeneratorModal', () => { StepButtonLabelMap ) as unknown as MockDataGeneratorStep[]; - // note: these tests can be removed after every modal step is implemented steps.forEach((currentStep) => { it(`renders the button with the correct label when the user is in step "${currentStep}"`, async () => { await renderModal({ currentStep }); @@ -1177,6 +1432,16 @@ describe('MockDataGeneratorModal', () => { StepButtonLabelMap[currentStep] ); }); + + it('fires a track event when the user is viewing a mock data generator step', async () => { + const result = await renderModal({ currentStep }); + expect(result.track).to.have.been.calledWith( + 'Mock Data Generator Screen Viewed', + { + screen: currentStep, + } + ); + }); }); }); }); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx index 74f48452bc9..c8bf3155baa 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { connect } from 'react-redux'; import { @@ -17,6 +17,7 @@ import { type MockDataGeneratorState, MockDataGeneratorStep } from './types'; import { DEFAULT_DOCUMENT_COUNT, MAX_DOCUMENT_COUNT, + MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP, StepButtonLabelMap, } from './constants'; import type { CollectionState } from '../../modules/collection-tab'; @@ -32,6 +33,11 @@ import FakerSchemaEditorScreen from './faker-schema-editor-screen'; import ScriptScreen from './script-screen'; import DocumentCountScreen from './document-count-screen'; import PreviewScreen from './preview-screen'; +import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; +import { + useIsAIFeatureEnabled, + usePreference, +} from 'compass-preferences-model/provider'; const footerStyles = css` flex-direction: row; @@ -75,6 +81,11 @@ const MockDataGeneratorModal = ({ const [documentCount, setDocumentCount] = React.useState( DEFAULT_DOCUMENT_COUNT ); + const track = useTelemetry(); + const isAIFeatureEnabled = useIsAIFeatureEnabled(); + const isSampleDocumentPassingEnabled = usePreference( + 'enableGenAISampleDocumentPassing' + ); const modalBodyContent = useMemo(() => { switch (currentStep) { @@ -114,6 +125,12 @@ const MockDataGeneratorModal = ({ setDocumentCount, ]); + useEffect(() => { + track('Mock Data Generator Screen Viewed', { + screen: currentStep, + }); + }, [currentStep, track]); + const isNextButtonDisabled = (currentStep === MockDataGeneratorStep.SCHEMA_EDITOR && !isSchemaConfirmed) || @@ -122,7 +139,13 @@ const MockDataGeneratorModal = ({ (currentStep === MockDataGeneratorStep.DOCUMENT_COUNT && documentCount > MAX_DOCUMENT_COUNT); - const handleNextClick = () => { + const handleNextClick = useCallback(() => { + const nextStep = MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP[currentStep]; + track('Mock Data Generator Screen Proceeded', { + from_screen: currentStep, + to_screen: nextStep, + }); + if (currentStep === MockDataGeneratorStep.GENERATE_DATA) { onClose(); } else if (currentStep === MockDataGeneratorStep.SCHEMA_CONFIRMATION) { @@ -130,7 +153,7 @@ const MockDataGeneratorModal = ({ } else { onNextStep(); } - }; + }, [currentStep, onConfirmSchema, onNextStep, onClose, track]); const shouldShowNamespace = currentStep !== MockDataGeneratorStep.GENERATE_DATA; @@ -143,13 +166,28 @@ const MockDataGeneratorModal = ({ onPreviousStep(); }; + const onModalClose = useCallback(() => { + track('Mock Data Generator Dismissed', { + screen: currentStep, + gen_ai_features_enabled: isAIFeatureEnabled, + send_sample_values_enabled: isSampleDocumentPassingEnabled, + }); + onClose(); + }, [ + currentStep, + track, + onClose, + isAIFeatureEnabled, + isSampleDocumentPassingEnabled, + ]); + return ( { if (!open) { - onClose(); + onModalClose(); } }} data-testid="generate-mock-data-modal" @@ -171,7 +209,7 @@ const MockDataGeneratorModal = ({ Back
- +