diff --git a/docs/security-test-summary.md b/docs/security-test-summary.md index 53db402e363..8dc6e5eb848 100644 --- a/docs/security-test-summary.md +++ b/docs/security-test-summary.md @@ -7,11 +7,6 @@ including the specific instances listed below. # Security Tests -## Atlas Login Integration Tests - -The Atlas Login feature is thoroughly tested, including proper authentication token -handling and credential revocation upon signout. - ## Connection Import / Export Testing diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index 570eb8e3b25..787e315d622 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -121,8 +121,6 @@ Generated on Sun, Jul 27, 2025 - [AI Opt In Modal Shown](#event--AiOptInModalShownEvent) - [AI Opt In Modal Dismissed](#event--AiOptInModalDismissedEvent) -- [AI Sign In Modal Shown](#event--AiSignInModalShownEvent) -- [AI Sign In Modal Dismissed](#event--AiSignInModalDismissedEvent) - [AI Generate Query Clicked](#event--AiGenerateQueryClickedEvent) - [AI Prompt Submitted](#event--AiPromptSubmittedEvent) - [AI Query Feedback](#event--AiQueryFeedbackEvent) @@ -1392,18 +1390,6 @@ This event is fired when the AI Opt-In Modal is shown to the user. This event is fired when the AI Opt-In Modal is dismissed by the user. - - -### AI Sign In Modal Shown - -This event is fired when the AI Sign-In Modal is shown to the user. - - - -### AI Sign In Modal Dismissed - -This event is fired when the AI Sign-In Modal is dismissed by the user. - ### AI Generate Query Clicked diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts index c932aadfe67..0a2a56fa96f 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts @@ -449,7 +449,7 @@ export const resetIsAggregationGeneratedFromQuery = export const showInput = (): PipelineBuilderThunkAction> => { return async (dispatch, _getState, { atlasAiService }) => { try { - if (process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN !== 'true') { + if (process.env.COMPASS_E2E_SKIP_AI_OPT_IN !== 'true') { await atlasAiService.ensureAiFeatureAccess(); } dispatch({ diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index aad7505167b..aa523245d91 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1382,14 +1382,6 @@ export const ModifySourceBanner = '[data-testid="modify-source-banner"]'; export const InsightIconButton = '[data-testid="insight-badge-button"]'; export const InsightPopoverCard = '[data-testid="insight-signal-card"]'; -// Atlas login -export const LogInWithAtlasButton = 'button=Log in with Atlas'; -export const LogInWithAtlasModalButton = 'button*=Log in to Atlas'; -export const DisconnectAtlasAccountButton = 'button=Log Out'; -export const AtlasLoginStatus = '[data-testid="atlas-login-status"]'; -export const AtlasLoginErrorToast = '#atlas-sign-in-error'; -export const AgreeAndContinueButton = 'button=Agree and continue'; - // Proxy settings export const ProxyUrl = '[data-testid="proxy-settings"] [data-testid="proxy-url"]'; diff --git a/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts b/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts index efbddc5477d..374c50d9d9e 100644 --- a/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts +++ b/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts @@ -49,7 +49,7 @@ describe('Collection ai query', function () { true ); await browser.setFeature('enableGenAIFeaturesAtlasOrg', true); - await browser.setFeature('optInDataExplorerGenAIFeatures', true); + await browser.setFeature('optInGenAIFeatures', true); }); describe('on the documents tab', function () { @@ -170,7 +170,7 @@ describe('Collection ai query', function () { true ); await browser.setFeature('enableGenAIFeaturesAtlasOrg', false); - await browser.setFeature('optInDataExplorerGenAIFeatures', true); + await browser.setFeature('optInGenAIFeatures', true); }); it('should not show the gen ai intro button', async function () { diff --git a/packages/compass-e2e-tests/tests/atlas-login.test.ts b/packages/compass-e2e-tests/tests/atlas-login.test.ts deleted file mode 100644 index 0983a993503..00000000000 --- a/packages/compass-e2e-tests/tests/atlas-login.test.ts +++ /dev/null @@ -1,336 +0,0 @@ -import type { CompassBrowser } from '../helpers/compass-browser'; -import { - init, - cleanup, - screenshotIfFailed, - Selectors, - skipForWeb, - TEST_COMPASS_WEB, - DEFAULT_CONNECTION_NAME_1, -} from '../helpers/compass'; -import type { Compass } from '../helpers/compass'; -import type { OIDCMockProviderConfig } from '@mongodb-js/oidc-mock-provider'; -import { OIDCMockProvider } from '@mongodb-js/oidc-mock-provider'; -import path from 'path'; -import { expect } from 'chai'; -import { createNumbersCollection } from '../helpers/insert-data'; -import { startMockAtlasServiceServer } from '../helpers/atlas-service'; -import type { Telemetry } from '../helpers/telemetry'; -import { startTelemetryServer } from '../helpers/telemetry'; - -const DEFAULT_TOKEN_PAYLOAD = { - expires_in: 3600, - payload: { - groups: ['testgroup'], - sub: 'testuser', - aud: 'resource-server-audience-value', - }, -}; - -function getTestBrowserShellCommand() { - return `${process.execPath} ${path.resolve( - __dirname, - '..', - 'fixtures', - 'curl.js' - )}`; -} - -describe('Atlas Login', function () { - let compass: Compass; - let browser: CompassBrowser; - let oidcMockProvider: OIDCMockProvider; - let getTokenPayload: OIDCMockProviderConfig['getTokenPayload']; - let stopMockAtlasServer: () => Promise; - let numberOfOIDCAuthRequests = 0; - - before(async function () { - skipForWeb(this, 'atlas-login not supported in compass-web'); - - // Start a mock server to pass an ai response. - const { endpoint, stop } = await startMockAtlasServiceServer(); - stopMockAtlasServer = stop; - process.env.COMPASS_ATLAS_SERVICE_UNAUTH_BASE_URL_OVERRIDE = endpoint; - - function isAuthorised(req: { headers: { authorization?: string } }) { - const [, token] = req.headers.authorization?.split(' ') ?? []; - // We can't check that the issued token is the one received by oidc-plugin - // so we are only checking that it was passed and assuming that it's good - // enough of a validation - return !!token; - } - - oidcMockProvider = await OIDCMockProvider.create({ - getTokenPayload(metadata) { - return getTokenPayload(metadata); - }, - overrideRequestHandler(_url, req, res) { - const url = new URL(_url); - - switch (url.pathname) { - case '/auth-portal-redirect': - res.statusCode = 307; - res.setHeader('Location', url.searchParams.get('fromURI') ?? ''); - res.end(); - break; - case '/authorize': - numberOfOIDCAuthRequests += 1; - break; - case '/v1/userinfo': - if (isAuthorised(req)) { - res.statusCode = 200; - res.write( - JSON.stringify({ - sub: Date.now().toString(32), - firstName: 'First', - lastName: 'Last', - primaryEmail: 'test@example.com', - login: 'test@example.com', - }) - ); - res.end(); - } else { - res.statusCode = 401; - res.end(); - } - break; - case '/v1/introspect': - res.statusCode = 200; - res.write(JSON.stringify({ active: isAuthorised(req) })); - res.end(); - break; - } - }, - }); - - process.env.COMPASS_CLIENT_ID_OVERRIDE = 'testServer'; - process.env.COMPASS_OIDC_ISSUER_OVERRIDE = oidcMockProvider.issuer; - process.env.COMPASS_ATLAS_AUTH_PORTAL_URL_OVERRIDE = `${oidcMockProvider.issuer}/auth-portal-redirect`; - }); - - beforeEach(async function () { - numberOfOIDCAuthRequests = 0; - - getTokenPayload = () => { - return DEFAULT_TOKEN_PAYLOAD; - }; - - compass = await init(this.test?.fullTitle()); - browser = compass.browser; - await browser.setFeature( - 'browserCommandForOIDCAuth', - getTestBrowserShellCommand() - ); - await browser.setupDefaultConnections(); - }); - - afterEach(async function () { - await browser.setFeature('browserCommandForOIDCAuth', undefined); - await screenshotIfFailed(compass, this.currentTest); - await cleanup(compass); - }); - - after(async function () { - if (TEST_COMPASS_WEB) { - return; - } - - await oidcMockProvider?.close(); - delete process.env.COMPASS_CLIENT_ID_OVERRIDE; - delete process.env.COMPASS_OIDC_ISSUER_OVERRIDE; - delete process.env.COMPASS_ATLAS_AUTH_PORTAL_URL_OVERRIDE; - - await stopMockAtlasServer(); - delete process.env.COMPASS_ATLAS_SERVICE_UNAUTH_BASE_URL_OVERRIDE; - }); - - describe('in settings', function () { - it('should sign in user when clicking on "Log in with Atlas" button', async function () { - await browser.openSettingsModal('ai'); - - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - const loginStatus = browser.$(Selectors.AtlasLoginStatus); - await browser.waitUntil(async () => { - return ( - (await loginStatus.getText()).trim() === - 'Logged in with Atlas account test@example.com' - ); - }); - expect(numberOfOIDCAuthRequests).to.eq(1); - }); - - describe('telemetry', () => { - let telemetry: Telemetry; - - before(async function () { - telemetry = await startTelemetryServer(); - }); - - after(async function () { - await telemetry.stop(); - }); - - it('should send identify after the user has logged in', async function () { - const atlasUserIdBefore = await browser.getFeature( - 'telemetryAtlasUserId' - ); - expect(atlasUserIdBefore).to.not.exist; - - await browser.openSettingsModal('ai'); - - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - const loginStatus = browser.$(Selectors.AtlasLoginStatus); - await browser.waitUntil(async () => { - return ( - (await loginStatus.getText()).trim() === - 'Logged in with Atlas account test@example.com' - ); - }); - - const atlasUserIdAfter = await browser.getFeature( - 'telemetryAtlasUserId' - ); - expect(atlasUserIdAfter).to.be.a('string'); - - const identify = telemetry - .events() - .find((entry) => entry.type === 'identify'); - expect(identify.traits.platform).to.equal(process.platform); - expect(identify.traits.arch).to.match(/^(x64|arm64)$/); - }); - }); - - it('should sign out user when "Disconnect" clicked', async function () { - await browser.openSettingsModal('ai'); - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - const loginStatus = browser.$(Selectors.AtlasLoginStatus); - - await browser.waitUntil(async () => { - return ( - (await loginStatus.getText()).trim() === - 'Logged in with Atlas account test@example.com' - ); - }); - - await browser.clickVisible(Selectors.DisconnectAtlasAccountButton); - - await browser.waitUntil(async () => { - return (await loginStatus.getText()).includes( - 'This is a feature powered by generative AI, and may give inaccurate responses' - ); - }); - }); - - it('should sign in user when disconnected and clicking again on "Log in with Atlas" button', async function () { - await browser.openSettingsModal('ai'); - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - let loginStatus = browser.$(Selectors.AtlasLoginStatus); - - await browser.waitUntil(async () => { - return ( - (await loginStatus.getText()).trim() === - 'Logged in with Atlas account test@example.com' - ); - }); - - await browser.clickVisible(Selectors.DisconnectAtlasAccountButton); - - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - loginStatus = browser.$(Selectors.AtlasLoginStatus); - await browser.waitUntil(async () => { - return ( - (await loginStatus.getText()).trim() === - 'Logged in with Atlas account test@example.com' - ); - }); - expect(numberOfOIDCAuthRequests).to.eq(2); - }); - - it('should show toast with error if sign in failed', async function () { - getTokenPayload = () => { - return Promise.reject(new Error('Auth failed')); - }; - - await browser.openSettingsModal('ai'); - await browser.clickVisible(Selectors.LogInWithAtlasButton); - - const errorToast = browser.$(Selectors.AtlasLoginErrorToast); - await errorToast.waitForDisplayed(); - - expect(await errorToast.getText()).to.match( - /Sign in failed\n+unexpected HTTP response status code.+Auth failed/ - ); - }); - }); - - describe('in CRUD view', function () { - beforeEach(async function () { - await createNumbersCollection(); - await browser.disconnectAll(); - await browser.connectToDefaults(); - await browser.navigateToCollectionTab( - DEFAULT_CONNECTION_NAME_1, - 'test', - 'numbers', - 'Documents' - ); - }); - - it('should not show AI input if sign in flow was not finished', async function () { - getTokenPayload = () => { - return new Promise(() => {}); - }; - - const generateQueryButton = browser.$('button*=Generate query'); - await browser.clickVisible(generateQueryButton); - - await browser.clickVisible(Selectors.LogInWithAtlasModalButton); - - // Because leafygreen doesn't render a button there and we don't have any - // control over it - await browser.clickVisible('span=Not now'); - - const aiInput = browser.$(Selectors.GenAITextInput); - expect(await aiInput.isExisting()).to.eq(false); - expect(await generateQueryButton.isDisplayed()).to.eq(true); - }); - }); - - describe('in Aggregation Builder view', function () { - beforeEach(async function () { - await createNumbersCollection(); - await browser.disconnectAll(); - await browser.connectToDefaults(); - await browser.navigateToCollectionTab( - DEFAULT_CONNECTION_NAME_1, - 'test', - 'numbers', - 'Aggregations' - ); - }); - - it('should not show AI input if sign in flow was not finished', async function () { - getTokenPayload = () => { - return new Promise(() => {}); - }; - - const generateQueryButton = browser.$('button*=Generate aggregation'); - await browser.clickVisible(generateQueryButton); - - await browser.clickVisible(Selectors.LogInWithAtlasModalButton); - - // Because leafygreen doesn't render a button there and we don't have any - // control over it - await browser.clickVisible('span=Not now'); - - const aiInput = browser.$(Selectors.GenAITextInput); - expect(await aiInput.isExisting()).to.eq(false); - expect(await generateQueryButton.isDisplayed()).to.eq(true); - }); - }); -}); diff --git a/packages/compass-e2e-tests/tests/collection-ai-query.test.ts b/packages/compass-e2e-tests/tests/collection-ai-query.test.ts index d403404a326..d1454932598 100644 --- a/packages/compass-e2e-tests/tests/collection-ai-query.test.ts +++ b/packages/compass-e2e-tests/tests/collection-ai-query.test.ts @@ -29,7 +29,7 @@ describe('Collection ai query', function () { before(async function () { skipForWeb(this, 'ai queries not yet available in compass-web'); - process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN = 'true'; + process.env.COMPASS_E2E_SKIP_AI_OPT_IN = 'true'; // Start a mock server to pass an ai response. const { @@ -72,7 +72,7 @@ describe('Collection ai query', function () { await stopMockAtlasServer(); - delete process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN; + delete process.env.COMPASS_E2E_SKIP_AI_OPT_IN; await cleanup(compass); await telemetry.stop(); diff --git a/packages/compass-generative-ai/src/atlas-ai-service.spec.ts b/packages/compass-generative-ai/src/atlas-ai-service.spec.ts index 46e190b742b..be807527b82 100644 --- a/packages/compass-generative-ai/src/atlas-ai-service.spec.ts +++ b/packages/compass-generative-ai/src/atlas-ai-service.spec.ts @@ -321,6 +321,47 @@ describe('AtlasAiService', function () { }); }); }); + + describe('optIntoGenAIFeatures', function () { + beforeEach(async function () { + // Reset preferences + await preferences.savePreferences({ + optInGenAIFeatures: false, + }); + }); + + it('should save preference when cloud preset', async function () { + const fetchStub = sandbox.stub().resolves(makeResponse({})); + global.fetch = fetchStub; + + await atlasAiService.optIntoGenAIFeatures(); + + // In Data Explorer, make a POST request to cloud endpoint and save preference + if (apiURLPreset === 'cloud') { + // Verify fetch was called with correct parameters + expect(fetchStub).to.have.been.calledOnce; + + expect(fetchStub).to.have.been.calledWith( + '/cloud/settings/optInDataExplorerGenAIFeatures', + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: new URLSearchParams([['value', 'true']]), + } + ); + } else { + // In Compass, no fetch is made, only stored locally + expect(fetchStub).to.not.have.been.called; + } + + // Verify preference was saved + const currentPreferences = preferences.getPreferences(); + expect(currentPreferences.optInGenAIFeatures).to.equal(true); + }); + }); }); } }); diff --git a/packages/compass-generative-ai/src/atlas-ai-service.ts b/packages/compass-generative-ai/src/atlas-ai-service.ts index d504c1fcc9a..3f0155960ca 100644 --- a/packages/compass-generative-ai/src/atlas-ai-service.ts +++ b/packages/compass-generative-ai/src/atlas-ai-service.ts @@ -9,7 +9,6 @@ import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import type { Document } from 'mongodb'; import type { Logger } from '@mongodb-js/compass-logging'; import { EJSON } from 'bson'; -import { signIntoAtlasWithModalPrompt } from './store/atlas-signin-reducer'; import { getStore } from './store/atlas-ai-store'; import { optIntoGenAIWithModalPrompt } from './store/atlas-optin-reducer'; @@ -256,7 +255,7 @@ export class AtlasAiService { } private throwIfAINotEnabled() { - if (process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN === 'true') { + if (process.env.COMPASS_E2E_SKIP_AI_OPT_IN === 'true') { return; } if (!isAIFeatureEnabled(this.preferences.getPreferences())) { @@ -277,13 +276,7 @@ export class AtlasAiService { } async ensureAiFeatureAccess({ signal }: { signal?: AbortSignal } = {}) { - // When the ai feature is attempted to be opened we make sure - // the user is signed into Atlas and opted in. - - if (this.apiURLPreset === 'cloud') { - return getStore().dispatch(optIntoGenAIWithModalPrompt({ signal })); - } - return getStore().dispatch(signIntoAtlasWithModalPrompt({ signal })); + return getStore().dispatch(optIntoGenAIWithModalPrompt({ signal })); } private getQueryOrAggregationFromUserInput = async ( @@ -391,23 +384,25 @@ export class AtlasAiService { ); } - // Performs a post request to atlas to set the user opt in preference to true. - async optIntoGenAIFeaturesAtlas() { - await this.atlasService.authenticatedFetch( - this.atlasService.cloudEndpoint( - '/settings/optInDataExplorerGenAIFeatures' - ), - { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: 'application/json', - }, - body: new URLSearchParams([['value', 'true']]), - } - ); + async optIntoGenAIFeatures() { + if (this.apiURLPreset === 'cloud') { + // Performs a post request to Atlas to set the user opt in preference to true. + await this.atlasService.authenticatedFetch( + this.atlasService.cloudEndpoint( + 'settings/optInDataExplorerGenAIFeatures' + ), + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: new URLSearchParams([['value', 'true']]), + } + ); + } await this.preferences.savePreferences({ - optInDataExplorerGenAIFeatures: true, + optInGenAIFeatures: true, }); } diff --git a/packages/compass-generative-ai/src/components/ai-signin-modal.tsx b/packages/compass-generative-ai/src/components/ai-signin-modal.tsx deleted file mode 100644 index 05c9a779752..00000000000 --- a/packages/compass-generative-ai/src/components/ai-signin-modal.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useEffect, useCallback } from 'react'; -import { connect } from 'react-redux'; -import { - Body, - Icon, - Link, - MarketingModal, - SpinLoader, - css, - spacing, - useDarkMode, -} from '@mongodb-js/compass-components'; -import { AiImageBanner } from './ai-image-banner'; -import { closeSignInModal, signIn } from '../store/atlas-signin-reducer'; -import type { RootState } from '../store/atlas-ai-store'; -import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; - -const GEN_AI_FAQ_LINK = 'https://www.mongodb.com/docs/generative-ai-faq/'; - -type SignInModalProps = { - isSignInModalVisible?: boolean; - isSignInInProgress?: boolean; - onSignInModalClose?: () => void; - onSignInClick?: () => void; -}; - -const titleStyles = css({ - marginBottom: spacing[400], - display: 'flex', - flexDirection: 'column', - alignItems: 'center', -}); - -const disclaimerStyles = css({ - padding: `0 ${spacing[900]}px`, -}); - -const AISignInModal: React.FunctionComponent = ({ - isSignInModalVisible = false, - isSignInInProgress = false, - onSignInModalClose, - onSignInClick, -}) => { - const darkMode = useDarkMode(); - const track = useTelemetry(); - - useEffect(() => { - if (isSignInModalVisible) { - track('AI Sign In Modal Shown', {}); - } - }, [isSignInModalVisible, track]); - - const handleModalClose = useCallback(() => { - track('AI Sign In Modal Dismissed', {}); - onSignInModalClose?.(); - }, [track, onSignInModalClose]); - - return ( - - This is a feature powered by generative AI, and may give inaccurate - responses. Please see our{' '} - - FAQ - {' '} - for more information. - - } - graphic={} - title={ -
- Use natural language to generate queries and pipelines -
- } - open={isSignInModalVisible} - onClose={handleModalClose} - // @ts-expect-error leafygreen only allows strings, but we need to pass - // icons - buttonText={ - <> -  Log in to Atlas to enable - {isSignInInProgress && ( - <> -   - - - )} - - } - onButtonClick={() => { - // We can't control buttons in marketing modal, so instead we just do - // nothing when button is clicked and sign in is in progress - if (isSignInInProgress) { - return; - } - onSignInClick?.(); - }} - linkText="Not now" - onLinkClick={handleModalClose} - > - - Atlas users can now quickly create queries and aggregations with - MongoDB's  intelligent AI-powered feature, available today in - Compass. - -
- ); -}; - -export default connect( - (state: RootState) => { - return { - isSignInModalVisible: state.signIn.isModalOpen, - isSignInInProgress: state.signIn.state === 'in-progress', - }; - }, - { onSignInModalClose: closeSignInModal, onSignInClick: signIn } -)(AISignInModal); diff --git a/packages/compass-generative-ai/src/components/plugin.tsx b/packages/compass-generative-ai/src/components/plugin.tsx index d4795d42dc6..15797c17c88 100644 --- a/packages/compass-generative-ai/src/components/plugin.tsx +++ b/packages/compass-generative-ai/src/components/plugin.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import AISignInModal from './ai-signin-modal'; import AIOptInModal from './ai-optin-modal'; import { ConfirmationModalArea } from '@mongodb-js/compass-components'; @@ -12,7 +11,6 @@ export const AtlasAiPlugin: React.FunctionComponent = ({ }) => { return ( - ); diff --git a/packages/compass-generative-ai/src/store/atlas-ai-store.ts b/packages/compass-generative-ai/src/store/atlas-ai-store.ts index 56370e8f2f9..7e63560a00d 100644 --- a/packages/compass-generative-ai/src/store/atlas-ai-store.ts +++ b/packages/compass-generative-ai/src/store/atlas-ai-store.ts @@ -1,10 +1,5 @@ import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunk from 'redux-thunk'; -import signInReducer, { - atlasServiceSignedIn, - atlasServiceSignedOut, - atlasServiceSignInTokenRefreshFailed, -} from './atlas-signin-reducer'; import optInReducer from './atlas-optin-reducer'; import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { AtlasAiService } from '../atlas-ai-service'; @@ -21,7 +16,6 @@ export function getStore() { return store; } const reducer = combineReducers({ - signIn: signInReducer, optIn: optInReducer, }); @@ -33,17 +27,7 @@ export function activatePlugin( { cleanup }: ActivateHelpers ) { store = configureStore(services); - services.atlasAuthService.on('signed-in', () => { - void store.dispatch(atlasServiceSignedIn()); - }); - services.atlasAuthService.on('signed-out', () => { - void store.dispatch(atlasServiceSignedOut()); - }); - - services.atlasAuthService.on('token-refresh-failed', () => { - void store.dispatch(atlasServiceSignInTokenRefreshFailed()); - }); return { store, deactivate: cleanup }; } export type CompassGenerativeAIExtraArgs = { diff --git a/packages/compass-generative-ai/src/store/atlas-optin-reducer.spec.ts b/packages/compass-generative-ai/src/store/atlas-optin-reducer.spec.ts index 9829e033833..947842f8054 100644 --- a/packages/compass-generative-ai/src/store/atlas-optin-reducer.spec.ts +++ b/packages/compass-generative-ai/src/store/atlas-optin-reducer.spec.ts @@ -20,7 +20,7 @@ describe('atlasOptInReducer', function () { beforeEach(async function () { mockPreferences = await createSandboxFromDefaultPreferences(); await mockPreferences.savePreferences({ - optInDataExplorerGenAIFeatures: false, + optInGenAIFeatures: false, }); }); @@ -31,7 +31,7 @@ describe('atlasOptInReducer', function () { describe('optIn', function () { it('should check state and set state to success if already opted in', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, @@ -45,8 +45,7 @@ describe('atlasOptInReducer', function () { ); void store.dispatch(atlasAiServiceOptedIn()); await store.dispatch(optIn()); - expect(mockAtlasAiService.optIntoGenAIFeaturesAtlas).not.to.have.been - .called; + expect(mockAtlasAiService.optIntoGenAIFeatures).not.to.have.been.called; expect(store.getState().optIn).to.have.nested.property( 'state', 'optin-success' @@ -55,7 +54,7 @@ describe('atlasOptInReducer', function () { it('should start opt in, and set state to success', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, @@ -69,8 +68,7 @@ describe('atlasOptInReducer', function () { ); void store.dispatch(optIntoGenAIWithModalPrompt()).catch(() => {}); await store.dispatch(optIn()); - expect(mockAtlasAiService.optIntoGenAIFeaturesAtlas).to.have.been - .calledOnce; + expect(mockAtlasAiService.optIntoGenAIFeatures).to.have.been.calledOnce; expect(store.getState().optIn).to.have.nested.property( 'state', 'optin-success' @@ -81,13 +79,13 @@ describe('atlasOptInReducer', function () { beforeEach(async function () { await mockPreferences.savePreferences({ enableGenAIFeaturesAtlasProject: false, - optInDataExplorerGenAIFeatures: true, + optInGenAIFeatures: true, }); }); it('should start the opt in flow', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, @@ -101,8 +99,7 @@ describe('atlasOptInReducer', function () { ); void store.dispatch(optIntoGenAIWithModalPrompt()).catch(() => {}); await store.dispatch(optIn()); - expect(mockAtlasAiService.optIntoGenAIFeaturesAtlas).to.have.been - .calledOnce; + expect(mockAtlasAiService.optIntoGenAIFeatures).to.have.been.calledOnce; expect(store.getState().optIn).to.have.nested.property( 'state', 'optin-success' @@ -112,9 +109,7 @@ describe('atlasOptInReducer', function () { it('should fail opt in if opt in failed', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox - .stub() - .rejects(new Error('Whooops!')), + optIntoGenAIFeatures: sandbox.stub().rejects(new Error('Whooops!')), }; const store = configureStore({ atlasAuthService: {} as any, @@ -127,8 +122,7 @@ describe('atlasOptInReducer', function () { // Avoid unhandled rejections. AttemptStateMap.get(attemptId)?.promise.catch(() => {}); await optInPromise; - expect(mockAtlasAiService.optIntoGenAIFeaturesAtlas).to.have.been - .calledOnce; + expect(mockAtlasAiService.optIntoGenAIFeatures).to.have.been.calledOnce; expect(store.getState().optIn).to.have.nested.property('state', 'error'); }); }); @@ -153,7 +147,7 @@ describe('atlasOptInReducer', function () { it('should cancel opt in if opt in is in progress', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox + optIntoGenAIFeatures: sandbox .stub() .callsFake(({ signal }: { signal: AbortSignal }) => { return new Promise((resolve, reject) => { @@ -183,10 +177,10 @@ describe('atlasOptInReducer', function () { }); }); - describe('optIntoAtlasWithModalPrompt', function () { + describe('optIntoGenAIWithModalPrompt', function () { it('should resolve when user finishes opt in with prompt flow', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, @@ -203,7 +197,7 @@ describe('atlasOptInReducer', function () { it('should reject if opt in flow fails', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().rejects(new Error('Whoops!')), + optIntoGenAIFeatures: sandbox.stub().rejects(new Error('Whoops!')), }; const store = configureStore({ atlasAuthService: {} as any, @@ -226,7 +220,7 @@ describe('atlasOptInReducer', function () { it('should reject if user dismissed the modal', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, @@ -249,7 +243,7 @@ describe('atlasOptInReducer', function () { it('should reject if provided signal was aborted', async function () { const mockAtlasAiService = { - optIntoGenAIFeaturesAtlas: sandbox.stub().resolves({ sub: '1234' }), + optIntoGenAIFeatures: sandbox.stub().resolves({ sub: '1234' }), }; const store = configureStore({ atlasAuthService: {} as any, diff --git a/packages/compass-generative-ai/src/store/atlas-optin-reducer.ts b/packages/compass-generative-ai/src/store/atlas-optin-reducer.ts index 1378c26a093..f653f815eb4 100644 --- a/packages/compass-generative-ai/src/store/atlas-optin-reducer.ts +++ b/packages/compass-generative-ai/src/store/atlas-optin-reducer.ts @@ -228,7 +228,7 @@ export const optIntoGenAIWithModalPrompt = ({ const { state } = getState().optIn; if ( (state === 'optin-success' || - preferences.getPreferences().optInDataExplorerGenAIFeatures) && + preferences.getPreferences().optInGenAIFeatures) && preferences.getPreferences().enableGenAIFeaturesAtlasProject ) { return Promise.resolve(); @@ -265,7 +265,7 @@ export const optIn = (): GenAIAtlasOptInThunkAction> => { try { throwIfAborted(signal); - await atlasAiService.optIntoGenAIFeaturesAtlas(); + await atlasAiService.optIntoGenAIFeatures(); dispatch(atlasAiServiceOptedIn()); resolve(); } catch (err) { diff --git a/packages/compass-generative-ai/src/store/atlas-signin-reducer.spec.ts b/packages/compass-generative-ai/src/store/atlas-signin-reducer.spec.ts deleted file mode 100644 index e8b0a5f84a2..00000000000 --- a/packages/compass-generative-ai/src/store/atlas-signin-reducer.spec.ts +++ /dev/null @@ -1,233 +0,0 @@ -import Sinon from 'sinon'; -import { expect } from 'chai'; - -import { - signIn, - cancelSignIn, - attemptId, - AttemptStateMap, - signIntoAtlasWithModalPrompt, - closeSignInModal, - atlasServiceSignedIn, -} from './atlas-signin-reducer'; -import { configureStore } from './atlas-ai-store'; -import type { PreferencesAccess } from 'compass-preferences-model'; -import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; - -describe('atlasSignInReducer', function () { - const sandbox = Sinon.createSandbox(); - let mockPreferences: PreferencesAccess; - - beforeEach(async function () { - mockPreferences = await createSandboxFromDefaultPreferences(); - }); - - afterEach(function () { - sandbox.reset(); - }); - - describe('signIn', function () { - it('should check authenticated state and set state to success if already authenticated', async function () { - const mockAtlasService = { - signIn: sandbox.stub().resolves({ sub: '1234' }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'initial' - ); - void store.dispatch(atlasServiceSignedIn()); - await store.dispatch(signIn()); - expect(mockAtlasService.signIn).not.to.have.been.called; - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'success' - ); - }); - - it('should start sign in, and set state to success', async function () { - const mockAtlasService = { - signIn: sandbox.stub().resolves({ sub: '1234' }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'initial' - ); - void store.dispatch(signIntoAtlasWithModalPrompt()).catch(() => {}); - await store.dispatch(signIn()); - expect(mockAtlasService.signIn).to.have.been.calledOnce; - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'success' - ); - }); - - it('should fail sign in if sign in failed', async function () { - const mockAtlasService = { - signIn: sandbox.stub().rejects(new Error('Pineapples!')), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - void store.dispatch(signIntoAtlasWithModalPrompt()).catch(() => {}); - const signInPromise = store.dispatch(signIn()); - // Avoid unhandled rejections. - AttemptStateMap.get(attemptId)?.promise.catch(() => {}); - await signInPromise; - expect(mockAtlasService.signIn).to.have.been.calledOnce; - expect(store.getState().signIn).to.have.nested.property('state', 'error'); - }); - }); - - describe('cancelSignIn', function () { - it('should do nothing if no sign in is in progress', function () { - const store = configureStore({ - atlasAuthService: {} as any, - atlasAiService: {} as any, - preferences: mockPreferences, - }); - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'initial' - ); - store.dispatch(cancelSignIn()); - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'initial' - ); - }); - - it('should cancel sign in if sign in is in progress', async function () { - const mockAtlasService = { - signIn: sandbox - .stub() - .callsFake(({ signal }: { signal: AbortSignal }) => { - return new Promise((resolve, reject) => { - signal.addEventListener('abort', () => { - reject(signal.reason); - }); - }); - }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - void store.dispatch(signIntoAtlasWithModalPrompt()).catch(() => {}); - - await Promise.all([ - store.dispatch(signIn()), - store.dispatch(cancelSignIn()), - ]); - expect(store.getState().signIn).to.have.nested.property( - 'state', - 'canceled' - ); - }); - }); - - describe('signIntoAtlasWithModalPrompt', function () { - it('should resolve when user finishes sign in with prompt flow', async function () { - const mockAtlasService = { - signIn: sandbox.stub().resolves({ sub: '1234' }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - const signInPromise = store.dispatch(signIntoAtlasWithModalPrompt()); - await store.dispatch(signIn()); - await signInPromise; - - expect(store.getState().signIn).to.have.property('state', 'success'); - }); - - it('should reject if sign in flow fails', async function () { - const mockAtlasService = { - signIn: sandbox.stub().rejects(new Error('Whoops!')), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - const signInPromise = store.dispatch(signIntoAtlasWithModalPrompt()); - await store.dispatch(signIn()); - - try { - await signInPromise; - throw new Error('Expected signInPromise to throw'); - } catch (err) { - expect(err).to.have.property('message', 'Whoops!'); - } - - expect(store.getState().signIn).to.have.property('state', 'error'); - }); - - it('should reject if user dismissed the modal', async function () { - const mockAtlasService = { - signIn: sandbox.stub().resolves({ sub: '1234' }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - const signInPromise = store.dispatch(signIntoAtlasWithModalPrompt()); - store.dispatch(closeSignInModal(new Error('This operation was aborted'))); - - try { - await signInPromise; - throw new Error('Expected signInPromise to throw'); - } catch (err) { - expect(err).to.have.property('message', 'This operation was aborted'); - } - - expect(store.getState().signIn).to.have.property('state', 'canceled'); - }); - - it('should reject if provided signal was aborted', async function () { - const mockAtlasService = { - signIn: sandbox.stub().resolves({ sub: '1234' }), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - atlasAiService: mockAtlasService as any, - preferences: mockPreferences, - }); - - const c = new AbortController(); - const signInPromise = store.dispatch( - signIntoAtlasWithModalPrompt({ signal: c.signal }) - ); - c.abort(new Error('Aborted from outside')); - - try { - await signInPromise; - throw new Error('Expected signInPromise to throw'); - } catch (err) { - expect(err).to.have.property('message', 'Aborted from outside'); - } - expect(store.getState().signIn).to.have.property('state', 'canceled'); - }); - }); -}); diff --git a/packages/compass-generative-ai/src/store/atlas-signin-reducer.ts b/packages/compass-generative-ai/src/store/atlas-signin-reducer.ts deleted file mode 100644 index c9c8df95e24..00000000000 --- a/packages/compass-generative-ai/src/store/atlas-signin-reducer.ts +++ /dev/null @@ -1,351 +0,0 @@ -import type { Action, AnyAction, Reducer } from 'redux'; -import type { ThunkAction } from 'redux-thunk'; -import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; -import { throwIfAborted } from '@mongodb-js/compass-utils'; -import type { RootState } from './atlas-ai-store'; -import { isAction } from '../utils/util'; - -type AttemptState = { - id: number; - controller: AbortController; - promise: Promise; - resolve: () => void; - reject: (reason?: any) => void; -}; - -export type AtlasSignInState = { - error: string | null; - isModalOpen: boolean; - attemptId: number | null; -} & ( - | { - state: 'initial' | 'in-progress' | 'error' | 'canceled'; - } - | { state: 'success' } -); - -export type GenAIAtlasSignInThunkAction< - R, - A extends AnyAction = AnyAction -> = ThunkAction; - -export const enum AtlasSignInActions { - OpenSignInModal = 'compass-generative-ai/atlas-signin/OpenSignInModal', - CloseSignInModal = 'compass-generative-ai/atlas-signin/CloseSignInModal', - AttemptStart = 'compass-generative-ai/atlas-signin/AttemptStart', - AttemptEnd = 'compass-generative-ai/atlas-signin/AttemptEnd', - Start = 'compass-generative-ai/atlas-signin/AtlasSignInStart', - Success = 'compass-generative-ai/atlas-signin/AtlasSignInSuccess', - Error = 'compass-generative-ai/atlas-signin/AtlasSignInError', - Cancel = 'compass-generative-ai/atlas-signin/AtlasSignInCancel', - SignInTokenRefreshFailed = 'compass-generative-ai/atlas-signin/SignInTokenRefreshFailed', - SignedOut = 'compass-generative-ai/atlas-signin/SignedOut', -} - -export type AtlasSignInOpenModalAction = { - type: AtlasSignInActions.OpenSignInModal; -}; - -export type AtlasSignInCloseModalAction = { - type: AtlasSignInActions.CloseSignInModal; -}; - -export type AtlasSignInAttemptStartAction = { - type: AtlasSignInActions.AttemptStart; - attemptId: number; -}; - -export type AtlasSignInAttemptEndAction = { - type: AtlasSignInActions.AttemptEnd; - attemptId: number; -}; - -export type AtlasSignInStartAction = { - type: AtlasSignInActions.Start; -}; - -export type AtlasSignInSuccessAction = { - type: AtlasSignInActions.Success; -}; - -export type AtlasSignInErrorAction = { - type: AtlasSignInActions.Error; - error: string; -}; - -export type AtlasSignInTokenRefreshFailedAction = { - type: AtlasSignInActions.SignInTokenRefreshFailed; -}; - -export type AtlasSignInSignedOutAction = { - type: AtlasSignInActions.SignedOut; -}; - -export type AtlasSignInCancelAction = { type: AtlasSignInActions.Cancel }; - -const INITIAL_STATE = { - state: 'initial' as const, - error: null, - isModalOpen: false, - attemptId: null, -}; - -// Exported for testing purposes only. -export const AttemptStateMap = new Map(); - -export let attemptId = 0; - -export function getAttempt(id?: number | null): AttemptState { - if (!id) { - id = ++attemptId; - const controller = new AbortController(); - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - if (resolve && reject) { - AttemptStateMap.set(id, { - id, - controller, - promise, - resolve: resolve, - reject: reject, - }); - } - } - const attemptState = AttemptStateMap.get(id); - if (!attemptState) { - throw new Error( - 'Trying to get the state for a non-existing sign in attempt' - ); - } - return attemptState; -} - -const signInReducer: Reducer = ( - state = { ...INITIAL_STATE }, - action -) => { - if ( - isAction( - action, - AtlasSignInActions.AttemptStart - ) - ) { - return { - ...state, - attemptId: action.attemptId, - }; - } - - if ( - isAction(action, AtlasSignInActions.AttemptEnd) - ) { - return { - ...state, - attemptId: null, - }; - } - - if (isAction(action, AtlasSignInActions.Start)) { - return { ...state, state: 'in-progress' }; - } - - if (isAction(action, AtlasSignInActions.Success)) { - return { - ...state, - state: 'success', - error: null, - isModalOpen: false, - }; - } - - if (isAction(action, AtlasSignInActions.Error)) { - return { - ...state, - state: 'error', - error: action.error, - isModalOpen: false, - }; - } - - if (isAction(action, AtlasSignInActions.Cancel)) { - return { ...INITIAL_STATE, state: 'canceled' }; - } - - if ( - isAction( - action, - AtlasSignInActions.OpenSignInModal - ) - ) { - return { ...state, isModalOpen: true }; - } - - if ( - isAction( - action, - AtlasSignInActions.CloseSignInModal - ) - ) { - return { ...state, isModalOpen: false }; - } - - if ( - isAction( - action, - AtlasSignInActions.SignInTokenRefreshFailed - ) - ) { - // Only reset state on refresh failed when we are currently successfully - // signed in. All other cases mean that either there is a sign in already - // in progress or something else already failed: no need to update either - // way - if (state.state !== 'success') { - return state; - } - return { ...INITIAL_STATE, state: 'error' }; - } - - if ( - isAction(action, AtlasSignInActions.SignedOut) - ) { - return { ...INITIAL_STATE }; - } - - return state; -}; - -const startAttempt = ( - fn: () => void -): GenAIAtlasSignInThunkAction => { - return (dispatch, getState) => { - if (getState().signIn.attemptId) { - throw new Error( - "Can't start sign in with prompt while another sign in attempt is in progress" - ); - } - const attempt = getAttempt(); - dispatch({ type: AtlasSignInActions.AttemptStart, attemptId: attempt.id }); - attempt.promise - .finally(() => { - dispatch({ - type: AtlasSignInActions.AttemptEnd, - attemptId: attempt.id, - }); - }) - .catch(() => { - // noop for the promise created by `finally`, original promise rejection - // should be handled by the service user - }); - setTimeout(fn); - return attempt; - }; -}; - -export const signIntoAtlasWithModalPrompt = ({ - signal, -}: { signal?: AbortSignal } = {}): GenAIAtlasSignInThunkAction< - Promise -> => { - return (dispatch, getState) => { - // Nothing to do if we already signed in. - const { state } = getState().signIn; - if (state === 'success') { - return Promise.resolve(); - } - const attempt = dispatch( - startAttempt(() => { - dispatch(openSignInModal()); - }) - ); - signal?.addEventListener('abort', () => { - dispatch(closeSignInModal(signal.reason)); - }); - return attempt.promise; - }; -}; - -export const signIn = (): GenAIAtlasSignInThunkAction> => { - return async (dispatch, getState, { atlasAuthService }) => { - if (['in-progress', 'authenticated'].includes(getState().signIn.state)) { - return; - } - const { attemptId } = getState().signIn; - if (attemptId === null) { - return; - } - const { - controller: { signal }, - resolve, - reject, - } = getAttempt(getState().signIn.attemptId); - dispatch({ - type: AtlasSignInActions.Start, - }); - - try { - throwIfAborted(signal); - - await atlasAuthService.signIn({ - signal, - }); - dispatch(atlasServiceSignedIn()); - resolve(); - } catch (err) { - if (signal.aborted) { - return; - } - dispatch({ - type: AtlasSignInActions.Error, - error: (err as Error).message, - }); - reject(err); - } - }; -}; - -export const openSignInModal = () => { - return { type: AtlasSignInActions.OpenSignInModal }; -}; - -export const closeSignInModal = ( - reason?: any -): GenAIAtlasSignInThunkAction => { - return (dispatch) => { - dispatch(cancelSignIn(reason)); - dispatch({ type: AtlasSignInActions.CloseSignInModal }); - }; -}; - -export const cancelSignIn = ( - reason?: any -): GenAIAtlasSignInThunkAction => { - return (dispatch, getState) => { - // Can't cancel sign in after the flow was finished indicated by current - // attempt id being set to null - if (getState().signIn.attemptId === null) { - return; - } - const attempt = getAttempt(getState().signIn.attemptId); - attempt.controller.abort(); - attempt.reject(reason ?? attempt.controller.signal.reason); - dispatch({ type: AtlasSignInActions.Cancel }); - }; -}; - -export const atlasServiceSignInTokenRefreshFailed = () => ({ - type: AtlasSignInActions.SignInTokenRefreshFailed, -}); - -export const atlasServiceSignedOut = () => ({ - type: AtlasSignInActions.SignedOut, -}); - -export const atlasServiceSignedIn = () => ({ - type: AtlasSignInActions.Success, -}); - -export default signInReducer; diff --git a/packages/compass-preferences-model/src/compass-web-preferences-access.ts b/packages/compass-preferences-model/src/compass-web-preferences-access.ts index 259390ea180..05b9095a3db 100644 --- a/packages/compass-preferences-model/src/compass-web-preferences-access.ts +++ b/packages/compass-preferences-model/src/compass-web-preferences-access.ts @@ -7,7 +7,7 @@ import { getActiveUser } from './utils'; const editablePreferences: (keyof UserPreferences)[] = [ // Value can change from false to true during allocation / checking - 'optInDataExplorerGenAIFeatures', + 'optInGenAIFeatures', 'cloudFeatureRolloutAccess', // TODO(COMPASS-9353): Provide a standard for updating Compass preferences in web 'enableIndexesGuidanceExp', diff --git a/packages/compass-preferences-model/src/preferences-schema.tsx b/packages/compass-preferences-model/src/preferences-schema.tsx index 93b5a6540d0..fdeed50452c 100644 --- a/packages/compass-preferences-model/src/preferences-schema.tsx +++ b/packages/compass-preferences-model/src/preferences-schema.tsx @@ -85,7 +85,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & | 'web-sandbox-atlas-dev' | 'web-sandbox-atlas-qa' | 'web-sandbox-atlas'; - optInDataExplorerGenAIFeatures: boolean; + optInGenAIFeatures: boolean; // Features that are enabled by default in Compass, but are disabled in Data // Explorer enableExplainPlan: boolean; @@ -810,17 +810,16 @@ export const storedUserPreferencesProps: Required<{ .default('atlas'), type: 'string', }, - optInDataExplorerGenAIFeatures: { + optInGenAIFeatures: { ui: true, cli: false, global: false, description: { - short: 'User Opt-in for Data Explorer Gen AI Features', + short: 'User or Client Opt-in for Gen AI Features', }, - validator: z.boolean().default(true), + validator: z.boolean().default(false), type: 'boolean', }, - enableAtlasSearchIndexes: { ui: true, cli: true, diff --git a/packages/compass-query-bar/src/stores/ai-query-reducer.ts b/packages/compass-query-bar/src/stores/ai-query-reducer.ts index 7cb478e1a64..b8301619272 100644 --- a/packages/compass-query-bar/src/stores/ai-query-reducer.ts +++ b/packages/compass-query-bar/src/stores/ai-query-reducer.ts @@ -414,7 +414,7 @@ export const cancelAIQuery = (): QueryBarThunkAction< export const showInput = (): QueryBarThunkAction> => { return async (dispatch, _getState, { atlasAiService }) => { try { - if (process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN !== 'true') { + if (process.env.COMPASS_E2E_SKIP_AI_OPT_IN !== 'true') { await atlasAiService.ensureAiFeatureAccess(); } dispatch({ type: AIQueryActionTypes.ShowInput }); diff --git a/packages/compass-settings/src/components/modal.tsx b/packages/compass-settings/src/components/modal.tsx index 0e87d05149d..15266ef0c56 100644 --- a/packages/compass-settings/src/components/modal.tsx +++ b/packages/compass-settings/src/components/modal.tsx @@ -21,7 +21,6 @@ import Sidebar from './sidebar'; import type { SettingsTabId } from '../stores/settings'; import { saveSettings, closeModal, selectTab } from '../stores/settings'; import type { RootState } from '../stores'; -import { getUserInfo } from '../stores/atlas-login'; import { useHasAIFeatureCloudRolloutAccess } from 'compass-preferences-model/provider'; type Settings = { @@ -63,7 +62,6 @@ const settingsStyles = css( ); export const SettingsModal: React.FunctionComponent = ({ - isAIFeatureEnabled, isOpen, selectedTab, onMount, @@ -91,11 +89,7 @@ export const SettingsModal: React.FunctionComponent = ({ }, ]; - if ( - isOIDCEnabled || - // because oidc options overlap with atlas login used for ai feature - isAIFeatureEnabled - ) { + if (isOIDCEnabled) { settings.push({ tabId: 'oidc', name: 'OIDC', @@ -170,7 +164,6 @@ export default connect( }; }, { - onMount: getUserInfo, onClose: closeModal, onSave: saveSettings, onSelectTab: selectTab, diff --git a/packages/compass-settings/src/components/settings/atlas-login.spec.tsx b/packages/compass-settings/src/components/settings/atlas-login.spec.tsx deleted file mode 100644 index 16f7e90ea92..00000000000 --- a/packages/compass-settings/src/components/settings/atlas-login.spec.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import React from 'react'; -import { EventEmitter } from 'events'; -import { - screen, - cleanup, - render, - waitFor, - userEvent, -} from '@mongodb-js/testing-library-compass'; -import { Provider } from 'react-redux'; -import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; -import Sinon from 'sinon'; -import { expect } from 'chai'; -import configureStore from '../../../test/configure-store'; -import { ConnectedAtlasLoginSettings } from './atlas-login'; -import { cancelAtlasLoginAttempt, signIn } from '../../stores/atlas-login'; -import { closeModal } from '../../stores/settings'; -import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; - -describe('AtlasLoginSettings', function () { - const sandbox = Sinon.createSandbox(); - - beforeEach(function () { - sandbox.reset(); - }); - - afterEach(function () { - cleanup(); - }); - - function renderAtlasLoginSettings( - atlasAuthService: Partial, - atlasAiService: Partial = {} - ) { - const store = configureStore({ - atlasAuthService: { - on: sandbox.stub() as any, - signIn: sandbox.stub().resolves({}), - signOut: sandbox.stub().resolves(), - ...atlasAuthService, - } as any, - atlasAiService: atlasAiService as any, - }); - render( - - - - ); - return { store }; - } - - it('should sign in user when signed out and sign in is clicked', async function () { - renderAtlasLoginSettings({ - signIn: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - }); - - userEvent.click( - screen.getByRole('button', { name: /Log in with Atlas/ }), - undefined, - { skipPointerEventsCheck: true } - ); - - await waitFor(function () { - // Disconnect button is a good indicator that we are signed in - screen.getByText('Log Out'); - }); - - expect(screen.getByTestId('atlas-signed-in-successful')).to.exist; - }); - - it('should sign out user when signed in and sign out is clicked', async function () { - const { store } = renderAtlasLoginSettings({ - signIn: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - }); - - await store.dispatch(signIn()); - - userEvent.click( - screen.getByRole('button', { name: /Log Out/ }), - undefined, - { skipPointerEventsCheck: true } - ); - - await waitFor(function () { - // Disconnect button is a good indicator that we are signed in - screen.getByText('Log in with Atlas'); - }); - expect( - screen.queryByText( - /This is a feature powered by generative AI, and may give inaccurate responses/ - ) - ).to.exist; - }); - - it('updates state with user info on `signed-in` event', async function () { - const emitter = new EventEmitter(); - const atlasAuthService = { - on: emitter.on.bind(emitter), - getUserInfo: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - } as any; - - renderAtlasLoginSettings(atlasAuthService); - - expect( - screen.queryByText( - /This is a feature powered by generative AI, and may give inaccurate responses/ - ) - ).to.exist; - - emitter.emit('signed-in'); - - await waitFor(function () { - // Disconnect button is a good indicator that we are signed in - screen.getByText('Log Out'); - }); - - expect(screen.getByTestId('atlas-signed-in-successful')).to.exist; - }); - - it('resets sign in state on `signed-out` event', async function () { - const emitter = new EventEmitter(); - const atlasAuthService = { - on: emitter.on.bind(emitter), - signIn: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - } as any; - - const { store } = renderAtlasLoginSettings(atlasAuthService); - - await store.dispatch(signIn()); - - expect(screen.getByTestId('atlas-signed-in-successful')).to.exist; - - emitter.emit('signed-out'); - - await waitFor(function () { - // Disconnect button is a good indicator that we are signed in - screen.getByText('Log in with Atlas'); - }); - - expect( - screen.queryByText( - /This is a feature powered by generative AI, and may give inaccurate responses/ - ) - ).to.exist; - }); - - it('resets sign in state on `token-refresh-failed` event', async function () { - const emitter = new EventEmitter(); - const atlasAuthService = { - on: emitter.on.bind(emitter), - signIn: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - } as any; - - const { store } = renderAtlasLoginSettings(atlasAuthService); - - await store.dispatch(signIn()); - - expect(screen.getByTestId('atlas-signed-in-successful')).to.exist; - - emitter.emit('token-refresh-failed'); - - await waitFor(function () { - // Disconnect button is a good indicator that we are signed in - screen.getByText('Log in with Atlas'); - }); - - expect( - screen.queryByText( - /This is a feature powered by generative AI, and may give inaccurate responses/ - ) - ).to.exist; - }); - - it('should cancel sign in attempt on modal close', function () { - const { store } = renderAtlasLoginSettings({ - signIn: sandbox - .stub() - .callsFake(({ signal }: { signal: AbortSignal }) => { - return new Promise((_, reject) => { - signal.addEventListener('abort', () => { - reject(signal.reason); - }); - }); - }), - }); - - userEvent.click( - screen.getByRole('button', { name: /Log in with Atlas/ }), - undefined, - { skipPointerEventsCheck: true } - ); - - expect(store.getState()).to.have.nested.property( - 'atlasLogin.status', - 'in-progress' - ); - - store.dispatch(closeModal()); - - expect(store.getState()).to.have.nested.property( - 'atlasLogin.status', - 'unauthenticated' - ); - }); - - it('should not reset sign in state if there is no sign in attempt in progress', async function () { - const atlasAuthService = { - signIn: sandbox.stub().resolves({ login: 'user@mongodb.com' }), - }; - - const { store } = renderAtlasLoginSettings(atlasAuthService); - - await store.dispatch(signIn()); - - expect(store.getState()).to.have.nested.property( - 'atlasLogin.status', - 'authenticated' - ); - - store.dispatch(cancelAtlasLoginAttempt()); - - expect(store.getState()).to.have.nested.property( - 'atlasLogin.status', - 'authenticated' - ); - }); -}); diff --git a/packages/compass-settings/src/components/settings/atlas-login.tsx b/packages/compass-settings/src/components/settings/atlas-login.tsx deleted file mode 100644 index 27173f776a0..00000000000 --- a/packages/compass-settings/src/components/settings/atlas-login.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import React from 'react'; -import { - Button, - Icon, - KeylineCard, - Link, - SpinLoader, - css, - palette, - spacing, - useDarkMode, - cx, -} from '@mongodb-js/compass-components'; -import { connect } from 'react-redux'; -import type { RootState } from '../../stores'; -import { signIn, signOut } from '../../stores/atlas-login'; - -const GEN_AI_FAQ_LINK = 'https://www.mongodb.com/docs/generative-ai-faq/'; - -const atlasLoginKeylineCardStyles = css({ - overflow: 'hidden', -}); - -const atlasLoginHeaderStyles = css({ - display: 'grid', - gridTemplateAreas: ` - "heading controls" - "description description" - `, - gridTemplateColumns: `1fr auto`, - gap: spacing[200], - padding: `${spacing[200]}px ${spacing[400]}px`, - boxShadow: `inset 0 -1px 0 ${palette.gray.light2}`, - backgroundColor: palette.gray.light3, -}); - -const atlasLoginHeaderDarkModeStyles = css({ - backgroundColor: palette.gray.dark3, - boxShadow: `inset 0 -1px 0 ${palette.gray.dark2}`, -}); - -const atlasLoginCardStyles = css({ - display: 'flex', - justifyContent: 'space-between', - flexDirection: 'row', - gap: spacing[400], - alignItems: 'center', -}); - -const atlasLoginHeadingTitleStyles = css({ - display: 'flex', - gap: spacing[200], - gridArea: 'heading', - fontWeight: 'bold', -}); - -const atlasLoginControlsStyles = css({ - flexShrink: 0, - gridArea: 'controls', -}); - -const atlasLoginHeaderDescriptionStyles = css({ - gridArea: 'description', -}); - -const atlasLoginEmailStyles = css({ - fontWeight: 'bold', -}); - -export const AtlasLoginSettings: React.FunctionComponent<{ - isSignInInProgress: boolean; - userLogin: string | null; - onSignInClick(): void; - onSignOutClick(): void; -}> = ({ isSignInInProgress, userLogin, onSignInClick, onSignOutClick }) => { - const darkMode = useDarkMode(); - const isSignedIn = userLogin !== null; - - return ( - -
-
- {!isSignedIn ? ( - <> -
-
- - - You must log in with an Atlas account to use natural - language prompts. - -
-
- This is a feature powered by generative AI, and may give - inaccurate responses. Please see our{' '} - - FAQ - {' '} - for more information. -
-
-
- -
- - ) : ( - <> -
-
- - - You can create queries and aggregations with generative AI. - -
-
-
- Logged in with Atlas account{' '} - {userLogin} -
-
-
-
- -
- - )} -
-
-
- ); -}; - -export const ConnectedAtlasLoginSettings = connect( - (state: RootState) => { - return { - isSignInInProgress: state.atlasLogin.status === 'in-progress', - userLogin: state.atlasLogin.userInfo?.login ?? null, - }; - }, - { - onSignInClick: signIn, - onSignOutClick: signOut, - } -)(AtlasLoginSettings); diff --git a/packages/compass-settings/src/components/settings/gen-ai-settings.spec.tsx b/packages/compass-settings/src/components/settings/gen-ai-settings.spec.tsx index 80410b1662c..962f2c76d97 100644 --- a/packages/compass-settings/src/components/settings/gen-ai-settings.spec.tsx +++ b/packages/compass-settings/src/components/settings/gen-ai-settings.spec.tsx @@ -23,30 +23,11 @@ function renderGenAiSettings({ const sampleDocsSettingText = 'Enable sending sample field values with query and aggregation generation requests'; -const atlasLoginSettingText = 'You must log in with an Atlas account to use '; describe('GenAISettings', function () { let container: HTMLElement; let store: ReturnType; - describe('when the isAIFeatureEnabled prop is false', function () { - beforeEach(async function () { - store = configureStore(); - await store.dispatch(fetchSettings()); - renderGenAiSettings({ - store, - props: { - isAIFeatureEnabled: false, - }, - }); - container = screen.getByTestId('gen-ai-settings'); - }); - - it('does not show the atlas login setting', function () { - expect(container).to.not.include.text(atlasLoginSettingText); - }); - }); - describe('when the isAIFeatureEnabled setting is true', function () { beforeEach(async function () { store = configureStore(); @@ -57,10 +38,6 @@ describe('GenAISettings', function () { container = screen.getByTestId('gen-ai-settings'); }); - it('shows the atlas login setting', function () { - expect(container).to.include.text(atlasLoginSettingText); - }); - it('shows the enableGenAISampleDocumentPassing setting', function () { expect(container).to.include.text(sampleDocsSettingText); }); diff --git a/packages/compass-settings/src/components/settings/gen-ai-settings.tsx b/packages/compass-settings/src/components/settings/gen-ai-settings.tsx index c41a109e2eb..ffd9f0a8fa2 100644 --- a/packages/compass-settings/src/components/settings/gen-ai-settings.tsx +++ b/packages/compass-settings/src/components/settings/gen-ai-settings.tsx @@ -1,14 +1,8 @@ import React from 'react'; -import { css, spacing } from '@mongodb-js/compass-components'; import { connect } from 'react-redux'; import type { RootState } from '../../stores'; import SettingsList from './settings-list'; -import { ConnectedAtlasLoginSettings } from './atlas-login'; - -const atlasSettingsContainerStyles = css({ - marginTop: spacing[400], -}); export const GenAISettings: React.FunctionComponent<{ isAIFeatureEnabled: boolean; @@ -24,9 +18,6 @@ export const GenAISettings: React.FunctionComponent<{ {isAIFeatureEnabled && ( <> -
- -
)} diff --git a/packages/compass-settings/src/stores/atlas-login.ts b/packages/compass-settings/src/stores/atlas-login.ts deleted file mode 100644 index 45d520bb1a7..00000000000 --- a/packages/compass-settings/src/stores/atlas-login.ts +++ /dev/null @@ -1,241 +0,0 @@ -import type { Action, Reducer } from 'redux'; -import { abort, getAbortSignal, isAction } from './utils'; -import type { AtlasUserInfo } from '@mongodb-js/atlas-service/renderer'; -import type { SettingsThunkAction } from '.'; - -type AtlasLoginSettingsState = { attemptId: number | null } & ( - | { - status: 'initial' | 'in-progress' | 'unauthenticated'; - userInfo: null; - } - | { status: 'authenticated'; userInfo: AtlasUserInfo } -); - -const INITIAL_STATE = { - status: 'initial' as const, - attemptId: null, - userInfo: null, -}; - -const enum AtlasLoginSettingsActionTypes { - SignInStart = 'compass-settings/atlas-login/SignInStart', - SignInSuccess = 'compass-settings/atlas-login/SignInSuccess', - SignInError = 'compass-settings/atlas-login/SignInError', - SignOut = 'compass-settings/atlas-login/SignOut', - GetUserInfoStart = 'compass-settings/atlas-login/GetUserInfoStart', - GetUserInfoSuccess = 'compass-settings/atlas-login/GetUserInfoSuccess', - GetUserInfoError = 'compass-settings/atlas-login/GetUserInfoError', - CancelAttempt = 'compass-settings/atlas-login/CancelAttempt', - AtlasServiceTokenRefreshFailed = 'compass-settings/atlas-login/AtlasServiceTokenRefreshFailed', - AtlasServiceSignedOut = 'compass-settings/atlas-login/AtlasServiceSignOut', -} - -type SignInStartAction = { - type: AtlasLoginSettingsActionTypes.SignInStart; - attemptId: number; -}; - -type SignInSuccessAction = { - type: AtlasLoginSettingsActionTypes.SignInSuccess; - userInfo: AtlasUserInfo; -}; - -type SignInErrorAction = { - type: AtlasLoginSettingsActionTypes.SignInError; - error: string; -}; - -type GetUserInfoStartAction = { - type: AtlasLoginSettingsActionTypes.GetUserInfoStart; -}; - -type GetUserInfoSuccessAction = { - type: AtlasLoginSettingsActionTypes.GetUserInfoSuccess; - userInfo: AtlasUserInfo; -}; - -type GetUserInfoErrorAction = { - type: AtlasLoginSettingsActionTypes.GetUserInfoError; - error: string; -}; - -type SignOutAction = { - type: AtlasLoginSettingsActionTypes.SignOut; -}; - -type AtlasServiceCancelAttemptAction = { - type: AtlasLoginSettingsActionTypes.CancelAttempt; -}; - -type AtlasServiceTokenRefreshFailedAction = { - type: AtlasLoginSettingsActionTypes.AtlasServiceTokenRefreshFailed; -}; - -type AtlasServiceSignedOutAction = { - type: AtlasLoginSettingsActionTypes.AtlasServiceSignedOut; -}; - -const reducer: Reducer = ( - state = INITIAL_STATE, - action -) => { - if ( - isAction( - action, - AtlasLoginSettingsActionTypes.SignInStart - ) - ) { - return { - status: 'in-progress', - attemptId: action.attemptId, - userInfo: null, - }; - } - - if ( - isAction( - action, - AtlasLoginSettingsActionTypes.GetUserInfoStart - ) - ) { - return { - ...state, - status: 'in-progress', - userInfo: null, - }; - } - - if ( - isAction( - action, - AtlasLoginSettingsActionTypes.SignInSuccess - ) || - isAction( - action, - AtlasLoginSettingsActionTypes.GetUserInfoSuccess - ) - ) { - return { - ...state, - status: 'authenticated', - userInfo: action.userInfo, - attemptId: null, - }; - } - - if ( - isAction(action, AtlasLoginSettingsActionTypes.SignOut) || - isAction( - action, - AtlasLoginSettingsActionTypes.SignInError - ) || - isAction( - action, - AtlasLoginSettingsActionTypes.GetUserInfoError - ) || - isAction( - action, - AtlasLoginSettingsActionTypes.AtlasServiceSignedOut - ) || - isAction( - action, - AtlasLoginSettingsActionTypes.AtlasServiceTokenRefreshFailed - ) || - isAction( - action, - AtlasLoginSettingsActionTypes.CancelAttempt - ) - ) { - return { - ...state, - status: 'unauthenticated', - userInfo: null, - }; - } - - return state; -}; - -export const signIn = (): SettingsThunkAction> => { - return async (dispatch, getState, { atlasAuthService }) => { - if ( - ['in-progress', 'authenticated'].includes(getState().atlasLogin.status) - ) { - return; - } - const { signal, id } = getAbortSignal(); - try { - dispatch({ - type: AtlasLoginSettingsActionTypes.SignInStart, - attemptId: id, - }); - const userInfo = await atlasAuthService.signIn({ - signal, - }); - dispatch({ type: AtlasLoginSettingsActionTypes.SignInSuccess, userInfo }); - } catch (err) { - if (signal?.aborted) { - return; - } - dispatch({ - type: AtlasLoginSettingsActionTypes.SignInError, - error: (err as Error).message, - }); - } - }; -}; - -export const getUserInfo = (): SettingsThunkAction> => { - return async (dispatch, getState, { atlasAuthService }) => { - if ( - ['in-progress', 'authenticated'].includes(getState().atlasLogin.status) - ) { - return; - } - try { - dispatch({ type: AtlasLoginSettingsActionTypes.GetUserInfoStart }); - const userInfo = await atlasAuthService.getUserInfo(); - dispatch({ - type: AtlasLoginSettingsActionTypes.GetUserInfoSuccess, - userInfo, - }); - } catch (err) { - dispatch({ - type: AtlasLoginSettingsActionTypes.GetUserInfoError, - error: (err as Error).message, - }); - } - }; -}; - -export const signOut = (): SettingsThunkAction> => { - return async (dispatch, _getState, { atlasAuthService }) => { - await atlasAuthService.signOut(); - dispatch({ type: AtlasLoginSettingsActionTypes.SignOut }); - }; -}; - -export const atlasServiceSignedOut = () => { - return { - type: AtlasLoginSettingsActionTypes.AtlasServiceSignedOut, - }; -}; - -export const atlasServiceTokenRefreshFailed = () => { - return { - type: AtlasLoginSettingsActionTypes.AtlasServiceTokenRefreshFailed, - }; -}; - -export const cancelAtlasLoginAttempt = (): SettingsThunkAction => { - return (dispatch, getState) => { - const { attemptId } = getState().atlasLogin; - if (attemptId === null) { - return; - } - abort(attemptId); - dispatch({ type: AtlasLoginSettingsActionTypes.CancelAttempt }); - }; -}; - -export default reducer; diff --git a/packages/compass-settings/src/stores/index.ts b/packages/compass-settings/src/stores/index.ts index fffcb7f0d4f..aa05b1af865 100644 --- a/packages/compass-settings/src/stores/index.ts +++ b/packages/compass-settings/src/stores/index.ts @@ -9,11 +9,6 @@ import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider' import { PreferencesSandbox } from './preferences-sandbox'; import type { SettingsTabId } from './settings'; import { openModal, reducer as settingsReducer } from './settings'; -import atlasLoginReducer, { - getUserInfo, - atlasServiceSignedOut, - atlasServiceTokenRefreshFailed, -} from './atlas-login'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { PreferencesAccess } from 'compass-preferences-model'; @@ -44,10 +39,8 @@ export function configureStore( const store = createStore( combineReducers({ settings: settingsReducer, - atlasLogin: atlasLoginReducer, }) as Reducer<{ settings: ReturnType; - atlasLogin: ReturnType; }>, // combineReducers CombinedState return type is broken, have to remove the EmptyObject from the union that it returns applyMiddleware( thunk.withExtraArgument({ @@ -60,18 +53,6 @@ export function configureStore( ) ); - options.atlasAuthService.on('signed-in', () => { - void store.dispatch(getUserInfo()); - }); - - options.atlasAuthService.on('signed-out', () => { - void store.dispatch(atlasServiceSignedOut()); - }); - - options.atlasAuthService.on('token-refresh-failed', () => { - void store.dispatch(atlasServiceTokenRefreshFailed()); - }); - return store; } diff --git a/packages/compass-settings/src/stores/settings.ts b/packages/compass-settings/src/stores/settings.ts index aaa136876f3..1b113741a75 100644 --- a/packages/compass-settings/src/stores/settings.ts +++ b/packages/compass-settings/src/stores/settings.ts @@ -4,7 +4,6 @@ import type { PreferenceStateInformation, UserConfigurablePreferences, } from 'compass-preferences-model'; -import { cancelAtlasLoginAttempt } from './atlas-login'; export type SettingsTabId = | 'general' @@ -260,7 +259,6 @@ export const closeModal = (): SettingsThunkAction< CloseSettingsModalAction > => { return (dispatch) => { - dispatch(cancelAtlasLoginAttempt()); dispatch({ type: ActionTypes.CloseSettingsModal }); }; }; diff --git a/packages/compass-telemetry/src/telemetry-events.ts b/packages/compass-telemetry/src/telemetry-events.ts index 64b2e7940ab..5be189524be 100644 --- a/packages/compass-telemetry/src/telemetry-events.ts +++ b/packages/compass-telemetry/src/telemetry-events.ts @@ -1543,26 +1543,6 @@ type AiOptInModalDismissedEvent = CommonEvent<{ payload: Record; }>; -/** - * This event is fired when the AI Sign-In Modal is shown to the user. - * - * @category Gen AI - */ -type AiSignInModalShownEvent = CommonEvent<{ - name: 'AI Sign In Modal Shown'; - payload: Record; -}>; - -/** - * This event is fired when the AI Sign-In Modal is dismissed by the user. - * - * @category Gen AI - */ -type AiSignInModalDismissedEvent = CommonEvent<{ - name: 'AI Sign In Modal Dismissed'; - payload: Record; -}>; - /** * This event is fired when a user clicks the Generate Query / Aggregation entry point. * @@ -2963,8 +2943,6 @@ export type TelemetryEvent = | AggregationUseCaseSavedEvent | AiOptInModalShownEvent | AiOptInModalDismissedEvent - | AiSignInModalShownEvent - | AiSignInModalDismissedEvent | AiGenerateQueryClickedEvent | AiPromptSubmittedEvent | AiQueryFeedbackEvent diff --git a/packages/compass-web/sandbox/index.tsx b/packages/compass-web/sandbox/index.tsx index b5bb034c0f3..17f676edf2b 100644 --- a/packages/compass-web/sandbox/index.tsx +++ b/packages/compass-web/sandbox/index.tsx @@ -48,7 +48,7 @@ const App = () => { enableGenAIFeaturesAtlasProject, enableGenAISampleDocumentPassingOnAtlasProject, enableGenAIFeaturesAtlasOrg, - optInDataExplorerGenAIFeatures, + optInGenAIFeatures, } = projectParams ?? {}; const atlasServiceSandboxBackendVariant = @@ -135,8 +135,7 @@ const App = () => { isAtlas && !!enableGenAISampleDocumentPassingOnAtlasProject, enableGenAIFeaturesAtlasOrg: isAtlas && !!enableGenAIFeaturesAtlasOrg, - optInDataExplorerGenAIFeatures: - isAtlas && !!optInDataExplorerGenAIFeatures, + optInGenAIFeatures: isAtlas && !!optInGenAIFeatures, enableDataModeling: true, }} onTrack={sandboxTelemetry.track} diff --git a/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx b/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx index fd6c5322273..c1c257881c7 100644 --- a/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx +++ b/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx @@ -19,7 +19,7 @@ type ProjectParams = { enableGenAIFeaturesAtlasProject: boolean; enableGenAISampleDocumentPassingOnAtlasProject: boolean; enableGenAIFeaturesAtlasOrg: boolean; - optInDataExplorerGenAIFeatures: boolean; + optInGenAIFeatures: boolean; }; type AtlasLoginReturnValue = @@ -129,8 +129,7 @@ export function useAtlasProxySignIn(): AtlasLoginReturnValue { projectId, csrfToken, csrfTime, - optInDataExplorerGenAIFeatures: - isOptedIntoDataExplorerGenAIFeatures, + optInGenAIFeatures: isOptedIntoDataExplorerGenAIFeatures, enableGenAIFeaturesAtlasOrg: genAIFeaturesEnabled, enableGenAISampleDocumentPassingOnAtlasProject: groupEnabledFeatureFlags.includes( diff --git a/packages/compass-web/src/preferences.tsx b/packages/compass-web/src/preferences.tsx index 13cdb1060cb..7347cdd53b4 100644 --- a/packages/compass-web/src/preferences.tsx +++ b/packages/compass-web/src/preferences.tsx @@ -55,7 +55,7 @@ export function useCompassWebPreferences( enableShell: false, enableCreatingNewConnections: false, enableGlobalWrites: false, - optInDataExplorerGenAIFeatures: false, + optInGenAIFeatures: false, enableConnectInNewWindow: false, ...initialPreferences, }) diff --git a/packages/compass/src/index.d.ts b/packages/compass/src/index.d.ts index fcfb851b225..3229c98dcd7 100644 --- a/packages/compass/src/index.d.ts +++ b/packages/compass/src/index.d.ts @@ -35,7 +35,7 @@ declare module 'process' { HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE?: string; COMPASS_ATLAS_SERVICE_UNAUTH_BASE_URL_OVERRIDE?: string; COMPASS_CLIENT_ID_OVERRIDE?: string; - COMPASS_E2E_SKIP_ATLAS_SIGNIN?: string; + COMPASS_E2E_SKIP_AI_OPT_IN?: string; COMPASS_OIDC_ISSUER_OVERRIDE?: string; COMPASS_ATLAS_AUTH_PORTAL_URL_OVERRIDE?: string; }