diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx index f58c7bca8f6..fda5bdb7b40 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx @@ -6,9 +6,14 @@ import { userEvent, } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; +import sinon from 'sinon'; import { renderWithStore } from '../../../test/configure-store'; import { PipelineToolbar } from './index'; import { createElectronPipelineStorage } from '@mongodb-js/my-queries-storage/electron'; +import { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry'; +import { ExperimentTestGroup } from '@mongodb-js/compass-telemetry/provider'; +import type { PreferencesAccess } from 'compass-preferences-model'; +import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; describe('PipelineToolbar', function () { describe('renders with setting row - visible', function () { @@ -141,4 +146,150 @@ describe('PipelineToolbar', function () { expect(() => within(toolbar).getByTestId('pipeline-settings')).to.throw; }); }); + + // @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 + describe('Atlas Skills Banner', function () { + let preferences: PreferencesAccess; + + beforeEach(async function () { + preferences = await createSandboxFromDefaultPreferences(); + }); + + // Helper function to render PipelineToolbar with mocked experimentation provider + async function renderPipelineToolbarWithExperimentation(experimentationOptions?: { + isInExperiment?: boolean; + isInVariant?: boolean; + }) { + const mockUseAssignment = sinon.stub(); + const mockUseTrackInSample = sinon.stub(); + const mockAssignExperiment = sinon.stub(); + const mockGetAssignment = sinon.stub(); + + const commonAsyncStatus = { + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + }; + + // Configure the mock based on experiment options + if (experimentationOptions?.isInExperiment) { + if (experimentationOptions?.isInVariant) { + mockUseAssignment.returns({ + assignment: { + assignmentData: { + variant: ExperimentTestGroup.atlasSkillsVariant, + }, + }, + ...commonAsyncStatus, + }); + } else { + mockUseAssignment.returns({ + assignment: { + assignmentData: { + variant: ExperimentTestGroup.atlasSkillsControl, + }, + }, + ...commonAsyncStatus, + }); + } + } else { + mockUseAssignment.returns({ + assignment: null, + ...commonAsyncStatus, + }); + } + + mockUseTrackInSample.returns(commonAsyncStatus); + mockAssignExperiment.returns(Promise.resolve(null)); + mockGetAssignment.returns(Promise.resolve(null)); + + return await renderWithStore( + + + , + { pipeline: [] }, // Initial state + undefined, // Connection info + { + preferences, + } + ); + } + + it('should show skills banner when user is in experiment and in variant', async function () { + await renderPipelineToolbarWithExperimentation({ + isInExperiment: true, + isInVariant: true, + }); + + expect( + screen.getByText( + 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' + ) + ).to.be.visible; + expect(screen.getByRole('link', { name: /go to skills/i })).to.be.visible; + expect(screen.getByLabelText('Award Icon')).to.be.visible; + }); + + it('should not show skills banner when user is in experiment but not in variant', async function () { + await renderPipelineToolbarWithExperimentation({ + isInExperiment: true, + isInVariant: false, + }); + + expect( + screen.queryByText( + 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' + ) + ).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not + .exist; + }); + + it('should not show skills banner by default when user is not in experiment', async function () { + await renderPipelineToolbarWithExperimentation({ + isInExperiment: false, + isInVariant: false, + }); + + expect( + screen.queryByText( + 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' + ) + ).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not + .exist; + }); + + it('should dismiss banner when close button is clicked', async function () { + await renderPipelineToolbarWithExperimentation({ + isInExperiment: true, + isInVariant: true, + }); + + const closeButton = screen.getByRole('button', { + name: 'Dismiss Skills Banner', + }); + + expect(closeButton).to.be.visible; + userEvent.click(closeButton); + + expect( + screen.queryByText( + 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' + ) + ).to.not.exist; + }); + }); }); diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx index 4ec3c280110..edd26f02345 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx @@ -5,9 +5,17 @@ import { spacing, palette, useDarkMode, + usePersistedState, + AtlasSkillsBanner, } from '@mongodb-js/compass-components'; + import { connect } from 'react-redux'; import { useIsAIFeatureEnabled } from 'compass-preferences-model/provider'; +import { + useTelemetry, + SkillsBannerContextEnum, + useAtlasSkillsBanner, +} from '@mongodb-js/compass-telemetry/provider'; import PipelineHeader from './pipeline-header'; import PipelineOptions from './pipeline-options'; @@ -58,7 +66,18 @@ export const PipelineToolbar: React.FunctionComponent = ({ }) => { const darkMode = useDarkMode(); const isAIFeatureEnabled = useIsAIFeatureEnabled(); + const track = useTelemetry(); const [isOptionsVisible, setIsOptionsVisible] = useState(false); + // @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 + const [dismissed, setDismissed] = usePersistedState( + 'mongodb_compass_dismissedAtlasAggSkillBanner', + false + ); + + const { shouldShowAtlasSkillsBanner } = useAtlasSkillsBanner( + SkillsBannerContextEnum.Aggregation + ); + return (
= ({
)} + + { + setDismissed(true); + track('Atlas Skills CTA Dismissed', { + context: 'Aggregation Tab', + }); + }} + showBanner={shouldShowAtlasSkillsBanner && !dismissed} + onCtaClick={() => { + track('Atlas Skills CTA Clicked', { + context: 'Aggregation Tab', + }); + }} + /> + {isBuilderView ? (