From 0e7316e9200fd98a19d30adc70d7f9211f4fe6ab Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Fri, 10 Oct 2025 14:46:06 -0400 Subject: [PATCH 01/13] CLOUDP-349749: reusable skills banner for atlas experiment --- package-lock.json | 22 ++++ .../src/components/atlas-skills-banner.tsx | 116 ++++++++++++++++++ packages/compass-components/src/index.ts | 2 + .../src/growth-experiments.ts | 3 + 4 files changed, 143 insertions(+) create mode 100644 packages/compass-components/src/components/atlas-skills-banner.tsx diff --git a/package-lock.json b/package-lock.json index 8e93d85b809..36330208117 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7196,6 +7196,17 @@ "@leafygreen-ui/leafygreen-provider": "^4.0.2" } }, + "node_modules/@leafygreen-ui/icon-v14": { + "name": "@leafygreen-ui/icon", + "version": "14.4.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.4.0.tgz", + "integrity": "sha512-j0ywdXxWTvnOa4vKsTAJV6q8jASQ/b4dJRkHun2DpzvWAqRuw7/eTloSuSRgGXbWJBiNPUsqnHqysQGeXqKoMA==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.0", + "lodash": "^4.17.21" + } + }, "node_modules/@leafygreen-ui/info-sprinkle": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/info-sprinkle/-/info-sprinkle-4.0.2.tgz", @@ -48466,6 +48477,7 @@ "@leafygreen-ui/hooks": "^8.3.4", "@leafygreen-ui/icon": "^14.6.0", "@leafygreen-ui/icon-button": "^16.0.2", + "@leafygreen-ui/icon-v14": "npm:@leafygreen-ui/icon@14.4.0", "@leafygreen-ui/info-sprinkle": "^4.0.2", "@leafygreen-ui/input-option": "^3.0.12", "@leafygreen-ui/leafygreen-provider": "^4.0.2", @@ -59617,6 +59629,15 @@ "polished": "^4.2.2" } }, + "@leafygreen-ui/icon-v14": { + "version": "npm:@leafygreen-ui/icon@14.4.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.4.0.tgz", + "integrity": "sha512-j0ywdXxWTvnOa4vKsTAJV6q8jASQ/b4dJRkHun2DpzvWAqRuw7/eTloSuSRgGXbWJBiNPUsqnHqysQGeXqKoMA==", + "requires": { + "@leafygreen-ui/emotion": "^4.0.9", + "lodash": "^4.17.21" + } + }, "@leafygreen-ui/info-sprinkle": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/info-sprinkle/-/info-sprinkle-4.0.2.tgz", @@ -62465,6 +62486,7 @@ "@leafygreen-ui/hooks": "^8.3.4", "@leafygreen-ui/icon": "^14.6.0", "@leafygreen-ui/icon-button": "^16.0.2", + "@leafygreen-ui/icon-v14": "npm:@leafygreen-ui/icon@14.4.0", "@leafygreen-ui/info-sprinkle": "^4.0.2", "@leafygreen-ui/input-option": "^3.0.12", "@leafygreen-ui/leafygreen-provider": "^4.0.2", diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx new file mode 100644 index 00000000000..0072e155d7c --- /dev/null +++ b/packages/compass-components/src/components/atlas-skills-banner.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { css } from '@leafygreen-ui/emotion'; +import IconButton from '@leafygreen-ui/icon-button'; +import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge'; +import Icon from '@leafygreen-ui/icon'; +// @ts-expect-error - Using icon v14 via alias for Award icon +import AwardIcon from '@leafygreen-ui/icon-v14/dist/Award'; +import { spacing } from '@leafygreen-ui/tokens'; +import Button from '@leafygreen-ui/button'; +import { + ExperimentTestGroup, + ExperimentTestName, + useAssignment, + useTelemetry, +} from '@mongodb-js/compass-telemetry/provider'; +import { palette } from '..'; + +const skillsCTAContent = css({ + border: `1px ${palette.gray.light1} solid`, + borderRadius: spacing[300], + padding: spacing[300], + paddingLeft: spacing[400], + display: 'flex', + width: '100%', + alignItems: 'center', +}); + +const skillsCTAText = css({ + display: 'flex', + alignSelf: 'center', + paddingLeft: spacing[200], +}); + +const badgeStyles = css({ + padding: `0 ${spacing[200]}px`, + minHeight: spacing[600], +}); + +const learnMoreBtnStyles = css({ + marginLeft: spacing[200], +}); + +const closeButtonStyles = css({ + marginLeft: 'auto', +}); + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export const AtlasSkillsBanner: React.FunctionComponent<{ + ctaText: string; + onCloseSkillsBanner: () => void; + skillsUrl: string; + showBanner: boolean; +}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, showBanner }) => { + const track = useTelemetry(); + + // Get experiment assignment for Atlas Skills Experiment + const atlasSkillsAssignment = useAssignment( + ExperimentTestName.atlasSkills, + true // trackIsInSample - this will fire the "Experiment Viewed" event + ); + + // // Track experiment viewed with context when assignment is available + // React.useEffect(() => { + // if (atlasSkillsAssignment?.assignment && showBanner) { + // track('Experiment Viewed', { + // test_name: `${ExperimentTestName.atlasSkills}${context ? `_${context}` : ''}`, + // }); + // } + // }, [atlasSkillsAssignment, showBanner, context, track]); + + const isInSkillsVariant = + atlasSkillsAssignment?.assignment?.assignmentData?.variant === + ExperimentTestGroup.atlasSkillsVariant; + + return showBanner && isInSkillsVariant ? ( +
+ + + +
{ctaText}
+ + + onCloseSkillsBanner()} + > + + +
+ ) : null; +}; diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index ae30f0f6dc3..daa7f0afa53 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -245,3 +245,5 @@ export type { NodeField, NodeGlyph, } from '@mongodb-js/diagramming'; +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export { AtlasSkillsBanner } from './components/atlas-skills-banner'; diff --git a/packages/compass-telemetry/src/growth-experiments.ts b/packages/compass-telemetry/src/growth-experiments.ts index 6b242a921fd..f7b079e5531 100644 --- a/packages/compass-telemetry/src/growth-experiments.ts +++ b/packages/compass-telemetry/src/growth-experiments.ts @@ -1,9 +1,12 @@ export enum ExperimentTestName { earlyJourneyIndexesGuidance = 'EARLY_JOURNEY_INDEXES_GUIDANCE_20250328', mockDataGenerator = 'MOCK_DATA_GENERATOR_20251001', + atlasSkills = 'ATLAS_SKILLS_EXPERIMENT_20251007', } export enum ExperimentTestGroup { mockDataGeneratorVariant = 'mockDataGeneratorVariant', mockDataGeneratorControl = 'mockDataGeneratorControl', + atlasSkillsVariant = 'atlasSkillsVariant', + atlasSkillsControl = 'atlasSkillsControl', } From 569e1eef8c9920f175315aba0d5153db817f1b5b Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Mon, 13 Oct 2025 10:25:05 -0400 Subject: [PATCH 02/13] CLOUDP-349749: skills banner updates --- package-lock.json | 22 -- .../src/components/atlas-skills-banner.tsx | 116 ---------- .../atlas-skills-banner.d.ts | 1 + packages/compass-telemetry/package.json | 6 +- .../src/atlas-skills-banner.spec.tsx | 198 ++++++++++++++++++ .../src/atlas-skills-banner.tsx | 130 ++++++++++++ packages/compass-telemetry/src/index.ts | 7 + packages/compass-telemetry/src/provider.tsx | 15 +- 8 files changed, 353 insertions(+), 142 deletions(-) delete mode 100644 packages/compass-components/src/components/atlas-skills-banner.tsx create mode 100644 packages/compass-telemetry/atlas-skills-banner.d.ts create mode 100644 packages/compass-telemetry/src/atlas-skills-banner.spec.tsx create mode 100644 packages/compass-telemetry/src/atlas-skills-banner.tsx diff --git a/package-lock.json b/package-lock.json index 36330208117..8e93d85b809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7196,17 +7196,6 @@ "@leafygreen-ui/leafygreen-provider": "^4.0.2" } }, - "node_modules/@leafygreen-ui/icon-v14": { - "name": "@leafygreen-ui/icon", - "version": "14.4.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.4.0.tgz", - "integrity": "sha512-j0ywdXxWTvnOa4vKsTAJV6q8jASQ/b4dJRkHun2DpzvWAqRuw7/eTloSuSRgGXbWJBiNPUsqnHqysQGeXqKoMA==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/emotion": "^5.0.0", - "lodash": "^4.17.21" - } - }, "node_modules/@leafygreen-ui/info-sprinkle": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/info-sprinkle/-/info-sprinkle-4.0.2.tgz", @@ -48477,7 +48466,6 @@ "@leafygreen-ui/hooks": "^8.3.4", "@leafygreen-ui/icon": "^14.6.0", "@leafygreen-ui/icon-button": "^16.0.2", - "@leafygreen-ui/icon-v14": "npm:@leafygreen-ui/icon@14.4.0", "@leafygreen-ui/info-sprinkle": "^4.0.2", "@leafygreen-ui/input-option": "^3.0.12", "@leafygreen-ui/leafygreen-provider": "^4.0.2", @@ -59629,15 +59617,6 @@ "polished": "^4.2.2" } }, - "@leafygreen-ui/icon-v14": { - "version": "npm:@leafygreen-ui/icon@14.4.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.4.0.tgz", - "integrity": "sha512-j0ywdXxWTvnOa4vKsTAJV6q8jASQ/b4dJRkHun2DpzvWAqRuw7/eTloSuSRgGXbWJBiNPUsqnHqysQGeXqKoMA==", - "requires": { - "@leafygreen-ui/emotion": "^4.0.9", - "lodash": "^4.17.21" - } - }, "@leafygreen-ui/info-sprinkle": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/info-sprinkle/-/info-sprinkle-4.0.2.tgz", @@ -62486,7 +62465,6 @@ "@leafygreen-ui/hooks": "^8.3.4", "@leafygreen-ui/icon": "^14.6.0", "@leafygreen-ui/icon-button": "^16.0.2", - "@leafygreen-ui/icon-v14": "npm:@leafygreen-ui/icon@14.4.0", "@leafygreen-ui/info-sprinkle": "^4.0.2", "@leafygreen-ui/input-option": "^3.0.12", "@leafygreen-ui/leafygreen-provider": "^4.0.2", diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx deleted file mode 100644 index 0072e155d7c..00000000000 --- a/packages/compass-components/src/components/atlas-skills-banner.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import { css } from '@leafygreen-ui/emotion'; -import IconButton from '@leafygreen-ui/icon-button'; -import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge'; -import Icon from '@leafygreen-ui/icon'; -// @ts-expect-error - Using icon v14 via alias for Award icon -import AwardIcon from '@leafygreen-ui/icon-v14/dist/Award'; -import { spacing } from '@leafygreen-ui/tokens'; -import Button from '@leafygreen-ui/button'; -import { - ExperimentTestGroup, - ExperimentTestName, - useAssignment, - useTelemetry, -} from '@mongodb-js/compass-telemetry/provider'; -import { palette } from '..'; - -const skillsCTAContent = css({ - border: `1px ${palette.gray.light1} solid`, - borderRadius: spacing[300], - padding: spacing[300], - paddingLeft: spacing[400], - display: 'flex', - width: '100%', - alignItems: 'center', -}); - -const skillsCTAText = css({ - display: 'flex', - alignSelf: 'center', - paddingLeft: spacing[200], -}); - -const badgeStyles = css({ - padding: `0 ${spacing[200]}px`, - minHeight: spacing[600], -}); - -const learnMoreBtnStyles = css({ - marginLeft: spacing[200], -}); - -const closeButtonStyles = css({ - marginLeft: 'auto', -}); - -// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 -export const AtlasSkillsBanner: React.FunctionComponent<{ - ctaText: string; - onCloseSkillsBanner: () => void; - skillsUrl: string; - showBanner: boolean; -}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, showBanner }) => { - const track = useTelemetry(); - - // Get experiment assignment for Atlas Skills Experiment - const atlasSkillsAssignment = useAssignment( - ExperimentTestName.atlasSkills, - true // trackIsInSample - this will fire the "Experiment Viewed" event - ); - - // // Track experiment viewed with context when assignment is available - // React.useEffect(() => { - // if (atlasSkillsAssignment?.assignment && showBanner) { - // track('Experiment Viewed', { - // test_name: `${ExperimentTestName.atlasSkills}${context ? `_${context}` : ''}`, - // }); - // } - // }, [atlasSkillsAssignment, showBanner, context, track]); - - const isInSkillsVariant = - atlasSkillsAssignment?.assignment?.assignmentData?.variant === - ExperimentTestGroup.atlasSkillsVariant; - - return showBanner && isInSkillsVariant ? ( -
- - - -
{ctaText}
- - - onCloseSkillsBanner()} - > - - -
- ) : null; -}; diff --git a/packages/compass-telemetry/atlas-skills-banner.d.ts b/packages/compass-telemetry/atlas-skills-banner.d.ts new file mode 100644 index 00000000000..bf1f225d732 --- /dev/null +++ b/packages/compass-telemetry/atlas-skills-banner.d.ts @@ -0,0 +1 @@ +export * from './dist/atlas-skills-banner'; diff --git a/packages/compass-telemetry/package.json b/packages/compass-telemetry/package.json index 8736c36f71d..73882a9c9bb 100644 --- a/packages/compass-telemetry/package.json +++ b/packages/compass-telemetry/package.json @@ -25,11 +25,13 @@ "compass:main": "src/index.ts", "exports": { ".": "./dist/index.js", - "./provider": "./dist/provider.js" + "./provider": "./dist/provider.js", + "./atlas-skills-banner": "./dist/atlas-skills-banner.js" }, "compass:exports": { ".": "./src/index.ts", - "./provider": "./src/provider.tsx" + "./provider": "./src/provider.tsx", + "./atlas-skills-banner": "./src/atlas-skills-banner.tsx" }, "types": "./dist/index.d.ts", "scripts": { diff --git a/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx b/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx new file mode 100644 index 00000000000..fa8d6613110 --- /dev/null +++ b/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx @@ -0,0 +1,198 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { CompassExperimentationProvider } from './experimentation-provider'; +import { ExperimentTestGroup, ExperimentTestName } from './growth-experiments'; + +import { AtlasSkillsBanner } from './atlas-skills-banner'; + +describe('AtlasSkillsBanner Component', function () { + const defaultProps = { + ctaText: + 'New to MongoDB? Document modeling skills will accelerate your progress.', + skillsUrl: 'https://www.mongodb.com/skills', + onCloseSkillsBanner: sinon.spy(), + showBanner: true, + }; + + // Helper function to render component with experimentation provider + const renderWithExperimentationProvider = (props = defaultProps) => { + return render( + ({ + assignment: { + assignmentData: { + variant: ExperimentTestGroup.atlasSkillsVariant, + isInSample: true, + }, + experimentData: { + assignmentDate: new Date().toISOString(), + entityId: 'mock-entity-id', + entityType: 'USER', + id: 'mock-assignment-id', + name: ExperimentTestName.atlasSkills, + variant: ExperimentTestGroup.atlasSkillsVariant, + isInSample: true, + assignmentId: 'mock-assignment-id', + experimentId: 'mock-experiment-id', + experimentName: ExperimentTestName.atlasSkills, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + experimentName: ExperimentTestName.atlasSkills, + assignmentId: 'mock-assignment-id', + }, + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + })} + assignExperiment={() => Promise.resolve(null)} + getAssignment={() => Promise.resolve(null)} + > + + + ); + }; + + it('should render the banner with CTA text and badge icon', function () { + renderWithExperimentationProvider(); + + expect( + screen.getByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.be.visible; + + // Badge component renders with the custom SVG icon + const svgIcon = screen.getByRole('img', { hidden: true }); + expect(svgIcon).to.exist; + }); + + it('should render the "Go to Skills" button with correct link and OpenNewTab icon', function () { + renderWithExperimentationProvider(); + + const goToSkillsButton = screen.getByRole('link', { + name: /go to skills/i, + }); + expect(goToSkillsButton).to.be.visible; + expect(goToSkillsButton.getAttribute('href')).to.equal( + 'https://www.mongodb.com/skills' + ); + expect(goToSkillsButton.getAttribute('target')).to.equal('_blank'); + // Verify the button has an icon by checking for svg element + expect(goToSkillsButton.querySelector('svg')).to.exist; + }); + + it('should render the close button and call onCloseSkillsBanner when clicked', function () { + const onCloseSkillsBanner = sinon.spy(); + renderWithExperimentationProvider({ + ...defaultProps, + onCloseSkillsBanner, + }); + + const closeButton = screen.getByRole('button', { + name: 'Close Atlas Skills CTA', + }); + expect(closeButton).to.be.visible; + expect(closeButton.getAttribute('title')).to.equal( + 'Close Atlas Skills CTA' + ); + + userEvent.click(closeButton); + expect(onCloseSkillsBanner).to.have.been.calledOnce; + }); + + it('should not render when showBanner is false', function () { + renderWithExperimentationProvider({ + ...defaultProps, + showBanner: false, + }); + + // Banner should not be visible + expect( + screen.queryByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.not.exist; + expect(screen.queryByRole('img', { hidden: true })).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; + }); + + it('should not render when user is not in experiment variant', function () { + render( + ({ + assignment: { + assignmentData: { + variant: ExperimentTestGroup.atlasSkillsControl, // Control group + isInSample: true, + }, + experimentData: { + assignmentDate: new Date().toISOString(), + entityId: 'mock-entity-id', + entityType: 'USER', + id: 'mock-assignment-id', + name: ExperimentTestName.atlasSkills, + variant: ExperimentTestGroup.atlasSkillsControl, + isInSample: true, + assignmentId: 'mock-assignment-id', + experimentId: 'mock-experiment-id', + experimentName: ExperimentTestName.atlasSkills, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any, + experimentName: ExperimentTestName.atlasSkills, + assignmentId: 'mock-assignment-id', + }, + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + })} + assignExperiment={() => Promise.resolve(null)} + getAssignment={() => Promise.resolve(null)} + > + + + ); + + // Banner should not be visible when in control group + expect( + screen.queryByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.not.exist; + expect(screen.queryByRole('img', { hidden: true })).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; + }); + + it('should not render when experiment assignment is null', function () { + render( + ({ + assignment: null, // No experiment assignment + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + })} + assignExperiment={() => Promise.resolve(null)} + getAssignment={() => Promise.resolve(null)} + > + + + ); + + // Banner should not be visible when no assignment + expect( + screen.queryByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.not.exist; + expect(screen.queryByRole('img', { hidden: true })).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; + }); +}); diff --git a/packages/compass-telemetry/src/atlas-skills-banner.tsx b/packages/compass-telemetry/src/atlas-skills-banner.tsx new file mode 100644 index 00000000000..9a50eb72764 --- /dev/null +++ b/packages/compass-telemetry/src/atlas-skills-banner.tsx @@ -0,0 +1,130 @@ +/* eslint-disable @mongodb-js/compass/no-leafygreen-outside-compass-components */ +import React from 'react'; +import { css } from '@leafygreen-ui/emotion'; +import IconButton from '@leafygreen-ui/icon-button'; +import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge'; +import Icon from '@leafygreen-ui/icon'; +import { spacing } from '@leafygreen-ui/tokens'; +import Button from '@leafygreen-ui/button'; +import { palette } from '@mongodb-js/compass-components'; +import { + ExperimentTestGroup, + ExperimentTestName, + useAssignment, + useFireExperimentViewed, +} from './provider'; + +const skillsCTAContent = css({ + border: `1px ${palette.gray.light1} solid`, + borderRadius: spacing[300], + padding: spacing[300], + paddingLeft: spacing[400], + display: 'flex', + width: '100%', + alignItems: 'center', +}); + +const skillsCTAText = css({ + display: 'flex', + alignSelf: 'center', + paddingLeft: spacing[200], +}); + +const badgeStyles = css({ + padding: '0 10px', + minHeight: spacing[600], +}); + +const learnMoreBtnStyles = css({ + marginLeft: spacing[200], +}); + +const closeButtonStyles = css({ + marginLeft: 'auto', +}); + +export enum SkillsBannerContextEnum { + Documents = 'documents', + Aggregation = 'aggregation', + Indexes = 'indexes', + Schema = 'schema', +} + +// Helper hook for components that want to show the Atlas Skills banner +export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => { + const atlasSkillsAssignment = useAssignment( + ExperimentTestName.atlasSkills, + false + ); + + const isInSkillsVariant = + atlasSkillsAssignment?.assignment?.assignmentData?.variant === + ExperimentTestGroup.atlasSkillsVariant; + + // Track experiment viewed when user is in experiment and banner would be shown + useFireExperimentViewed({ + testName: ExperimentTestName.atlasSkills, + shouldFire: !!atlasSkillsAssignment, + additionalProperties: { screen: context }, + }); + + return { + shouldShowAtlasSkillsBanner: isInSkillsVariant, + }; +}; + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export const AtlasSkillsBanner: React.FunctionComponent<{ + ctaText: string; + onCloseSkillsBanner: () => void; + skillsUrl: string; + showBanner: boolean; +}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, showBanner }) => { + return showBanner ? ( +
+ + {/* Custom SVG for award icon */} + + + + + +
{ctaText}
+ + + onCloseSkillsBanner()} + > + + +
+ ) : null; +}; diff --git a/packages/compass-telemetry/src/index.ts b/packages/compass-telemetry/src/index.ts index 3fdacb2c506..bc10c2913f7 100644 --- a/packages/compass-telemetry/src/index.ts +++ b/packages/compass-telemetry/src/index.ts @@ -8,3 +8,10 @@ export type { export { CompassExperimentationProvider } from './experimentation-provider'; export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments'; + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export { + AtlasSkillsBanner, + SkillsBannerContextEnum, + useAtlasSkillsBanner, +} from './atlas-skills-banner'; diff --git a/packages/compass-telemetry/src/provider.tsx b/packages/compass-telemetry/src/provider.tsx index 4446dbbfb03..d1c9cab1dc3 100644 --- a/packages/compass-telemetry/src/provider.tsx +++ b/packages/compass-telemetry/src/provider.tsx @@ -132,19 +132,29 @@ export function useTrackOnChange( * * @param testName - The name of the experiment to track. * @param shouldFire - A boolean indicating whether to fire the event. Defaults to true. + * @param additionalProperties - Optional additional properties to include in the track call. * * @example * useFireExperimentViewed({ * testName: ExperimentTestName.earlyJourneyIndexesGuidance, * shouldFire: enableInIndexesGuidanceExp , * }); + * + * @example + * useFireExperimentViewed({ + * testName: ExperimentTestName.atlasSkills, + * shouldFire: showBanner, + * additionalProperties: { screen: 'aggregations' }, + * }); */ export const useFireExperimentViewed = ({ testName, shouldFire = true, + additionalProperties, }: { testName: ExperimentTestName; shouldFire?: boolean; + additionalProperties?: Record; }) => { useTrackOnChange( (track: TrackFunction) => { @@ -153,9 +163,10 @@ export const useFireExperimentViewed = ({ } track('Experiment Viewed', { test_name: testName, - }); + ...additionalProperties, + } as any); }, - [shouldFire, testName], + [shouldFire, testName, additionalProperties], undefined ); }; From d515b45dadbe7d4d14276c70d578734751b0c174 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Mon, 13 Oct 2025 12:33:12 -0400 Subject: [PATCH 03/13] CLOUDP-349749: useTrackInSample --- .../src/atlas-skills-banner.tsx | 8 ++--- .../src/experimentation-provider.tsx | 33 ++++++++++++++++++- packages/compass-telemetry/src/provider.tsx | 17 ++-------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/compass-telemetry/src/atlas-skills-banner.tsx b/packages/compass-telemetry/src/atlas-skills-banner.tsx index 9a50eb72764..f742ad12fce 100644 --- a/packages/compass-telemetry/src/atlas-skills-banner.tsx +++ b/packages/compass-telemetry/src/atlas-skills-banner.tsx @@ -11,7 +11,7 @@ import { ExperimentTestGroup, ExperimentTestName, useAssignment, - useFireExperimentViewed, + useTrackInSample, } from './provider'; const skillsCTAContent = css({ @@ -62,10 +62,8 @@ export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => { ExperimentTestGroup.atlasSkillsVariant; // Track experiment viewed when user is in experiment and banner would be shown - useFireExperimentViewed({ - testName: ExperimentTestName.atlasSkills, - shouldFire: !!atlasSkillsAssignment, - additionalProperties: { screen: context }, + useTrackInSample(ExperimentTestName.atlasSkills, !!atlasSkillsAssignment, { + screen: context, }); return { diff --git a/packages/compass-telemetry/src/experimentation-provider.tsx b/packages/compass-telemetry/src/experimentation-provider.tsx index 6bdbda73647..ae74459d727 100644 --- a/packages/compass-telemetry/src/experimentation-provider.tsx +++ b/packages/compass-telemetry/src/experimentation-provider.tsx @@ -20,10 +20,18 @@ type GetAssignmentFn = ( options?: types.GetAssignmentOptions ) => Promise | null>; +type UseTrackInSampleHook = ( + experimentName: ExperimentTestName, + shouldFireEvent?: boolean, + customProperties?: types.TypeData['experimentViewedProps'], + team?: types.TypeData['loggerTeam'] +) => typesReact.BasicHookResponse; + interface CompassExperimentationProviderContextValue { useAssignment: UseAssignmentHook; assignExperiment: AssignExperimentFn; getAssignment: GetAssignmentFn; + useTrackInSample: UseTrackInSampleHook; } const initialContext: CompassExperimentationProviderContextValue = { @@ -43,6 +51,15 @@ const initialContext: CompassExperimentationProviderContextValue = { getAssignment() { return Promise.resolve(null); }, + useTrackInSample() { + return { + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + }; + }, }; export const ExperimentationContext = @@ -54,17 +71,26 @@ export const CompassExperimentationProvider: React.FC<{ useAssignment: UseAssignmentHook; assignExperiment: AssignExperimentFn; getAssignment: GetAssignmentFn; -}> = ({ children, useAssignment, assignExperiment, getAssignment }) => { + useTrackInSample: UseTrackInSampleHook; +}> = ({ + children, + useAssignment, + assignExperiment, + getAssignment, + useTrackInSample, +}) => { // Use useRef to keep the functions up-to-date; Use mutation pattern to maintain the // same object reference to prevent unnecessary re-renders of consuming components const { current: contextValue } = useRef({ useAssignment, assignExperiment, getAssignment, + useTrackInSample, }); contextValue.useAssignment = useAssignment; contextValue.assignExperiment = assignExperiment; contextValue.getAssignment = getAssignment; + contextValue.useTrackInSample = useTrackInSample; return ( @@ -77,3 +103,8 @@ export const CompassExperimentationProvider: React.FC<{ export const useAssignment = (...args: Parameters) => { return useContext(ExperimentationContext).useAssignment(...args); }; + +// Hook for components to access experiment assignment +export const useTrackInSample = (...args: Parameters) => { + return useContext(ExperimentationContext).useTrackInSample(...args); +}; diff --git a/packages/compass-telemetry/src/provider.tsx b/packages/compass-telemetry/src/provider.tsx index d1c9cab1dc3..355f351c929 100644 --- a/packages/compass-telemetry/src/provider.tsx +++ b/packages/compass-telemetry/src/provider.tsx @@ -132,29 +132,19 @@ export function useTrackOnChange( * * @param testName - The name of the experiment to track. * @param shouldFire - A boolean indicating whether to fire the event. Defaults to true. - * @param additionalProperties - Optional additional properties to include in the track call. * * @example * useFireExperimentViewed({ * testName: ExperimentTestName.earlyJourneyIndexesGuidance, * shouldFire: enableInIndexesGuidanceExp , * }); - * - * @example - * useFireExperimentViewed({ - * testName: ExperimentTestName.atlasSkills, - * shouldFire: showBanner, - * additionalProperties: { screen: 'aggregations' }, - * }); */ export const useFireExperimentViewed = ({ testName, shouldFire = true, - additionalProperties, }: { testName: ExperimentTestName; shouldFire?: boolean; - additionalProperties?: Record; }) => { useTrackOnChange( (track: TrackFunction) => { @@ -163,14 +153,13 @@ export const useFireExperimentViewed = ({ } track('Experiment Viewed', { test_name: testName, - ...additionalProperties, - } as any); + }); }, - [shouldFire, testName, additionalProperties], + [shouldFire, testName], undefined ); }; export type { TrackFunction }; export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments'; -export { useAssignment } from './experimentation-provider'; +export { useAssignment, useTrackInSample } from './experimentation-provider'; From 583ce1b799ab39133ad1ecc8f1f105670add2859 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Mon, 13 Oct 2025 15:25:26 -0400 Subject: [PATCH 04/13] CLOUDP-349749: atlas skills comp w helper --- .../components/atlas-skills-banner.spec.tsx | 93 ++++++++ .../src/components/atlas-skills-banner.tsx | 88 ++++++++ .../src/atlas-skills-banner.spec.tsx | 198 ------------------ .../src/atlas-skills-banner.tsx | 128 ----------- .../compass-telemetry/src/atlas-skills.ts | 34 +++ packages/compass-telemetry/src/index.ts | 6 +- 6 files changed, 216 insertions(+), 331 deletions(-) create mode 100644 packages/compass-components/src/components/atlas-skills-banner.spec.tsx create mode 100644 packages/compass-components/src/components/atlas-skills-banner.tsx delete mode 100644 packages/compass-telemetry/src/atlas-skills-banner.spec.tsx delete mode 100644 packages/compass-telemetry/src/atlas-skills-banner.tsx create mode 100644 packages/compass-telemetry/src/atlas-skills.ts diff --git a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx new file mode 100644 index 00000000000..f929c42d44f --- /dev/null +++ b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; + +import { AtlasSkillsBanner } from './atlas-skills-banner'; + +describe('AtlasSkillsBanner Component', function () { + const defaultProps = { + ctaText: + 'New to MongoDB? Document modeling skills will accelerate your progress.', + skillsUrl: 'https://www.mongodb.com/skills', + onCloseSkillsBanner: sinon.spy(), + showBanner: true, + }; + + it('should render the banner with correct text', function () { + render(); + + expect( + screen.getByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.be.visible; + }); + + it('should render the badge with award icon', function () { + render(); + + // Check for the SVG award icon + const svgIcon = screen.getByRole('img', { hidden: true }); + expect(svgIcon).to.be.visible; + }); + + it('should render the "Go to Skills" button with correct href', function () { + render(); + + const goToSkillsButton = screen.getByRole('link', { + name: /go to skills/i, + }); + expect(goToSkillsButton).to.be.visible; + expect(goToSkillsButton.getAttribute('href')).to.equal( + 'https://www.mongodb.com/skills' + ); + expect(goToSkillsButton.getAttribute('target')).to.equal('_blank'); + }); + + it('should call onCtaClick when "Go to Skills" button is clicked', function () { + const onCtaClick = sinon.spy(); + render(); + + const goToSkillsButton = screen.getByRole('link', { + name: /go to skills/i, + }); + + userEvent.click(goToSkillsButton); + expect(onCtaClick).to.have.been.calledOnce; + }); + + it('should render the close button and call onCloseSkillsBanner when clicked', function () { + const onCloseSkillsBanner = sinon.spy(); + render( + + ); + + const closeButton = screen.getByRole('button', { + name: 'Close Atlas Skills CTA', + }); + expect(closeButton).to.be.visible; + expect(closeButton.getAttribute('title')).to.equal( + 'Close Atlas Skills CTA' + ); + + userEvent.click(closeButton); + expect(onCloseSkillsBanner).to.have.been.calledOnce; + }); + + it('should not render when showBanner is false', function () { + render(); + + // Banner should not be visible + expect( + screen.queryByText( + 'New to MongoDB? Document modeling skills will accelerate your progress.' + ) + ).to.not.exist; + expect(screen.queryByRole('img', { hidden: true })).to.not.exist; + expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; + }); +}); diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx new file mode 100644 index 00000000000..fca99e205ad --- /dev/null +++ b/packages/compass-components/src/components/atlas-skills-banner.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { css } from '@leafygreen-ui/emotion'; +import IconButton from '@leafygreen-ui/icon-button'; +import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge'; +import Icon from '@leafygreen-ui/icon'; +import { spacing } from '@leafygreen-ui/tokens'; +import Button from '@leafygreen-ui/button'; +import { palette } from '@leafygreen-ui/palette'; + +const skillsCTAContent = css({ + border: `1px ${palette.gray.light1} solid`, + borderRadius: spacing[300], + padding: spacing[300], + paddingLeft: spacing[400], + display: 'flex', + width: '100%', + alignItems: 'center', +}); + +const skillsCTAText = css({ + display: 'flex', + alignSelf: 'center', + paddingLeft: spacing[200], +}); + +const badgeStyles = css({ + padding: '0 10px', + minHeight: spacing[600], +}); + +const learnMoreBtnStyles = css({ + marginLeft: spacing[200], +}); + +const closeButtonStyles = css({ + marginLeft: 'auto', +}); + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export const AtlasSkillsBanner: React.FunctionComponent<{ + ctaText: string; + onCloseSkillsBanner: () => void; + onCtaClick?: () => void; + skillsUrl: string; + showBanner: boolean; +}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, onCtaClick, showBanner }) => { + return showBanner ? ( +
+ + {/* Custom SVG for award icon */} + + + + +
{ctaText}
+ + + + + +
+ ) : null; +}; diff --git a/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx b/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx deleted file mode 100644 index fa8d6613110..00000000000 --- a/packages/compass-telemetry/src/atlas-skills-banner.spec.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import React from 'react'; -import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; -import { expect } from 'chai'; -import sinon from 'sinon'; -import { CompassExperimentationProvider } from './experimentation-provider'; -import { ExperimentTestGroup, ExperimentTestName } from './growth-experiments'; - -import { AtlasSkillsBanner } from './atlas-skills-banner'; - -describe('AtlasSkillsBanner Component', function () { - const defaultProps = { - ctaText: - 'New to MongoDB? Document modeling skills will accelerate your progress.', - skillsUrl: 'https://www.mongodb.com/skills', - onCloseSkillsBanner: sinon.spy(), - showBanner: true, - }; - - // Helper function to render component with experimentation provider - const renderWithExperimentationProvider = (props = defaultProps) => { - return render( - ({ - assignment: { - assignmentData: { - variant: ExperimentTestGroup.atlasSkillsVariant, - isInSample: true, - }, - experimentData: { - assignmentDate: new Date().toISOString(), - entityId: 'mock-entity-id', - entityType: 'USER', - id: 'mock-assignment-id', - name: ExperimentTestName.atlasSkills, - variant: ExperimentTestGroup.atlasSkillsVariant, - isInSample: true, - assignmentId: 'mock-assignment-id', - experimentId: 'mock-experiment-id', - experimentName: ExperimentTestName.atlasSkills, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - experimentName: ExperimentTestName.atlasSkills, - assignmentId: 'mock-assignment-id', - }, - asyncStatus: null, - error: null, - isLoading: false, - isError: false, - isSuccess: true, - })} - assignExperiment={() => Promise.resolve(null)} - getAssignment={() => Promise.resolve(null)} - > - - - ); - }; - - it('should render the banner with CTA text and badge icon', function () { - renderWithExperimentationProvider(); - - expect( - screen.getByText( - 'New to MongoDB? Document modeling skills will accelerate your progress.' - ) - ).to.be.visible; - - // Badge component renders with the custom SVG icon - const svgIcon = screen.getByRole('img', { hidden: true }); - expect(svgIcon).to.exist; - }); - - it('should render the "Go to Skills" button with correct link and OpenNewTab icon', function () { - renderWithExperimentationProvider(); - - const goToSkillsButton = screen.getByRole('link', { - name: /go to skills/i, - }); - expect(goToSkillsButton).to.be.visible; - expect(goToSkillsButton.getAttribute('href')).to.equal( - 'https://www.mongodb.com/skills' - ); - expect(goToSkillsButton.getAttribute('target')).to.equal('_blank'); - // Verify the button has an icon by checking for svg element - expect(goToSkillsButton.querySelector('svg')).to.exist; - }); - - it('should render the close button and call onCloseSkillsBanner when clicked', function () { - const onCloseSkillsBanner = sinon.spy(); - renderWithExperimentationProvider({ - ...defaultProps, - onCloseSkillsBanner, - }); - - const closeButton = screen.getByRole('button', { - name: 'Close Atlas Skills CTA', - }); - expect(closeButton).to.be.visible; - expect(closeButton.getAttribute('title')).to.equal( - 'Close Atlas Skills CTA' - ); - - userEvent.click(closeButton); - expect(onCloseSkillsBanner).to.have.been.calledOnce; - }); - - it('should not render when showBanner is false', function () { - renderWithExperimentationProvider({ - ...defaultProps, - showBanner: false, - }); - - // Banner should not be visible - expect( - screen.queryByText( - 'New to MongoDB? Document modeling skills will accelerate your progress.' - ) - ).to.not.exist; - expect(screen.queryByRole('img', { hidden: true })).to.not.exist; - expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; - }); - - it('should not render when user is not in experiment variant', function () { - render( - ({ - assignment: { - assignmentData: { - variant: ExperimentTestGroup.atlasSkillsControl, // Control group - isInSample: true, - }, - experimentData: { - assignmentDate: new Date().toISOString(), - entityId: 'mock-entity-id', - entityType: 'USER', - id: 'mock-assignment-id', - name: ExperimentTestName.atlasSkills, - variant: ExperimentTestGroup.atlasSkillsControl, - isInSample: true, - assignmentId: 'mock-assignment-id', - experimentId: 'mock-experiment-id', - experimentName: ExperimentTestName.atlasSkills, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, - experimentName: ExperimentTestName.atlasSkills, - assignmentId: 'mock-assignment-id', - }, - asyncStatus: null, - error: null, - isLoading: false, - isError: false, - isSuccess: true, - })} - assignExperiment={() => Promise.resolve(null)} - getAssignment={() => Promise.resolve(null)} - > - - - ); - - // Banner should not be visible when in control group - expect( - screen.queryByText( - 'New to MongoDB? Document modeling skills will accelerate your progress.' - ) - ).to.not.exist; - expect(screen.queryByRole('img', { hidden: true })).to.not.exist; - expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; - }); - - it('should not render when experiment assignment is null', function () { - render( - ({ - assignment: null, // No experiment assignment - asyncStatus: null, - error: null, - isLoading: false, - isError: false, - isSuccess: true, - })} - assignExperiment={() => Promise.resolve(null)} - getAssignment={() => Promise.resolve(null)} - > - - - ); - - // Banner should not be visible when no assignment - expect( - screen.queryByText( - 'New to MongoDB? Document modeling skills will accelerate your progress.' - ) - ).to.not.exist; - expect(screen.queryByRole('img', { hidden: true })).to.not.exist; - expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; - }); -}); diff --git a/packages/compass-telemetry/src/atlas-skills-banner.tsx b/packages/compass-telemetry/src/atlas-skills-banner.tsx deleted file mode 100644 index f742ad12fce..00000000000 --- a/packages/compass-telemetry/src/atlas-skills-banner.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* eslint-disable @mongodb-js/compass/no-leafygreen-outside-compass-components */ -import React from 'react'; -import { css } from '@leafygreen-ui/emotion'; -import IconButton from '@leafygreen-ui/icon-button'; -import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge'; -import Icon from '@leafygreen-ui/icon'; -import { spacing } from '@leafygreen-ui/tokens'; -import Button from '@leafygreen-ui/button'; -import { palette } from '@mongodb-js/compass-components'; -import { - ExperimentTestGroup, - ExperimentTestName, - useAssignment, - useTrackInSample, -} from './provider'; - -const skillsCTAContent = css({ - border: `1px ${palette.gray.light1} solid`, - borderRadius: spacing[300], - padding: spacing[300], - paddingLeft: spacing[400], - display: 'flex', - width: '100%', - alignItems: 'center', -}); - -const skillsCTAText = css({ - display: 'flex', - alignSelf: 'center', - paddingLeft: spacing[200], -}); - -const badgeStyles = css({ - padding: '0 10px', - minHeight: spacing[600], -}); - -const learnMoreBtnStyles = css({ - marginLeft: spacing[200], -}); - -const closeButtonStyles = css({ - marginLeft: 'auto', -}); - -export enum SkillsBannerContextEnum { - Documents = 'documents', - Aggregation = 'aggregation', - Indexes = 'indexes', - Schema = 'schema', -} - -// Helper hook for components that want to show the Atlas Skills banner -export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => { - const atlasSkillsAssignment = useAssignment( - ExperimentTestName.atlasSkills, - false - ); - - const isInSkillsVariant = - atlasSkillsAssignment?.assignment?.assignmentData?.variant === - ExperimentTestGroup.atlasSkillsVariant; - - // Track experiment viewed when user is in experiment and banner would be shown - useTrackInSample(ExperimentTestName.atlasSkills, !!atlasSkillsAssignment, { - screen: context, - }); - - return { - shouldShowAtlasSkillsBanner: isInSkillsVariant, - }; -}; - -// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 -export const AtlasSkillsBanner: React.FunctionComponent<{ - ctaText: string; - onCloseSkillsBanner: () => void; - skillsUrl: string; - showBanner: boolean; -}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, showBanner }) => { - return showBanner ? ( -
- - {/* Custom SVG for award icon */} - - - - - -
{ctaText}
- - - onCloseSkillsBanner()} - > - - -
- ) : null; -}; diff --git a/packages/compass-telemetry/src/atlas-skills.ts b/packages/compass-telemetry/src/atlas-skills.ts new file mode 100644 index 00000000000..a81658d2424 --- /dev/null +++ b/packages/compass-telemetry/src/atlas-skills.ts @@ -0,0 +1,34 @@ +import { + ExperimentTestGroup, + ExperimentTestName, + useAssignment, +} from './provider'; +import { useTrackInSample } from './experimentation-provider'; + +export enum SkillsBannerContextEnum { + Documents = 'documents', + Aggregation = 'aggregation', + Indexes = 'indexes', + Schema = 'schema', +} + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => { + const atlasSkillsAssignment = useAssignment( + ExperimentTestName.atlasSkills, + false + ); + + const isInSkillsVariant = + atlasSkillsAssignment?.assignment?.assignmentData?.variant === + ExperimentTestGroup.atlasSkillsVariant; + + // Track experiment viewed when user is in experiment and banner would be shown + useTrackInSample(ExperimentTestName.atlasSkills, !!atlasSkillsAssignment, { + screen: context, + }); + + return { + shouldShowAtlasSkillsBanner: isInSkillsVariant, + }; +}; diff --git a/packages/compass-telemetry/src/index.ts b/packages/compass-telemetry/src/index.ts index bc10c2913f7..299103f2563 100644 --- a/packages/compass-telemetry/src/index.ts +++ b/packages/compass-telemetry/src/index.ts @@ -10,8 +10,4 @@ export { CompassExperimentationProvider } from './experimentation-provider'; export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments'; // @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 -export { - AtlasSkillsBanner, - SkillsBannerContextEnum, - useAtlasSkillsBanner, -} from './atlas-skills-banner'; +export { SkillsBannerContextEnum, useAtlasSkillsBanner } from './atlas-skills'; From ce7736653f8b68aec8f98929293ba054ede954f0 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Mon, 13 Oct 2025 15:28:01 -0400 Subject: [PATCH 05/13] CLOUDP-349749: telemetry exports --- packages/compass-telemetry/atlas-skills-banner.d.ts | 1 - packages/compass-telemetry/atlas-skills.d.ts | 1 + packages/compass-telemetry/package.json | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 packages/compass-telemetry/atlas-skills-banner.d.ts create mode 100644 packages/compass-telemetry/atlas-skills.d.ts diff --git a/packages/compass-telemetry/atlas-skills-banner.d.ts b/packages/compass-telemetry/atlas-skills-banner.d.ts deleted file mode 100644 index bf1f225d732..00000000000 --- a/packages/compass-telemetry/atlas-skills-banner.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dist/atlas-skills-banner'; diff --git a/packages/compass-telemetry/atlas-skills.d.ts b/packages/compass-telemetry/atlas-skills.d.ts new file mode 100644 index 00000000000..4b93c6f7d1e --- /dev/null +++ b/packages/compass-telemetry/atlas-skills.d.ts @@ -0,0 +1 @@ +export * from './dist/atlas-skills'; diff --git a/packages/compass-telemetry/package.json b/packages/compass-telemetry/package.json index 73882a9c9bb..5f5e2dd5d43 100644 --- a/packages/compass-telemetry/package.json +++ b/packages/compass-telemetry/package.json @@ -26,12 +26,12 @@ "exports": { ".": "./dist/index.js", "./provider": "./dist/provider.js", - "./atlas-skills-banner": "./dist/atlas-skills-banner.js" + "./atlas-skills": "./dist/atlas-skills.js" }, "compass:exports": { ".": "./src/index.ts", "./provider": "./src/provider.tsx", - "./atlas-skills-banner": "./src/atlas-skills-banner.tsx" + "./atlas-skills": "./src/atlas-skills.ts" }, "types": "./dist/index.d.ts", "scripts": { From 72eb33f4aac677edaa17ec95b198614bd5b033b4 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 09:33:40 -0400 Subject: [PATCH 06/13] CLOUDP-349749: awardIcon --- package-lock.json | 40 +++++++++++++++++++ .../src/components/atlas-skills-banner.tsx | 14 +------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e93d85b809..352d5a63aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48624,6 +48624,16 @@ "polished": "^4.2.2" } }, + "packages/compass-components/node_modules/@leafygreen-ui/icon": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.6.0.tgz", + "integrity": "sha512-j1nYs9zP7HIWzXFYYsxunokykN3TI9EbHH/Dlh9ISEiwTX7qXsqgPYY02uFIGULiHomNAOe8AIzncJ41lqPR3A==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.3", + "lodash": "^4.17.21" + } + }, "packages/compass-components/node_modules/@leafygreen-ui/icon-button": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon-button/-/icon-button-16.0.2.tgz", @@ -48677,6 +48687,16 @@ "polished": "^4.2.2" } }, + "packages/compass-components/node_modules/@leafygreen-ui/icon/node_modules/@leafygreen-ui/emotion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.3.tgz", + "integrity": "sha512-elu8af9Qh8Oy/IwqXcNKitHAQBAO4+zmHuLi0fRzY46kTwXvLYqPpJFRcySqITWLPyBcMsF56ta8yQKXghEYOA==", + "license": "Apache-2.0", + "dependencies": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + }, "packages/compass-components/node_modules/@leafygreen-ui/inline-definition": { "version": "8.0.12", "resolved": "https://registry.npmjs.org/@leafygreen-ui/inline-definition/-/inline-definition-8.0.12.tgz", @@ -62609,6 +62629,26 @@ } } }, + "@leafygreen-ui/icon": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.6.0.tgz", + "integrity": "sha512-j1nYs9zP7HIWzXFYYsxunokykN3TI9EbHH/Dlh9ISEiwTX7qXsqgPYY02uFIGULiHomNAOe8AIzncJ41lqPR3A==", + "requires": { + "@leafygreen-ui/emotion": "^5.0.3", + "lodash": "^4.17.21" + }, + "dependencies": { + "@leafygreen-ui/emotion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.3.tgz", + "integrity": "sha512-elu8af9Qh8Oy/IwqXcNKitHAQBAO4+zmHuLi0fRzY46kTwXvLYqPpJFRcySqITWLPyBcMsF56ta8yQKXghEYOA==", + "requires": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + } + } + }, "@leafygreen-ui/icon-button": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon-button/-/icon-button-16.0.2.tgz", diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx index fca99e205ad..911437520a0 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.tsx @@ -47,19 +47,7 @@ export const AtlasSkillsBanner: React.FunctionComponent<{ return showBanner ? (
- {/* Custom SVG for award icon */} - - - +
{ctaText}
From 42addf8891711fc8f7c49b04607917bfcc148088 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 09:56:53 -0400 Subject: [PATCH 07/13] CLOUDP-349749: export changes --- package-lock.json | 42 +------------------ .../components/atlas-skills-banner.spec.tsx | 8 ++-- packages/compass-telemetry/atlas-skills.d.ts | 1 - packages/compass-telemetry/package.json | 6 +-- .../compass-telemetry/src/atlas-skills.ts | 8 +--- packages/compass-telemetry/src/provider.tsx | 3 ++ 6 files changed, 12 insertions(+), 56 deletions(-) delete mode 100644 packages/compass-telemetry/atlas-skills.d.ts diff --git a/package-lock.json b/package-lock.json index 352d5a63aaf..ab6b0ef5ab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48624,16 +48624,6 @@ "polished": "^4.2.2" } }, - "packages/compass-components/node_modules/@leafygreen-ui/icon": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.6.0.tgz", - "integrity": "sha512-j1nYs9zP7HIWzXFYYsxunokykN3TI9EbHH/Dlh9ISEiwTX7qXsqgPYY02uFIGULiHomNAOe8AIzncJ41lqPR3A==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/emotion": "^5.0.3", - "lodash": "^4.17.21" - } - }, "packages/compass-components/node_modules/@leafygreen-ui/icon-button": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon-button/-/icon-button-16.0.2.tgz", @@ -48687,16 +48677,6 @@ "polished": "^4.2.2" } }, - "packages/compass-components/node_modules/@leafygreen-ui/icon/node_modules/@leafygreen-ui/emotion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.3.tgz", - "integrity": "sha512-elu8af9Qh8Oy/IwqXcNKitHAQBAO4+zmHuLi0fRzY46kTwXvLYqPpJFRcySqITWLPyBcMsF56ta8yQKXghEYOA==", - "license": "Apache-2.0", - "dependencies": { - "@emotion/css": "^11.1.3", - "@emotion/server": "^11.4.0" - } - }, "packages/compass-components/node_modules/@leafygreen-ui/inline-definition": { "version": "8.0.12", "resolved": "https://registry.npmjs.org/@leafygreen-ui/inline-definition/-/inline-definition-8.0.12.tgz", @@ -62629,26 +62609,6 @@ } } }, - "@leafygreen-ui/icon": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-14.6.0.tgz", - "integrity": "sha512-j1nYs9zP7HIWzXFYYsxunokykN3TI9EbHH/Dlh9ISEiwTX7qXsqgPYY02uFIGULiHomNAOe8AIzncJ41lqPR3A==", - "requires": { - "@leafygreen-ui/emotion": "^5.0.3", - "lodash": "^4.17.21" - }, - "dependencies": { - "@leafygreen-ui/emotion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.3.tgz", - "integrity": "sha512-elu8af9Qh8Oy/IwqXcNKitHAQBAO4+zmHuLi0fRzY46kTwXvLYqPpJFRcySqITWLPyBcMsF56ta8yQKXghEYOA==", - "requires": { - "@emotion/css": "^11.1.3", - "@emotion/server": "^11.4.0" - } - } - } - }, "@leafygreen-ui/icon-button": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon-button/-/icon-button-16.0.2.tgz", @@ -66288,7 +66248,7 @@ "requires": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^13.1.2", + "@leafygreen-ui/icon": "^14.6.0", "@leafygreen-ui/inline-definition": "^9.0.5", "@leafygreen-ui/leafygreen-provider": "^4.0.2", "@leafygreen-ui/palette": "^4.1.3", diff --git a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx index f929c42d44f..459eeba9d79 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx @@ -27,9 +27,9 @@ describe('AtlasSkillsBanner Component', function () { it('should render the badge with award icon', function () { render(); - // Check for the SVG award icon - const svgIcon = screen.getByRole('img', { hidden: true }); - expect(svgIcon).to.be.visible; + // Check for the badge containing the award icon + const badge = screen.getByRole('status'); + expect(badge).to.be.visible; }); it('should render the "Go to Skills" button with correct href', function () { @@ -87,7 +87,7 @@ describe('AtlasSkillsBanner Component', function () { 'New to MongoDB? Document modeling skills will accelerate your progress.' ) ).to.not.exist; - expect(screen.queryByRole('img', { hidden: true })).to.not.exist; + expect(screen.queryByRole('status')).to.not.exist; expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; }); }); diff --git a/packages/compass-telemetry/atlas-skills.d.ts b/packages/compass-telemetry/atlas-skills.d.ts deleted file mode 100644 index 4b93c6f7d1e..00000000000 --- a/packages/compass-telemetry/atlas-skills.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dist/atlas-skills'; diff --git a/packages/compass-telemetry/package.json b/packages/compass-telemetry/package.json index 5f5e2dd5d43..8736c36f71d 100644 --- a/packages/compass-telemetry/package.json +++ b/packages/compass-telemetry/package.json @@ -25,13 +25,11 @@ "compass:main": "src/index.ts", "exports": { ".": "./dist/index.js", - "./provider": "./dist/provider.js", - "./atlas-skills": "./dist/atlas-skills.js" + "./provider": "./dist/provider.js" }, "compass:exports": { ".": "./src/index.ts", - "./provider": "./src/provider.tsx", - "./atlas-skills": "./src/atlas-skills.ts" + "./provider": "./src/provider.tsx" }, "types": "./dist/index.d.ts", "scripts": { diff --git a/packages/compass-telemetry/src/atlas-skills.ts b/packages/compass-telemetry/src/atlas-skills.ts index a81658d2424..c2ceda8ae0a 100644 --- a/packages/compass-telemetry/src/atlas-skills.ts +++ b/packages/compass-telemetry/src/atlas-skills.ts @@ -1,9 +1,5 @@ -import { - ExperimentTestGroup, - ExperimentTestName, - useAssignment, -} from './provider'; -import { useTrackInSample } from './experimentation-provider'; +import { ExperimentTestGroup, ExperimentTestName } from './growth-experiments'; +import { useAssignment, useTrackInSample } from './experimentation-provider'; export enum SkillsBannerContextEnum { Documents = 'documents', diff --git a/packages/compass-telemetry/src/provider.tsx b/packages/compass-telemetry/src/provider.tsx index 355f351c929..d481433e40b 100644 --- a/packages/compass-telemetry/src/provider.tsx +++ b/packages/compass-telemetry/src/provider.tsx @@ -163,3 +163,6 @@ export const useFireExperimentViewed = ({ export type { TrackFunction }; export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments'; export { useAssignment, useTrackInSample } from './experimentation-provider'; + +// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311 +export { SkillsBannerContextEnum, useAtlasSkillsBanner } from './atlas-skills'; From 5e44e4616c94f41f307aa2a38d0422a1c8cf9e2f Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 09:57:45 -0400 Subject: [PATCH 08/13] CLOUDP-349749: package-lock --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index ab6b0ef5ab2..8e93d85b809 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66248,7 +66248,7 @@ "requires": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^14.6.0", + "@leafygreen-ui/icon": "^13.1.2", "@leafygreen-ui/inline-definition": "^9.0.5", "@leafygreen-ui/leafygreen-provider": "^4.0.2", "@leafygreen-ui/palette": "^4.1.3", From b1120a3a9fd277ee3839ca441ab1f5fc99d81eb8 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 11:35:10 -0400 Subject: [PATCH 09/13] CLOUDP-349749: test fix --- .../src/components/atlas-skills-banner.spec.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx index 459eeba9d79..2c62a87f84a 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx @@ -27,9 +27,9 @@ describe('AtlasSkillsBanner Component', function () { it('should render the badge with award icon', function () { render(); - // Check for the badge containing the award icon - const badge = screen.getByRole('status'); - expect(badge).to.be.visible; + // Check for the award icon + const awardIcon = screen.getByLabelText('Award Icon'); + expect(awardIcon).to.be.visible; }); it('should render the "Go to Skills" button with correct href', function () { @@ -87,7 +87,7 @@ describe('AtlasSkillsBanner Component', function () { 'New to MongoDB? Document modeling skills will accelerate your progress.' ) ).to.not.exist; - expect(screen.queryByRole('status')).to.not.exist; + expect(screen.queryByLabelText('Award Icon')).to.not.exist; expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist; }); }); From 7b7793109aefbaed76c5fa0dab8dfc21ea9a70b2 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 11:47:13 -0400 Subject: [PATCH 10/13] CLOUDP-349749: update comment --- packages/compass-telemetry/src/atlas-skills.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-telemetry/src/atlas-skills.ts b/packages/compass-telemetry/src/atlas-skills.ts index c2ceda8ae0a..6f6dbc1ebb8 100644 --- a/packages/compass-telemetry/src/atlas-skills.ts +++ b/packages/compass-telemetry/src/atlas-skills.ts @@ -19,7 +19,7 @@ export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => { atlasSkillsAssignment?.assignment?.assignmentData?.variant === ExperimentTestGroup.atlasSkillsVariant; - // Track experiment viewed when user is in experiment and banner would be shown + // Track users who are assigned to the skills experiment (variant or control) useTrackInSample(ExperimentTestName.atlasSkills, !!atlasSkillsAssignment, { screen: context, }); From 40b707c74f4e0f1d7749ac50ee5731bf1f4ddefe Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Tue, 14 Oct 2025 12:01:06 -0400 Subject: [PATCH 11/13] CLOUDP-349749: compass-collection tests --- .../collection-header-actions/collection-header-actions.spec.tsx | 1 + .../src/components/collection-header/collection-header.spec.tsx | 1 + 2 files changed, 2 insertions(+) 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 1c55645717c..7040b30c8ec 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 @@ -42,6 +42,7 @@ describe('CollectionHeaderActions [Component]', function () { return renderWithActiveConnection( 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 42ff1aa3e87..764c3210937 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 @@ -394,6 +394,7 @@ describe('CollectionHeader [Component]', function () { return renderWithActiveConnection( From 0deca629c5f4250ea7027cb2a6fde3a541456f11 Mon Sep 17 00:00:00 2001 From: Carolyn McCawley Date: Wed, 15 Oct 2025 12:24:23 -0400 Subject: [PATCH 12/13] CLOUDP-349749: removing span --- .../compass-components/src/components/atlas-skills-banner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx index 911437520a0..73a8b2e21e7 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.tsx @@ -61,7 +61,7 @@ export const AtlasSkillsBanner: React.FunctionComponent<{ title="Go to Skills" className={learnMoreBtnStyles} > - Go to Skills + Go to Skills Date: Thu, 16 Oct 2025 10:22:35 -0400 Subject: [PATCH 13/13] CLOUDP-349749: label update --- .../src/components/atlas-skills-banner.spec.tsx | 6 ++---- .../src/components/atlas-skills-banner.tsx | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx index 2c62a87f84a..f1b8a22e85b 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.spec.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.spec.tsx @@ -67,12 +67,10 @@ describe('AtlasSkillsBanner Component', function () { ); const closeButton = screen.getByRole('button', { - name: 'Close Atlas Skills CTA', + name: 'Dismiss Skills Banner', }); expect(closeButton).to.be.visible; - expect(closeButton.getAttribute('title')).to.equal( - 'Close Atlas Skills CTA' - ); + expect(closeButton.getAttribute('title')).to.equal('Dismiss Skills Banner'); userEvent.click(closeButton); expect(onCloseSkillsBanner).to.have.been.calledOnce; diff --git a/packages/compass-components/src/components/atlas-skills-banner.tsx b/packages/compass-components/src/components/atlas-skills-banner.tsx index 73a8b2e21e7..c8ce1f7abde 100644 --- a/packages/compass-components/src/components/atlas-skills-banner.tsx +++ b/packages/compass-components/src/components/atlas-skills-banner.tsx @@ -65,8 +65,8 @@ export const AtlasSkillsBanner: React.FunctionComponent<{