Skip to content

Commit 9919a7f

Browse files
authored
feat(compass-crud): Atlas Skills Banner in Documents Tab CLOUDP-349749 & CLOUDP-349750 (#7465)
1 parent b552c5e commit 9919a7f

File tree

3 files changed

+241
-2
lines changed

3 files changed

+241
-2
lines changed

packages/compass-crud/src/components/crud-toolbar.spec.tsx

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React from 'react';
22
import { expect } from 'chai';
33
import sinon from 'sinon';
4-
import { screen, within, userEvent } from '@mongodb-js/testing-library-compass';
4+
import {
5+
screen,
6+
within,
7+
userEvent,
8+
renderWithConnections,
9+
} from '@mongodb-js/testing-library-compass';
510
import type { PreferencesAccess } from 'compass-preferences-model';
611
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';
712
import { CrudToolbar } from './crud-toolbar';
813
import { renderWithQueryBar } from '../../test/render-with-query-bar';
14+
import { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry';
15+
import { ExperimentTestGroup } from '@mongodb-js/compass-telemetry/provider';
916

1017
const noop = () => {
1118
/* noop */
@@ -821,4 +828,170 @@ describe('CrudToolbar Component', function () {
821828
expect(() => screen.getByTestId('insight-badge-button')).to.throw();
822829
});
823830
});
831+
832+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
833+
describe('Atlas Skills Banner', function () {
834+
function renderCrudToolbarWithExperimentation(experimentationOptions?: {
835+
isInExperiment?: boolean;
836+
isInVariant?: boolean;
837+
}) {
838+
const mockUseAssignment = sinon.stub();
839+
const mockUseTrackInSample = sinon.stub();
840+
const mockAssignExperiment = sinon.stub();
841+
const mockGetAssignment = sinon.stub();
842+
843+
const commonAsyncStatus = {
844+
asyncStatus: null,
845+
error: null,
846+
isLoading: false,
847+
isError: false,
848+
isSuccess: true,
849+
};
850+
851+
// Configure the mock based on experiment options
852+
if (experimentationOptions?.isInExperiment) {
853+
if (experimentationOptions?.isInVariant) {
854+
mockUseAssignment.returns({
855+
assignment: {
856+
assignmentData: {
857+
variant: ExperimentTestGroup.atlasSkillsVariant,
858+
},
859+
},
860+
...commonAsyncStatus,
861+
});
862+
} else {
863+
mockUseAssignment.returns({
864+
assignment: {
865+
assignmentData: {
866+
variant: ExperimentTestGroup.atlasSkillsControl,
867+
},
868+
},
869+
...commonAsyncStatus,
870+
});
871+
}
872+
} else {
873+
mockUseAssignment.returns({
874+
assignment: null,
875+
...commonAsyncStatus,
876+
});
877+
}
878+
879+
mockUseTrackInSample.returns(commonAsyncStatus);
880+
mockAssignExperiment.returns(Promise.resolve(null));
881+
mockGetAssignment.returns(Promise.resolve(null));
882+
883+
const renderResult = renderWithConnections(
884+
<CompassExperimentationProvider
885+
useAssignment={mockUseAssignment}
886+
useTrackInSample={mockUseTrackInSample}
887+
assignExperiment={mockAssignExperiment}
888+
getAssignment={mockGetAssignment}
889+
>
890+
<CrudToolbar
891+
activeDocumentView="List"
892+
count={55}
893+
end={20}
894+
getPage={noop}
895+
insertDataHandler={noop}
896+
loadingCount={false}
897+
isFetching={false}
898+
docsPerPage={25}
899+
isWritable
900+
instanceDescription=""
901+
onApplyClicked={noop}
902+
onResetClicked={noop}
903+
onUpdateButtonClicked={noop}
904+
onDeleteButtonClicked={noop}
905+
onExpandAllClicked={noop}
906+
onCollapseAllClicked={noop}
907+
openExportFileDialog={noop}
908+
outdated={false}
909+
page={0}
910+
readonly={false}
911+
refreshDocuments={noop}
912+
resultId="123"
913+
start={0}
914+
viewSwitchHandler={noop}
915+
updateMaxDocumentsPerPage={noop}
916+
queryLimit={0}
917+
querySkip={0}
918+
/>
919+
</CompassExperimentationProvider>,
920+
{ preferences: preferences.getPreferences() }
921+
);
922+
return renderResult;
923+
}
924+
925+
it('should show skills banner when user is in experiment and in variant', function () {
926+
renderCrudToolbarWithExperimentation({
927+
isInExperiment: true,
928+
isInVariant: true,
929+
});
930+
931+
expect(
932+
screen.getByText(
933+
'Practice creating, reading, updating, and deleting documents efficiently.'
934+
)
935+
).to.be.visible;
936+
const goToSkillsButton = screen.getByRole('link', {
937+
name: /go to skills/i,
938+
});
939+
expect(goToSkillsButton).to.be.visible;
940+
expect(screen.getByLabelText('Award Icon')).to.be.visible;
941+
942+
expect(goToSkillsButton.getAttribute('href')).to.equal(
943+
'https://learn.mongodb.com/courses/crud-operations-in-mongodb?team=growth'
944+
);
945+
});
946+
947+
it('should not show skills banner when user is in experiment but not in variant', function () {
948+
renderCrudToolbarWithExperimentation({
949+
isInExperiment: true,
950+
isInVariant: false,
951+
});
952+
953+
expect(
954+
screen.queryByText(
955+
'Practice creating, reading, updating, and deleting documents efficiently.'
956+
)
957+
).to.not.exist;
958+
expect(screen.queryByRole('link', { name: /go to skills/i })).to.not
959+
.exist;
960+
});
961+
962+
it('should not show skills banner by default when user is not in experiment', function () {
963+
renderCrudToolbarWithExperimentation({
964+
isInExperiment: false,
965+
isInVariant: false,
966+
});
967+
968+
expect(
969+
screen.queryByText(
970+
'Practice creating, reading, updating, and deleting documents efficiently.'
971+
)
972+
).to.not.exist;
973+
expect(screen.queryByRole('link', { name: /go to skills/i })).to.not
974+
.exist;
975+
});
976+
977+
it('should dismiss banner when close button is clicked', function () {
978+
renderCrudToolbarWithExperimentation({
979+
isInExperiment: true,
980+
isInVariant: true,
981+
});
982+
983+
const closeButton = screen.getByRole('button', {
984+
name: 'Dismiss Skills Banner',
985+
});
986+
987+
expect(closeButton).to.be.visible;
988+
userEvent.click(closeButton);
989+
990+
expect(
991+
screen.queryByText(
992+
'Practice creating, reading, updating, and deleting documents efficiently.'
993+
)
994+
).to.not.exist;
995+
});
996+
});
824997
});

