From ac0fde98f13d636f19d663dae7b57df024c653d5 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Thu, 16 Oct 2025 15:59:39 -0400 Subject: [PATCH 1/3] CLOUDP-349751: aggregation tab --- .../pipeline-toolbar/index.spec.tsx | 181 ++++++++++++++++++ .../src/components/pipeline-toolbar/index.tsx | 40 ++++ 2 files changed, 221 insertions(+) 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..0ee6cd6a2e4 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,180 @@ 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 not show skills banner when user has dismissed it', async function () { + // Mock localStorage to simulate dismissed state + const originalLocalStorage = global.localStorage; + const mockLocalStorage = { + getItem: sinon.stub().returns('true'), // Banner was dismissed + setItem: sinon.stub(), + removeItem: sinon.stub(), + clear: sinon.stub(), + length: 0, + key: sinon.stub(), + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + global.localStorage = mockLocalStorage as any; + + try { + await renderPipelineToolbarWithExperimentation({ + isInExperiment: true, + isInVariant: true, + }); + + expect( + screen.queryByText( + 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' + ) + ).to.not.exist; + } finally { + global.localStorage = originalLocalStorage; + } + }); + + 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..1238add0ce1 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'; @@ -40,6 +48,9 @@ const optionsStyles = css({ marginTop: spacing[200], }); +const DISMISSED_ATLAS_AGG_SKILL_BANNER_LOCAL_STORAGE_KEY = + 'mongodb_compass_dismissedAtlasAggSkillBanner' as const; + export type PipelineToolbarProps = { isAIInputVisible?: boolean; isAggregationGeneratedFromQuery?: boolean; @@ -58,7 +69,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( + DISMISSED_ATLAS_AGG_SKILL_BANNER_LOCAL_STORAGE_KEY, + false + ); + + const { shouldShowAtlasSkillsBanner } = useAtlasSkillsBanner( + SkillsBannerContextEnum.Aggregation + ); + return (
= ({
)} + + { + setDismissed(true); + track('Aggregation Skill CTA Dismissed', { + context: 'Atlas Skills', + }); + }} + showBanner={shouldShowAtlasSkillsBanner && !dismissed} + onCtaClick={() => { + track('Aggregation Skill CTA Clicked', { + context: 'Atlas Skills', + }); + }} + /> + {isBuilderView ? (
From c57193d02e7530d44b424154652a72c9d110092e Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Thu, 16 Oct 2025 17:28:11 -0400 Subject: [PATCH 2/3] CLOUDP-349751: fix test --- .../pipeline-toolbar/index.spec.tsx | 30 ------------------- 1 file changed, 30 deletions(-) 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 0ee6cd6a2e4..fda5bdb7b40 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.spec.tsx @@ -272,36 +272,6 @@ describe('PipelineToolbar', function () { .exist; }); - it('should not show skills banner when user has dismissed it', async function () { - // Mock localStorage to simulate dismissed state - const originalLocalStorage = global.localStorage; - const mockLocalStorage = { - getItem: sinon.stub().returns('true'), // Banner was dismissed - setItem: sinon.stub(), - removeItem: sinon.stub(), - clear: sinon.stub(), - length: 0, - key: sinon.stub(), - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - global.localStorage = mockLocalStorage as any; - - try { - await renderPipelineToolbarWithExperimentation({ - isInExperiment: true, - isInVariant: true, - }); - - expect( - screen.queryByText( - 'Learn how to build aggregation pipelines to process, transform, and analyze data efficiently.' - ) - ).to.not.exist; - } finally { - global.localStorage = originalLocalStorage; - } - }); - it('should dismiss banner when close button is clicked', async function () { await renderPipelineToolbarWithExperimentation({ isInExperiment: true, From cbf865c3652976f58cb3d8a94da53ee1f6b98e81 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Fri, 17 Oct 2025 13:23:02 -0400 Subject: [PATCH 3/3] CLOUDP-349751: analytics update --- .../src/components/pipeline-toolbar/index.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx index 1238add0ce1..edd26f02345 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx @@ -48,9 +48,6 @@ const optionsStyles = css({ marginTop: spacing[200], }); -const DISMISSED_ATLAS_AGG_SKILL_BANNER_LOCAL_STORAGE_KEY = - 'mongodb_compass_dismissedAtlasAggSkillBanner' as const; - export type PipelineToolbarProps = { isAIInputVisible?: boolean; isAggregationGeneratedFromQuery?: boolean; @@ -73,7 +70,7 @@ export const PipelineToolbar: React.FunctionComponent = ({ const [isOptionsVisible, setIsOptionsVisible] = useState(false); // @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 const [dismissed, setDismissed] = usePersistedState( - DISMISSED_ATLAS_AGG_SKILL_BANNER_LOCAL_STORAGE_KEY, + 'mongodb_compass_dismissedAtlasAggSkillBanner', false ); @@ -109,14 +106,14 @@ export const PipelineToolbar: React.FunctionComponent = ({ skillsUrl="https://learn.mongodb.com/courses/fundamentals-of-data-transformation?team=growth" onCloseSkillsBanner={() => { setDismissed(true); - track('Aggregation Skill CTA Dismissed', { - context: 'Atlas Skills', + track('Atlas Skills CTA Dismissed', { + context: 'Aggregation Tab', }); }} showBanner={shouldShowAtlasSkillsBanner && !dismissed} onCtaClick={() => { - track('Aggregation Skill CTA Clicked', { - context: 'Atlas Skills', + track('Atlas Skills CTA Clicked', { + context: 'Aggregation Tab', }); }} />