Skip to content

Commit b3f2aea

Browse files
committed
feat(compass-generative-ai): update Gen AI opt-in modal COMPASS-9593
1 parent 37dc196 commit b3f2aea

File tree

7 files changed

+429
-691
lines changed

7 files changed

+429
-691
lines changed

packages/compass-generative-ai/src/atlas-ai-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ export class AtlasAiService {
281281
// the user is signed into Atlas and opted in.
282282

283283
if (this.apiURLPreset === 'cloud') {
284-
return getStore().dispatch(optIntoGenAIWithModalPrompt({ signal }));
284+
return getStore().dispatch(
285+
optIntoGenAIWithModalPrompt({ signal, isCloudOptIn: true })
286+
);
285287
}
286288
return getStore().dispatch(signIntoAtlasWithModalPrompt({ signal }));
287289
}

packages/compass-generative-ai/src/components/ai-image-banner.tsx

Lines changed: 244 additions & 559 deletions
Large diffs are not rendered by default.

packages/compass-generative-ai/src/components/ai-optin-modal.spec.tsx

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import { PreferencesProvider } from 'compass-preferences-model/provider';
99
let mockPreferences: PreferencesAccess;
1010

1111
describe('AIOptInModal Component', function () {
12+
const baseProps = {
13+
projectId: 'ab123',
14+
isCloudOptIn: true,
15+
isOptInModalVisible: true,
16+
isOptInInProgress: false,
17+
onOptInModalClose: () => {},
18+
onOptInClick: () => {},
19+
};
20+
1221
beforeEach(async function () {
1322
mockPreferences = await createSandboxFromDefaultPreferences();
1423
});
@@ -20,37 +29,23 @@ describe('AIOptInModal Component', function () {
2029
it('should show the modal title', function () {
2130
render(
2231
<PreferencesProvider value={mockPreferences}>
23-
<AIOptInModal
24-
projectId="ab123"
25-
isOptInModalVisible={true}
26-
isOptInInProgress={false}
27-
onOptInModalClose={() => {}}
28-
onOptInClick={() => {}}
29-
></AIOptInModal>
32+
<AIOptInModal {...baseProps}></AIOptInModal>
3033
</PreferencesProvider>
3134
);
3235
expect(
3336
screen.getByRole('heading', {
34-
name: 'Use natural language to generate queries and pipelines',
37+
name: 'Opt-in Gen AI-Powered features',
3538
})
3639
).to.exist;
3740
});
38-
it('should show the cancel button', function () {
41+
it('should show the not now link', function () {
3942
render(
4043
<PreferencesProvider value={mockPreferences}>
41-
<AIOptInModal
42-
projectId="ab123"
43-
isOptInModalVisible={true}
44-
isOptInInProgress={false}
45-
onOptInModalClose={() => {}}
46-
onOptInClick={() => {}}
47-
>
48-
{' '}
49-
</AIOptInModal>
44+
<AIOptInModal {...baseProps}></AIOptInModal>
5045
</PreferencesProvider>
5146
);
52-
const button = screen.getByText('Cancel').closest('button');
53-
expect(button).to.not.match('disabled');
47+
const link = screen.getByText('Not now');
48+
expect(link).to.exist;
5449
});
5550

5651
it('should show the opt in button enabled when project AI setting is enabled', async function () {
@@ -59,19 +54,34 @@ describe('AIOptInModal Component', function () {
5954
});
6055
render(
6156
<PreferencesProvider value={mockPreferences}>
62-
<AIOptInModal
63-
projectId="ab123"
64-
isOptInModalVisible={true}
65-
isOptInInProgress={false}
66-
onOptInModalClose={() => {}}
67-
onOptInClick={() => {}}
68-
>
69-
{' '}
70-
</AIOptInModal>
57+
<AIOptInModal {...baseProps}></AIOptInModal>
58+
</PreferencesProvider>
59+
);
60+
const button = screen.getByText('Opt-in AI features');
61+
expect(button).to.exist;
62+
});
63+
64+
it('should show an info banner in a cloud opt-in', async function () {
65+
await mockPreferences.savePreferences({
66+
enableGenAIFeaturesAtlasProject: true,
67+
});
68+
render(
69+
<PreferencesProvider value={mockPreferences}>
70+
<AIOptInModal {...baseProps} isCloudOptIn={true}></AIOptInModal>
71+
</PreferencesProvider>
72+
);
73+
const banner = screen.getByTestId('ai-optin-cloud-banner');
74+
expect(banner).to.exist;
75+
});
76+
77+
it('should not show a banner in non-cloud environment', function () {
78+
render(
79+
<PreferencesProvider value={mockPreferences}>
80+
<AIOptInModal {...baseProps} isCloudOptIn={false}></AIOptInModal>
7181
</PreferencesProvider>
7282
);
73-
const button = screen.getByText('Use Natural Language').closest('button');
74-
expect(button?.getAttribute('aria-disabled')).to.equal('false');
83+
const banner = screen.queryByTestId('ai-optin-cloud-banner');
84+
expect(banner).to.not.exist;
7585
});
7686

7787
it('should disable the opt in button if project AI setting is disabled ', async function () {
@@ -80,18 +90,10 @@ describe('AIOptInModal Component', function () {
8090
});
8191
render(
8292
<PreferencesProvider value={mockPreferences}>
83-
<AIOptInModal
84-
projectId="ab123"
85-
isOptInModalVisible={true}
86-
isOptInInProgress={false}
87-
onOptInModalClose={() => {}}
88-
onOptInClick={() => {}}
89-
>
90-
{' '}
91-
</AIOptInModal>
93+
<AIOptInModal {...baseProps}></AIOptInModal>
9294
</PreferencesProvider>
9395
);
94-
const button = screen.getByText('Use Natural Language').closest('button');
95-
expect(button?.getAttribute('aria-disabled')).to.equal('true');
96+
const button = screen.getByText('AI features disabled');
97+
expect(button).to.exist;
9698
});
9799
});

packages/compass-generative-ai/src/components/ai-optin-modal.tsx

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import {
44
Banner,
55
Body,
66
Link,
7-
ConfirmationModal,
8-
SpinLoader,
7+
MarketingModal,
98
css,
109
spacing,
11-
H3,
1210
palette,
11+
useDarkMode,
12+
SpinLoader,
1313
} from '@mongodb-js/compass-components';
1414
import { AiImageBanner } from './ai-image-banner';
1515
import { closeOptInModal, optIn } from '../store/atlas-optin-reducer';
@@ -22,50 +22,44 @@ const GEN_AI_FAQ_LINK = 'https://www.mongodb.com/docs/generative-ai-faq/';
2222
type OptInModalProps = {
2323
isOptInModalVisible: boolean;
2424
isOptInInProgress: boolean;
25+
isCloudOptIn: boolean;
2526
onOptInModalClose: () => void;
2627
onOptInClick: () => void;
2728
projectId?: string;
2829
};
2930

30-
const titleStyles = css({
31-
marginBottom: spacing[400],
32-
marginTop: spacing[400],
33-
marginLeft: spacing[500],
34-
marginRight: spacing[500],
35-
textAlign: 'center',
36-
});
37-
38-
const bodyStyles = css({
39-
marginBottom: spacing[400],
40-
marginTop: spacing[400],
41-
marginLeft: spacing[300],
42-
marginRight: spacing[300],
43-
display: 'flex',
44-
flexDirection: 'column',
45-
alignItems: 'center',
46-
textAlign: 'center',
47-
});
48-
4931
const disclaimerStyles = css({
5032
color: palette.gray.dark1,
51-
marginTop: spacing[400],
52-
marginLeft: spacing[800],
53-
marginRight: spacing[800],
33+
paddingLeft: spacing[800],
34+
paddingRight: spacing[800],
5435
});
5536

56-
const bannerStyles = css({
57-
padding: spacing[400],
58-
marginTop: spacing[400],
59-
textAlign: 'left',
37+
// TODO: The LG MarketingModal does not provide a way to disable a button
38+
// so this is a temporary workaround to make the button look disabled.
39+
const disableOptInButtonStyles = css({
40+
button: {
41+
opacity: 0.5,
42+
pointerEvents: 'none',
43+
cursor: 'not-allowed',
44+
},
6045
});
61-
const getButtonText = (isOptInInProgress: boolean) => {
46+
47+
const getButtonText = ({
48+
isOptInInProgress,
49+
isCloudOptIn,
50+
darkMode,
51+
}: {
52+
isOptInInProgress: boolean;
53+
isCloudOptIn: boolean;
54+
darkMode: boolean | undefined;
55+
}) => {
6256
return (
6357
<>
64-
&nbsp;Use Natural Language
65-
{isOptInInProgress && (
58+
Opt-in AI features
59+
{isOptInInProgress && isCloudOptIn && (
6660
<>
6761
&nbsp;
68-
<SpinLoader darkMode={true}></SpinLoader>
62+
<SpinLoader darkMode={darkMode}></SpinLoader>
6963
</>
7064
)}
7165
</>
@@ -75,12 +69,14 @@ const getButtonText = (isOptInInProgress: boolean) => {
7569
export const AIOptInModal: React.FunctionComponent<OptInModalProps> = ({
7670
isOptInModalVisible,
7771
isOptInInProgress,
72+
isCloudOptIn,
7873
onOptInModalClose,
7974
onOptInClick,
8075
projectId,
8176
}) => {
8277
const isProjectAIEnabled = usePreference('enableGenAIFeaturesAtlasProject');
8378
const track = useTelemetry();
79+
const darkMode = useDarkMode();
8480
const PROJECT_SETTINGS_LINK = projectId
8581
? window.location.origin + '/v2/' + projectId + '#/settings/groupSettings'
8682
: null;
@@ -92,7 +88,7 @@ export const AIOptInModal: React.FunctionComponent<OptInModalProps> = ({
9288
}, [isOptInModalVisible, track]);
9389

9490
const onConfirmClick = () => {
95-
if (isOptInInProgress) {
91+
if ((isOptInInProgress && isCloudOptIn) || !isProjectAIEnabled) {
9692
return;
9793
}
9894
onOptInClick();
@@ -104,43 +100,23 @@ export const AIOptInModal: React.FunctionComponent<OptInModalProps> = ({
104100
}, [track, onOptInModalClose]);
105101

106102
return (
107-
<ConfirmationModal
108-
open={isOptInModalVisible}
109-
title=""
110-
confirmButtonProps={{
111-
children: getButtonText(isOptInInProgress),
112-
disabled: !isProjectAIEnabled,
113-
onClick: onConfirmClick,
114-
}}
115-
cancelButtonProps={{
116-
onClick: handleModalClose,
117-
}}
118-
>
119-
<Body className={bodyStyles}>
120-
<AiImageBanner></AiImageBanner>
121-
<H3 className={titleStyles}>
122-
Use natural language to generate queries and pipelines
123-
</H3>
124-
Atlas users can now quickly create queries and aggregations with
125-
MongoDB&apos;s&nbsp; intelligent AI-powered feature, available today.
126-
<Banner
127-
variant={isProjectAIEnabled ? 'info' : 'warning'}
128-
className={bannerStyles}
129-
>
130-
{isProjectAIEnabled
131-
? 'AI features are enabled for project users with data access.'
132-
: 'AI features are disabled for project users.'}{' '}
133-
Project Owners can {isProjectAIEnabled ? 'disable' : 'enable'} Data
134-
Explorer AI features in the{' '}
135-
{PROJECT_SETTINGS_LINK !== null ? (
136-
<Link href={PROJECT_SETTINGS_LINK} target="_blank">
137-
Project Settings
138-
</Link>
139-
) : (
140-
'Project Settings'
141-
)}
142-
.
143-
</Banner>
103+
<MarketingModal
104+
open={true}
105+
onClose={handleModalClose}
106+
className={isCloudOptIn ? disableOptInButtonStyles : undefined}
107+
onButtonClick={onConfirmClick}
108+
title="Opt-in Gen AI-Powered features"
109+
// @ts-expect-error - buttonText expects a string but supports ReactNode as well.
110+
buttonText={getButtonText({
111+
isOptInInProgress,
112+
isCloudOptIn,
113+
darkMode,
114+
})}
115+
linkText="Not now"
116+
onLinkClick={handleModalClose}
117+
graphic={<AiImageBanner />}
118+
darkMode={darkMode}
119+
disclaimer={
144120
<div className={disclaimerStyles}>
145121
This is a feature powered by generative AI, and may give inaccurate
146122
responses. Please see our{' '}
@@ -149,8 +125,49 @@ export const AIOptInModal: React.FunctionComponent<OptInModalProps> = ({
149125
</Link>{' '}
150126
for more information.
151127
</div>
128+
}
129+
>
130+
<Body
131+
style={{
132+
textAlign: 'left',
133+
marginTop: spacing[300],
134+
}}
135+
>
136+
Opt in now MongoDB’s intelligent AI-powered features:
137+
<ul
138+
style={{
139+
textAlign: 'left',
140+
marginTop: spacing[100],
141+
listStyle: 'disc',
142+
}}
143+
>
144+
<li>AI Assistant allows you to ask questions across connections</li>
145+
<li>Natural Language Bar to create queries and aggregations</li>
146+
{/* <li>Mock Data Generator to create AI powered sample data</li> */}
147+
</ul>
148+
{isCloudOptIn && (
149+
<Banner
150+
data-testid="ai-optin-cloud-banner"
151+
variant={isProjectAIEnabled ? 'info' : 'warning'}
152+
style={{ marginTop: spacing[300] }}
153+
>
154+
{isProjectAIEnabled
155+
? 'AI features are enabled for project users with data access.'
156+
: 'AI features are disabled for project users.'}{' '}
157+
Project Owners can {isProjectAIEnabled ? 'disable' : 'enable'} Data
158+
Explorer AI features in the{' '}
159+
{PROJECT_SETTINGS_LINK !== null ? (
160+
<Link href={PROJECT_SETTINGS_LINK} target="_blank">
161+
Project Settings
162+
</Link>
163+
) : (
164+
'Project Settings'
165+
)}
166+
.
167+
</Banner>
168+
)}
152169
</Body>
153-
</ConfirmationModal>
170+
</MarketingModal>
154171
);
155172
};
156173

packages/compass-generative-ai/src/components/plugin.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import { ConfirmationModalArea } from '@mongodb-js/compass-components';
55

66
export interface AtlasAiPluginProps {
77
projectId?: string;
8+
isCloudOptIn: boolean;
89
}
910

1011
export const AtlasAiPlugin: React.FunctionComponent<AtlasAiPluginProps> = ({
1112
projectId,
13+
isCloudOptIn,
1214
}) => {
1315
return (
1416
<ConfirmationModalArea>
1517
<AISignInModal></AISignInModal>
16-
<AIOptInModal projectId={projectId}></AIOptInModal>
18+
<AIOptInModal
19+
isCloudOptIn={isCloudOptIn}
20+
projectId={projectId}
21+
></AIOptInModal>
1722
</ConfirmationModalArea>
1823
);
1924
};

0 commit comments

Comments
 (0)