packages/compass-crud/src/components/crud-toolbar.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React, { useCallback, useMemo } from 'react';
2-
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
2+
import {
3+
useTelemetry,
4+
SkillsBannerContextEnum,
5+
useAtlasSkillsBanner,
6+
} from '@mongodb-js/compass-telemetry/provider';
7+
38
import {
49
Body,
510
DropdownMenuButton,
@@ -14,6 +19,8 @@ import {
1419
Option,
1520
SignalPopover,
1621
useContextMenuGroups,
22+
usePersistedState,
23+
AtlasSkillsBanner,
1724
} from '@mongodb-js/compass-components';
1825
import type { MenuAction, Signal } from '@mongodb-js/compass-components';
1926
import { ViewSwitcher } from './view-switcher';
@@ -181,6 +188,15 @@ const CrudToolbar: React.FunctionComponent<CrudToolbarProps> = ({
181188
const track = useTelemetry();
182189
const connectionInfoRef = useConnectionInfoRef();
183190
const isImportExportEnabled = usePreference('enableImportExport');
191+
const [dismissed, setDismissed] = usePersistedState(
192+
'mongodb_compass_dismissedAtlasDocSkillBanner',
193+
false
194+
);
195+
196+
// @experiment Skills in Atlas | Jira Epic: CLOUDP-346311
197+
const { shouldShowAtlasSkillsBanner } = useAtlasSkillsBanner(
198+
SkillsBannerContextEnum.Documents
199+
);
184200

185201
const displayedDocumentCount = useMemo(
186202
() => (loadingCount ? '' : `${count ?? 'N/A'}`),
@@ -309,6 +325,24 @@ const CrudToolbar: React.FunctionComponent<CrudToolbarProps> = ({
309325
showExplainButton={enableExplainPlan}
310326
/>
311327
</div>
328+
329+
<AtlasSkillsBanner
330+
ctaText="Practice creating, reading, updating, and deleting documents efficiently."
331+
skillsUrl="https://learn.mongodb.com/courses/crud-operations-in-mongodb?team=growth"
332+
onCloseSkillsBanner={() => {
333+
setDismissed(true);
334+
track('Atlas Skills CTA Dismissed', {
335+
context: 'Documents Tab',
336+
});
337+
}}
338+
showBanner={shouldShowAtlasSkillsBanner && !dismissed}
339+
onCtaClick={() => {
340+
track('Atlas Skills CTA Clicked', {
341+
context: 'Documents Tab',
342+
});
343+
}}
344+
/>
345+
312346
<div className={crudBarStyles}>
313347
<div className={toolbarLeftActionStyles}>
314348
{!readonly && (

packages/compass-telemetry/src/telemetry-events.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,6 +2654,36 @@ type AtlasLinkClickedEvent = CommonEvent<{
26542654
};
26552655
}>;
26562656

2657+
/**
2658+
* This event is fired when a user clicks the Atlas Skills CTA banner.
2659+
*
2660+
* @category Other
2661+
*/
2662+
type AtlasSkillsCtaClickedEvent = CommonEvent<{
2663+
name: 'Atlas Skills CTA Clicked';
2664+
payload: {
2665+
/**
2666+
* The context/screen from which the Atlas Skills CTA was dismissed.
2667+
*/
2668+
context: 'Documents Tab' | 'Aggregation Tab' | 'Indexes Tab' | 'Schema Tab';
2669+
};
2670+
}>;
2671+
2672+
/**
2673+
* This event is fired when a user dismisses the Atlas Skills CTA banner.
2674+
*
2675+
* @category Other
2676+
*/
2677+
type AtlasSkillsCtaDismissedEvent = CommonEvent<{
2678+
name: 'Atlas Skills CTA Dismissed';
2679+
payload: {
2680+
/**
2681+
* The context/screen from which the Atlas Skills CTA was dismissed.
2682+
*/
2683+
context: 'Documents Tab' | 'Aggregation Tab' | 'Indexes Tab' | 'Schema Tab';
2684+
};
2685+
}>;
2686+
26572687
/**
26582688
* This event is fired when the application launch is initiated.
26592689
*
@@ -3158,6 +3188,8 @@ export type TelemetryEvent =
31583188
| AiResponseGeneratedEvent
31593189
| ApplicationLaunchedEvent
31603190
| AtlasLinkClickedEvent
3191+
| AtlasSkillsCtaClickedEvent
3192+
| AtlasSkillsCtaDismissedEvent
31613193
| AtlasSignInErrorEvent
31623194
| AtlasSignInSuccessEvent
31633195
| AtlasSignOutEvent

0 commit comments

Comments
 (0)