Skip to content

Commit 4e52cde

Browse files
authored
feat(compass-telemetry): Atlas Skills Banner CLOUDP-349749 (#7441)
1 parent 2cf72d0 commit 4e52cde

File tree

10 files changed

+243
-2
lines changed

10 files changed

+243
-2
lines changed

packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ describe('CollectionHeaderActions [Component]', function () {
4242
return renderWithActiveConnection(
4343
<CompassExperimentationProvider
4444
useAssignment={mockUseAssignment}
45+
useTrackInSample={sinon.stub()}
4546
assignExperiment={sinon.stub()}
4647
getAssignment={sinon.stub().resolves(null)}
4748
>

packages/compass-collection/src/components/collection-header/collection-header.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ describe('CollectionHeader [Component]', function () {
394394
return renderWithActiveConnection(
395395
<CompassExperimentationProvider
396396
useAssignment={mockUseAssignment}
397+
useTrackInSample={Sinon.stub()}
397398
assignExperiment={Sinon.stub()}
398399
getAssignment={Sinon.stub().resolves(null)}
399400
>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from 'react';
2+
import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
3+
import { expect } from 'chai';
4+
import sinon from 'sinon';
5+
6+
import { AtlasSkillsBanner } from './atlas-skills-banner';
7+
8+
describe('AtlasSkillsBanner Component', function () {
9+
const defaultProps = {
10+
ctaText:
11+
'New to MongoDB? Document modeling skills will accelerate your progress.',
12+
skillsUrl: 'https://www.mongodb.com/skills',
13+
onCloseSkillsBanner: sinon.spy(),
14+
showBanner: true,
15+
};
16+
17+
it('should render the banner with correct text', function () {
18+
render(<AtlasSkillsBanner {...defaultProps} />);
19+
20+
expect(
21+
screen.getByText(
22+
'New to MongoDB? Document modeling skills will accelerate your progress.'
23+
)
24+
).to.be.visible;
25+
});
26+
27+
it('should render the badge with award icon', function () {
28+
render(<AtlasSkillsBanner {...defaultProps} />);
29+
30+
// Check for the award icon
31+
const awardIcon = screen.getByLabelText('Award Icon');
32+
expect(awardIcon).to.be.visible;
33+
});
34+
35+
it('should render the "Go to Skills" button with correct href', function () {
36+
render(<AtlasSkillsBanner {...defaultProps} />);
37+
38+
const goToSkillsButton = screen.getByRole('link', {
39+
name: /go to skills/i,
40+
});
41+
expect(goToSkillsButton).to.be.visible;
42+
expect(goToSkillsButton.getAttribute('href')).to.equal(
43+
'https://www.mongodb.com/skills'
44+
);
45+
expect(goToSkillsButton.getAttribute('target')).to.equal('_blank');
46+
});
47+
48+
it('should call onCtaClick when "Go to Skills" button is clicked', function () {
49+
const onCtaClick = sinon.spy();
50+
render(<AtlasSkillsBanner {...defaultProps} onCtaClick={onCtaClick} />);
51+
52+
const goToSkillsButton = screen.getByRole('link', {
53+
name: /go to skills/i,
54+
});
55+
56+
userEvent.click(goToSkillsButton);
57+
expect(onCtaClick).to.have.been.calledOnce;
58+
});
59+
60+
it('should render the close button and call onCloseSkillsBanner when clicked', function () {
61+
const onCloseSkillsBanner = sinon.spy();
62+
render(
63+
<AtlasSkillsBanner
64+
{...defaultProps}
65+
onCloseSkillsBanner={onCloseSkillsBanner}
66+
/>
67+
);
68+
69+
const closeButton = screen.getByRole('button', {
70+
name: 'Dismiss Skills Banner',
71+
});
72+
expect(closeButton).to.be.visible;
73+
expect(closeButton.getAttribute('title')).to.equal('Dismiss Skills Banner');
74+
75+
userEvent.click(closeButton);
76+
expect(onCloseSkillsBanner).to.have.been.calledOnce;
77+
});
78+
79+
it('should not render when showBanner is false', function () {
80+
render(<AtlasSkillsBanner {...defaultProps} showBanner={false} />);
81+
82+
// Banner should not be visible
83+
expect(
84+
screen.queryByText(
85+
'New to MongoDB? Document modeling skills will accelerate your progress.'
86+
)
87+
).to.not.exist;
88+
expect(screen.queryByLabelText('Award Icon')).to.not.exist;
89+
expect(screen.queryByRole('link', { name: /go to skills/i })).to.not.exist;
90+
});
91+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React from 'react';
2+
import { css } from '@leafygreen-ui/emotion';
3+
import IconButton from '@leafygreen-ui/icon-button';
4+
import Badge, { Variant as BadgeVariant } from '@leafygreen-ui/badge';
5+
import Icon from '@leafygreen-ui/icon';
6+
import { spacing } from '@leafygreen-ui/tokens';
7+
import Button from '@leafygreen-ui/button';
8+
import { palette } from '@leafygreen-ui/palette';
9+
10+
const skillsCTAContent = css({
11+
border: `1px ${palette.gray.light1} solid`,
12+
borderRadius: spacing[300],
13+
padding: spacing[300],
14+
paddingLeft: spacing[400],
15+
display: 'flex',
16+
width: '100%',
17+
alignItems: 'center',
18+
});
19+
20+
const skillsCTAText = css({
21+
display: 'flex',
22+
alignSelf: 'center',
23+
paddingLeft: spacing[200],
24+
});
25+
26+
const badgeStyles = css({
27+
padding: '0 10px',
28+
minHeight: spacing[600],
29+
});
30+
31+
const learnMoreBtnStyles = css({
32+
marginLeft: spacing[200],
33+
});
34+
35+
const closeButtonStyles = css({
36+
marginLeft: 'auto',
37+
});
38+
39+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
40+
export const AtlasSkillsBanner: React.FunctionComponent<{
41+
ctaText: string;
42+
onCloseSkillsBanner: () => void;
43+
onCtaClick?: () => void;
44+
skillsUrl: string;
45+
showBanner: boolean;
46+
}> = ({ ctaText, skillsUrl, onCloseSkillsBanner, onCtaClick, showBanner }) => {
47+
return showBanner ? (
48+
<div className={skillsCTAContent}>
49+
<Badge variant={BadgeVariant.Green} className={badgeStyles}>
50+
<Icon glyph="Award" />
51+
</Badge>
52+
<div className={skillsCTAText}>{ctaText}</div>
53+
54+
<Button
55+
value="Go to Skills"
56+
size="xsmall"
57+
href={skillsUrl}
58+
target="_blank"
59+
onClick={onCtaClick}
60+
leftGlyph={<Icon glyph="OpenNewTab"></Icon>}
61+
title="Go to Skills"
62+
className={learnMoreBtnStyles}
63+
>
64+
Go to Skills
65+
</Button>
66+
<IconButton
67+
className={closeButtonStyles}
68+
title="Dismiss Skills Banner"
69+
aria-label="Dismiss Skills Banner"
70+
onClick={onCloseSkillsBanner}
71+
>
72+
<Icon glyph="X" />
73+
</IconButton>
74+
</div>
75+
) : null;
76+
};

packages/compass-components/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,5 @@ export type {
246246
NodeField,
247247
NodeGlyph,
248248
} from '@mongodb-js/diagramming';
249+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
250+
export { AtlasSkillsBanner } from './components/atlas-skills-banner';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { ExperimentTestGroup, ExperimentTestName } from './growth-experiments';
2+
import { useAssignment, useTrackInSample } from './experimentation-provider';
3+
4+
export enum SkillsBannerContextEnum {
5+
Documents = 'documents',
6+
Aggregation = 'aggregation',
7+
Indexes = 'indexes',
8+
Schema = 'schema',
9+
}
10+
11+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
12+
export const useAtlasSkillsBanner = (context: SkillsBannerContextEnum) => {
13+
const atlasSkillsAssignment = useAssignment(
14+
ExperimentTestName.atlasSkills,
15+
false
16+
);
17+
18+
const isInSkillsVariant =
19+
atlasSkillsAssignment?.assignment?.assignmentData?.variant ===
20+
ExperimentTestGroup.atlasSkillsVariant;
21+
22+
// Track users who are assigned to the skills experiment (variant or control)
23+
useTrackInSample(ExperimentTestName.atlasSkills, !!atlasSkillsAssignment, {
24+
screen: context,
25+
});
26+
27+
return {
28+
shouldShowAtlasSkillsBanner: isInSkillsVariant,
29+
};
30+
};

packages/compass-telemetry/src/experimentation-provider.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,18 @@ type GetAssignmentFn = (
2020
options?: types.GetAssignmentOptions<types.TypeData>
2121
) => Promise<types.SDKAssignment<ExperimentTestName, string> | null>;
2222

23+
type UseTrackInSampleHook = (
24+
experimentName: ExperimentTestName,
25+
shouldFireEvent?: boolean,
26+
customProperties?: types.TypeData['experimentViewedProps'],
27+
team?: types.TypeData['loggerTeam']
28+
) => typesReact.BasicHookResponse;
29+
2330
interface CompassExperimentationProviderContextValue {
2431
useAssignment: UseAssignmentHook;
2532
assignExperiment: AssignExperimentFn;
2633
getAssignment: GetAssignmentFn;
34+
useTrackInSample: UseTrackInSampleHook;
2735
}
2836

2937
const initialContext: CompassExperimentationProviderContextValue = {
@@ -43,6 +51,15 @@ const initialContext: CompassExperimentationProviderContextValue = {
4351
getAssignment() {
4452
return Promise.resolve(null);
4553
},
54+
useTrackInSample() {
55+
return {
56+
asyncStatus: null,
57+
error: null,
58+
isLoading: false,
59+
isError: false,
60+
isSuccess: true,
61+
};
62+
},
4663
};
4764

4865
export const ExperimentationContext =
@@ -54,17 +71,26 @@ export const CompassExperimentationProvider: React.FC<{
5471
useAssignment: UseAssignmentHook;
5572
assignExperiment: AssignExperimentFn;
5673
getAssignment: GetAssignmentFn;
57-
}> = ({ children, useAssignment, assignExperiment, getAssignment }) => {
74+
useTrackInSample: UseTrackInSampleHook;
75+
}> = ({
76+
children,
77+
useAssignment,
78+
assignExperiment,
79+
getAssignment,
80+
useTrackInSample,
81+
}) => {
5882
// Use useRef to keep the functions up-to-date; Use mutation pattern to maintain the
5983
// same object reference to prevent unnecessary re-renders of consuming components
6084
const { current: contextValue } = useRef({
6185
useAssignment,
6286
assignExperiment,
6387
getAssignment,
88+
useTrackInSample,
6489
});
6590
contextValue.useAssignment = useAssignment;
6691
contextValue.assignExperiment = assignExperiment;
6792
contextValue.getAssignment = getAssignment;
93+
contextValue.useTrackInSample = useTrackInSample;
6894

6995
return (
7096
<ExperimentationContext.Provider value={contextValue}>
@@ -77,3 +103,8 @@ export const CompassExperimentationProvider: React.FC<{
77103
export const useAssignment = (...args: Parameters<UseAssignmentHook>) => {
78104
return useContext(ExperimentationContext).useAssignment(...args);
79105
};
106+
107+
// Hook for components to access experiment assignment
108+
export const useTrackInSample = (...args: Parameters<UseTrackInSampleHook>) => {
109+
return useContext(ExperimentationContext).useTrackInSample(...args);
110+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
export enum ExperimentTestName {
22
earlyJourneyIndexesGuidance = 'EARLY_JOURNEY_INDEXES_GUIDANCE_20250328',
33
mockDataGenerator = 'MOCK_DATA_GENERATOR_20251001',
4+
atlasSkills = 'ATLAS_SKILLS_EXPERIMENT_20251007',
45
}
56

67
export enum ExperimentTestGroup {
78
mockDataGeneratorVariant = 'mockDataGeneratorVariant',
89
mockDataGeneratorControl = 'mockDataGeneratorControl',
10+
atlasSkillsVariant = 'atlasSkillsVariant',
11+
atlasSkillsControl = 'atlasSkillsControl',
912
}

packages/compass-telemetry/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ export type {
88

99
export { CompassExperimentationProvider } from './experimentation-provider';
1010
export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments';
11+
12+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
13+
export { SkillsBannerContextEnum, useAtlasSkillsBanner } from './atlas-skills';

packages/compass-telemetry/src/provider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,7 @@ export const useFireExperimentViewed = ({
162162

163163
export type { TrackFunction };
164164
export { ExperimentTestName, ExperimentTestGroup } from './growth-experiments';
165-
export { useAssignment } from './experimentation-provider';
165+
export { useAssignment, useTrackInSample } from './experimentation-provider';
166+
167+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
168+
export { SkillsBannerContextEnum, useAtlasSkillsBanner } from './atlas-skills';

0 commit comments

Comments
 (0)