diff --git a/package-lock.json b/package-lock.json index 6d84e918a93..80f1be6c2cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43032,7 +43032,6 @@ "hadron-ipc": "^3.2.25", "lodash": "^4.17.21", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, @@ -44755,12 +44754,16 @@ "@mongodb-js/compass-connections": "^1.47.0", "@mongodb-js/compass-intercom": "^0.14.0", "@mongodb-js/compass-logging": "^1.4.10", + "@mongodb-js/compass-utils": "^0.6.14", "bson": "^6.8.0", "compass-preferences-model": "^2.30.0", "hadron-app-registry": "^9.2.7", "mongodb": "^6.9.0", "mongodb-schema": "^12.2.0", - "react": "^17.0.2" + "react": "^17.0.2", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "devDependencies": { "@mongodb-js/connection-info": "^0.9.3", @@ -54927,7 +54930,6 @@ "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2", "sinon": "^9.2.3", @@ -55977,6 +55979,7 @@ "@mongodb-js/compass-connections": "^1.47.0", "@mongodb-js/compass-intercom": "^0.14.0", "@mongodb-js/compass-logging": "^1.4.10", + "@mongodb-js/compass-utils": "^0.6.14", "@mongodb-js/connection-info": "^0.9.3", "@mongodb-js/eslint-config-compass": "^1.1.7", "@mongodb-js/mocha-config-compass": "^1.4.2", @@ -56002,6 +56005,9 @@ "p-queue": "^7.4.1", "prettier": "^2.7.1", "react": "^17.0.2", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", "sinon": "^9.2.3", "typescript": "^5.0.4", "xvfb-maybe": "^0.2.1" diff --git a/packages/atlas-service/package.json b/packages/atlas-service/package.json index 129ccb1d7e5..1978054d3b4 100644 --- a/packages/atlas-service/package.json +++ b/packages/atlas-service/package.json @@ -88,7 +88,6 @@ "hadron-ipc": "^3.2.25", "lodash": "^4.17.21", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" } diff --git a/packages/atlas-service/src/atlas-auth-service.ts b/packages/atlas-service/src/atlas-auth-service.ts index 22cf698a786..76f2ff6acf8 100644 --- a/packages/atlas-service/src/atlas-auth-service.ts +++ b/packages/atlas-service/src/atlas-auth-service.ts @@ -4,7 +4,7 @@ import type { AtlasUserInfo } from './util'; export type ArgsWithSignal> = T & { signal?: AbortSignal; }; -export type SignInPrompt = 'none' | 'ai-promo-modal'; +export type SignInPrompt = 'none'; type AtlasAuthServiceEvents = { 'signed-in': []; diff --git a/packages/atlas-service/src/compass-atlas-auth-service.ts b/packages/atlas-service/src/compass-atlas-auth-service.ts index d4ee83c9072..51f3fed1535 100644 --- a/packages/atlas-service/src/compass-atlas-auth-service.ts +++ b/packages/atlas-service/src/compass-atlas-auth-service.ts @@ -1,9 +1,6 @@ import { ipcRenderer } from 'hadron-ipc'; import type { CompassAuthService as AtlasServiceMain } from './main'; -import { - signInWithModalPrompt, - signInWithoutPrompt, -} from './store/atlas-signin-reducer'; +import { signInWithoutPrompt } from './store/atlas-signin-reducer'; import { getStore } from './store/atlas-signin-store'; import { AtlasAuthService } from './atlas-auth-service'; import type { ArgsWithSignal, SignInPrompt } from './atlas-auth-service'; @@ -46,8 +43,6 @@ export class CompassAtlasAuthService extends AtlasAuthService { switch (promptType) { case 'none': return getStore().dispatch(signInWithoutPrompt({ signal })); - case 'ai-promo-modal': - return getStore().dispatch(signInWithModalPrompt({ signal })); default: return this.ipc.signIn({ signal }); } diff --git a/packages/atlas-service/src/renderer.ts b/packages/atlas-service/src/renderer.ts index 2407aee7b21..f69e1ce5225 100644 --- a/packages/atlas-service/src/renderer.ts +++ b/packages/atlas-service/src/renderer.ts @@ -1,12 +1,11 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { activatePlugin } from './store/atlas-signin-store'; import { atlasAuthServiceLocator } from './provider'; -import { AtlasSignIn } from './components'; export const AtlasAuthPlugin = registerHadronPlugin( { name: 'AtlasAuth', - component: AtlasSignIn, + component: () => null, activate: activatePlugin, }, { diff --git a/packages/atlas-service/src/store/atlas-signin-reducer.spec.ts b/packages/atlas-service/src/store/atlas-signin-reducer.spec.ts index 653097138ab..0c13a4bc062 100644 --- a/packages/atlas-service/src/store/atlas-signin-reducer.spec.ts +++ b/packages/atlas-service/src/store/atlas-signin-reducer.spec.ts @@ -5,8 +5,6 @@ import { cancelSignIn, attemptId, AttemptStateMap, - signInWithModalPrompt, - closeSignInModal, signInWithoutPrompt, } from './atlas-signin-reducer'; import { expect } from 'chai'; @@ -176,7 +174,7 @@ describe('atlasSignInReducer', function () { atlasAuthService: mockAtlasService as any, }); - void store.dispatch(signInWithModalPrompt()).catch(() => {}); + void store.dispatch(signInWithoutPrompt()).catch(() => {}); await Promise.all([ store.dispatch(signIn()), @@ -186,101 +184,6 @@ describe('atlasSignInReducer', function () { }); }); - describe('signInWithModalPrompt', function () { - it('should resolve when user finishes sign in with prompt flow', async function () { - const mockAtlasService = { - isAuthenticated: sandbox.stub().resolves(false), - signIn: sandbox.stub().resolves({ sub: '1234' }), - getUserInfo: sandbox.stub().resolves({ sub: '1234' }), - emit: sandbox.stub(), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - }); - - const signInPromise = store.dispatch(signInWithModalPrompt()); - await store.dispatch(signIn()); - await signInPromise; - - expect(store.getState()).to.have.property('state', 'success'); - }); - - it('should reject if sign in flow fails', async function () { - const mockAtlasService = { - isAuthenticated: sandbox.stub().resolves(false), - signIn: sandbox.stub().rejects(new Error('Whoops!')), - getUserInfo: sandbox.stub().resolves({ sub: '1234' }), - emit: sandbox.stub(), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - }); - - const signInPromise = store.dispatch(signInWithModalPrompt()); - 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()).to.have.property('state', 'error'); - }); - - it('should reject if user dismissed the modal', async function () { - const mockAtlasService = { - isAuthenticated: sandbox.stub().resolves(false), - signIn: sandbox.stub().resolves({ sub: '1234' }), - getUserInfo: sandbox.stub().resolves({ sub: '1234' }), - emit: sandbox.stub(), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - }); - - const signInPromise = store.dispatch(signInWithModalPrompt()); - store.dispatch(closeSignInModal()); - - 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()).to.have.property('state', 'canceled'); - }); - - it('should reject if provided signal was aborted', async function () { - const mockAtlasService = { - isAuthenticated: sandbox.stub().resolves(false), - signIn: sandbox.stub().resolves({ sub: '1234' }), - getUserInfo: sandbox.stub().resolves({ sub: '1234' }), - emit: sandbox.stub(), - }; - const store = configureStore({ - atlasAuthService: mockAtlasService as any, - }); - - const c = new AbortController(); - const signInPromise = store.dispatch( - signInWithModalPrompt({ 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()).to.have.property('state', 'canceled'); - }); - }); - describe('signInWithoutPrompt', function () { it('should resolve when sign in flow finishes', async function () { const mockAtlasService = { diff --git a/packages/atlas-service/src/store/atlas-signin-reducer.ts b/packages/atlas-service/src/store/atlas-signin-reducer.ts index b1d4c4d8b6f..92561fdee0c 100644 --- a/packages/atlas-service/src/store/atlas-signin-reducer.ts +++ b/packages/atlas-service/src/store/atlas-signin-reducer.ts @@ -14,7 +14,6 @@ export function isAction( export type AtlasSignInState = { error: string | null; - isModalOpen: boolean; // For managing attempt state that doesn't belong in the store currentAttemptId: number | null; } & ( @@ -37,8 +36,6 @@ export type AtlasSignInThunkAction< > = ThunkAction; export const enum AtlasSignInActions { - OpenSignInModal = 'atlas-service/atlas-signin/OpenSignInModal', - CloseSignInModal = 'atlas-service/atlas-signin/CloseSignInModal', RestoringStart = 'atlas-service/atlas-signin/StartRestoring', RestoringFailed = 'atlas-service/atlas-signin/RestoringFailed', RestoringSuccess = 'atlas-service/atlas-signin/RestoringSuccess', @@ -52,14 +49,6 @@ export const enum AtlasSignInActions { SignedOut = 'atlas-service/atlas-signin/SignedOut', } -export type AtlasSignInOpenModalAction = { - type: AtlasSignInActions.OpenSignInModal; -}; - -export type AtlasSignInCloseModalAction = { - type: AtlasSignInActions.CloseSignInModal; -}; - export type AtlasSignInRestoringStartAction = { type: AtlasSignInActions.RestoringStart; }; @@ -250,24 +239,6 @@ const reducer: Reducer = ( 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, @@ -337,29 +308,6 @@ const startAttempt = (fn: () => void): AtlasSignInThunkAction => { }; }; -export const signInWithModalPrompt = ({ - signal, -}: { signal?: AbortSignal } = {}): AtlasSignInThunkAction< - Promise -> => { - return async (dispatch, getState) => { - // Nothing to do if we already signed in - const { state, userInfo } = getState(); - if (state === 'success') { - return userInfo; - } - const attempt = dispatch( - startAttempt(() => { - dispatch(openSignInModal()); - }) - ); - signal?.addEventListener('abort', () => { - dispatch(closeSignInModal(signal.reason)); - }); - return attempt.promise; - }; -}; - export const signInWithoutPrompt = ({ signal, }: { signal?: AbortSignal } = {}): AtlasSignInThunkAction< @@ -383,12 +331,8 @@ export const signInWithoutPrompt = ({ }; }; -export const openSignInModal = () => { - return { type: AtlasSignInActions.OpenSignInModal }; -}; - /** - * Sign in from the opt in window + * Sign into Atlas. To be called when the user isn't signed in yet. */ export const signIn = (): AtlasSignInThunkAction> => { return async (dispatch, getState, { atlasAuthService }) => { @@ -434,15 +378,6 @@ export const signIn = (): AtlasSignInThunkAction> => { }; }; -export const closeSignInModal = ( - reason?: any -): AtlasSignInThunkAction => { - return (dispatch) => { - dispatch(cancelSignIn(reason)); - dispatch({ type: AtlasSignInActions.CloseSignInModal }); - }; -}; - export const cancelSignIn = (reason?: any): AtlasSignInThunkAction => { return (dispatch, getState) => { // Can't cancel sign in after the flow was finished indicated by current diff --git a/packages/compass-aggregations/src/index.ts b/packages/compass-aggregations/src/index.ts index 386fef6a51e..967b2e091bb 100644 --- a/packages/compass-aggregations/src/index.ts +++ b/packages/compass-aggregations/src/index.ts @@ -25,7 +25,6 @@ import { } from '@mongodb-js/compass-app-stores/provider'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; -import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provider'; import { pipelineStorageLocator } from '@mongodb-js/my-queries-storage/provider'; import { connectionRepositoryAccessLocator } from '@mongodb-js/compass-connections/provider'; @@ -49,7 +48,6 @@ const CompassAggregationsHadronPlugin = registerHadronPlugin( preferences: preferencesLocator, logger: createLoggerLocator('COMPASS-AGGREGATIONS-UI'), track: telemetryLocator, - atlasAuthService: atlasAuthServiceLocator, atlasAiService: atlasAiServiceLocator, pipelineStorage: pipelineStorageLocator, connectionInfoRef: connectionInfoRefLocator, diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts index 2a58a0a6cf0..59686b726d0 100644 --- a/packages/compass-aggregations/src/modules/index.ts +++ b/packages/compass-aggregations/src/modules/index.ts @@ -42,7 +42,6 @@ import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type AppRegistry from 'hadron-app-registry'; import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; -import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { MongoDBInstance } from 'mongodb-instance-model'; import type { DataService } from '../modules/data-service'; import type { @@ -100,7 +99,6 @@ export type PipelineBuilderExtraArgs = { localAppRegistry: AppRegistry; pipelineBuilder: PipelineBuilder; pipelineStorage: PipelineStorage; - atlasAuthService: AtlasAuthService; workspaces: WorkspacesService; preferences: PreferencesAccess; logger: Logger; 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 5c60b683f8c..f2f980d485a 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts @@ -447,10 +447,10 @@ export const resetIsAggregationGeneratedFromQuery = }; export const showInput = (): PipelineBuilderThunkAction> => { - return async (dispatch, _getState, { atlasAuthService }) => { + return async (dispatch, _getState, { atlasAiService }) => { try { if (process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN !== 'true') { - await atlasAuthService.signIn({ promptType: 'ai-promo-modal' }); + await atlasAiService.ensureAiFeatureAccess(); } dispatch({ type: AIPipelineActionTypes.ShowInput, diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts index 8de3a4be5ae..b85472af616 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts @@ -128,7 +128,6 @@ function createStore({ thunk.withExtraArgument({ globalAppRegistry: new AppRegistry(), localAppRegistry: new AppRegistry(), - atlasAuthService: {} as any, atlasAiService: {} as any, pipelineBuilder, pipelineStorage: new CompassPipelineStorage(), diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.spec.ts index ee34bed867b..72a4743c575 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.spec.ts @@ -44,7 +44,6 @@ function createStore( }, applyMiddleware( thunk.withExtraArgument({ - atlasAuthService: {} as any, atlasAiService: {} as any, pipelineBuilder, pipelineStorage: new CompassPipelineStorage(), diff --git a/packages/compass-aggregations/src/stores/store.ts b/packages/compass-aggregations/src/stores/store.ts index 4f0d950be02..7ee9ebedb01 100644 --- a/packages/compass-aggregations/src/stores/store.ts +++ b/packages/compass-aggregations/src/stores/store.ts @@ -33,7 +33,6 @@ import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; -import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { PipelineStorage } from '@mongodb-js/my-queries-storage/provider'; import { maxTimeMSChanged } from '../modules/max-time-ms'; import type { @@ -80,7 +79,6 @@ export type AggregationsPluginServices = { preferences: PreferencesAccess; logger: Logger; track: TrackFunction; - atlasAuthService: AtlasAuthService; atlasAiService: AtlasAiService; pipelineStorage?: PipelineStorage; connectionInfoRef: ConnectionInfoRef; @@ -100,7 +98,6 @@ export function activateAggregationsPlugin( logger, track, atlasAiService, - atlasAuthService, pipelineStorage, connectionInfoRef, connectionScopedAppRegistry, @@ -184,7 +181,6 @@ export function activateAggregationsPlugin( localAppRegistry, pipelineBuilder, pipelineStorage, - atlasAuthService, workspaces, instance, preferences, diff --git a/packages/compass-aggregations/test/configure-store.ts b/packages/compass-aggregations/test/configure-store.ts index a211c17dea9..4cf517cbbf8 100644 --- a/packages/compass-aggregations/test/configure-store.ts +++ b/packages/compass-aggregations/test/configure-store.ts @@ -3,31 +3,12 @@ import type { ConfigureStoreOptions, } from '../src/stores/store'; import { mockDataService } from './mocks/data-service'; -import { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import { createPluginTestHelpers } from '@mongodb-js/testing-library-compass'; import { CompassAggregationsPlugin } from '../src/index'; import type { DataService } from '@mongodb-js/compass-connections/provider'; import React from 'react'; import { PipelineStorageProvider } from '@mongodb-js/my-queries-storage/provider'; -export class MockAtlasAuthService extends AtlasAuthService { - isAuthenticated() { - return Promise.resolve(true); - } - async getUserInfo() { - return Promise.resolve({} as any); - } - async signIn() { - return Promise.resolve({} as any); - } - async signOut() { - return Promise.resolve(); - } - getAuthHeaders() { - return Promise.resolve({}); - } -} - export class MockAtlasAiService { async getAggregationFromUserInput() { return Promise.resolve({}); @@ -35,6 +16,9 @@ export class MockAtlasAiService { async getQueryFromUserInput() { return Promise.resolve({}); } + async ensureAiFeatureAccess() { + return Promise.resolve(); + } } function getMockedPluginArgs( @@ -42,11 +26,9 @@ function getMockedPluginArgs( dataService: Partial = mockDataService(), services: Partial = {} ) { - const atlasAuthService = new MockAtlasAuthService(); const atlasAiService = new MockAtlasAiService(); return [ CompassAggregationsPlugin.provider.withMockServices({ - atlasAuthService, atlasAiService, collection: { toJSON: () => ({}), diff --git a/packages/compass-crud/src/components/crud-toolbar.spec.tsx b/packages/compass-crud/src/components/crud-toolbar.spec.tsx index 77b4643f491..43ce37903c3 100644 --- a/packages/compass-crud/src/components/crud-toolbar.spec.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.spec.tsx @@ -39,7 +39,6 @@ const MockQueryBarPlugin = QueryBarPlugin.withMockServices({ favoriteQueryStorageAccess: compassFavoriteQueryStorageAccess, recentQueryStorageAccess: compassRecentQueryStorageAccess, atlasAiService: {} as any, - atlasAuthService: { on() {}, removeListener() {} } as any, }); const addDataText = 'Add Data'; diff --git a/packages/compass-generative-ai/package.json b/packages/compass-generative-ai/package.json index 5c051a9c6ac..b7a0114f514 100644 --- a/packages/compass-generative-ai/package.json +++ b/packages/compass-generative-ai/package.json @@ -57,12 +57,16 @@ "@mongodb-js/compass-connections": "^1.47.0", "@mongodb-js/compass-intercom": "^0.14.0", "@mongodb-js/compass-logging": "^1.4.10", + "@mongodb-js/compass-utils": "^0.6.14", "bson": "^6.8.0", "compass-preferences-model": "^2.30.0", "hadron-app-registry": "^9.2.7", "mongodb": "^6.9.0", "mongodb-schema": "^12.2.0", - "react": "^17.0.2" + "react": "^17.0.2", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "devDependencies": { "@mongodb-js/connection-info": "^0.9.3", diff --git a/packages/compass-generative-ai/src/atlas-ai-service.ts b/packages/compass-generative-ai/src/atlas-ai-service.ts index b5038c1faf9..89cdccfaf8f 100644 --- a/packages/compass-generative-ai/src/atlas-ai-service.ts +++ b/packages/compass-generative-ai/src/atlas-ai-service.ts @@ -9,6 +9,8 @@ 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-signin-store'; type GenerativeAiInput = { userInput: string; @@ -324,6 +326,12 @@ 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. + return getStore().dispatch(signIntoAtlasWithModalPrompt({ signal })); + } + private getQueryOrAggregationFromUserInput = async ( { urlId, diff --git a/packages/atlas-service/src/components/ai-signin-banner-image.tsx b/packages/compass-generative-ai/src/components/atlas-signin/ai-signin-banner-image.tsx similarity index 100% rename from packages/atlas-service/src/components/ai-signin-banner-image.tsx rename to packages/compass-generative-ai/src/components/atlas-signin/ai-signin-banner-image.tsx diff --git a/packages/atlas-service/src/components/ai-signin-modal.tsx b/packages/compass-generative-ai/src/components/atlas-signin/ai-signin-modal.tsx similarity index 95% rename from packages/atlas-service/src/components/ai-signin-modal.tsx rename to packages/compass-generative-ai/src/components/atlas-signin/ai-signin-modal.tsx index 464dc88a559..a199945b067 100644 --- a/packages/atlas-service/src/components/ai-signin-modal.tsx +++ b/packages/compass-generative-ai/src/components/atlas-signin/ai-signin-modal.tsx @@ -11,8 +11,8 @@ import { useDarkMode, } from '@mongodb-js/compass-components'; import { AISignInImageBanner } from './ai-signin-banner-image'; -import type { AtlasSignInState } from '../store/atlas-signin-reducer'; -import { closeSignInModal, signIn } from '../store/atlas-signin-reducer'; +import type { AtlasSignInState } from '../../store/atlas-signin-reducer'; +import { closeSignInModal, signIn } from '../../store/atlas-signin-reducer'; const GEN_AI_FAQ_LINK = 'https://www.mongodb.com/docs/generative-ai-faq/'; diff --git a/packages/atlas-service/src/components/index.tsx b/packages/compass-generative-ai/src/components/atlas-signin/index.tsx similarity index 100% rename from packages/atlas-service/src/components/index.tsx rename to packages/compass-generative-ai/src/components/atlas-signin/index.tsx diff --git a/packages/compass-generative-ai/src/components/index.ts b/packages/compass-generative-ai/src/components/index.ts index f2f97da4ac7..cc65c9df4f4 100644 --- a/packages/compass-generative-ai/src/components/index.ts +++ b/packages/compass-generative-ai/src/components/index.ts @@ -3,3 +3,4 @@ export { AIExperienceEntry, createAIPlaceholderHTMLPlaceholder, } from './ai-experience-entry'; +export { AtlasSignIn } from './atlas-signin'; diff --git a/packages/compass-generative-ai/src/index.ts b/packages/compass-generative-ai/src/index.ts index caf0b37a175..a6f55d1bbec 100644 --- a/packages/compass-generative-ai/src/index.ts +++ b/packages/compass-generative-ai/src/index.ts @@ -1,3 +1,20 @@ +import { registerHadronPlugin } from 'hadron-app-registry'; +import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; + +import { activatePlugin } from './store/atlas-signin-store'; +import { AtlasSignIn } from './components'; + +export const CompassGenerativeAIPlugin = registerHadronPlugin( + { + name: 'CompassGenerativeAI', + component: AtlasSignIn, + activate: activatePlugin, + }, + { + atlasAuthService: atlasAuthServiceLocator, + } +); + export { AIExperienceEntry, GenerativeAIInput, 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 new file mode 100644 index 00000000000..3a036cb536c --- /dev/null +++ b/packages/compass-generative-ai/src/store/atlas-signin-reducer.spec.ts @@ -0,0 +1,189 @@ +import Sinon from 'sinon'; +import { expect } from 'chai'; + +import { + signIn, + cancelSignIn, + attemptId, + AttemptStateMap, + signIntoAtlasWithModalPrompt, + closeSignInModal, + atlasServiceSignedIn, +} from './atlas-signin-reducer'; +import { configureStore } from './atlas-signin-store'; + +describe('atlasSignInReducer', function () { + const sandbox = Sinon.createSandbox(); + + 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, + }); + + expect(store.getState()).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()).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, + }); + + expect(store.getState()).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()).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, + }); + + 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()).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, + }); + expect(store.getState()).to.have.nested.property('state', 'initial'); + store.dispatch(cancelSignIn()); + expect(store.getState()).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, + }); + + void store.dispatch(signIntoAtlasWithModalPrompt()).catch(() => {}); + + await Promise.all([ + store.dispatch(signIn()), + store.dispatch(cancelSignIn()), + ]); + expect(store.getState()).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, + }); + + const signInPromise = store.dispatch(signIntoAtlasWithModalPrompt()); + await store.dispatch(signIn()); + await signInPromise; + + expect(store.getState()).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, + }); + + 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()).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, + }); + + 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()).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, + }); + + 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()).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 new file mode 100644 index 00000000000..52b15b96920 --- /dev/null +++ b/packages/compass-generative-ai/src/store/atlas-signin-reducer.ts @@ -0,0 +1,357 @@ +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'; + +function isAction( + action: AnyAction, + type: A['type'] +): action is A { + return action.type === type; +} + +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', + TokenRefreshFailed = 'compass-generative-ai/atlas-signin/TokenRefreshFailed', + 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.TokenRefreshFailed; +}; + +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 reducer: 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.TokenRefreshFailed + ) + ) { + // 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().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(); + 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().state)) { + return; + } + const { attemptId } = getState(); + if (attemptId === null) { + return; + } + const { + controller: { signal }, + resolve, + reject, + } = getAttempt(getState().attemptId); + dispatch({ + type: AtlasSignInActions.Start, + }); + + try { + throwIfAborted(signal); + + await atlasAuthService.signIn({ + signal, + promptType: 'none', + }); + 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().attemptId === null) { + return; + } + const attempt = getAttempt(getState().attemptId); + attempt.controller.abort(); + attempt.reject(reason ?? attempt.controller.signal.reason); + dispatch({ type: AtlasSignInActions.Cancel }); + }; +}; + +export const atlasServiceTokenRefreshFailed = () => ({ + type: AtlasSignInActions.TokenRefreshFailed, +}); + +export const atlasServiceSignedOut = () => ({ + type: AtlasSignInActions.SignedOut, +}); + +export const atlasServiceSignedIn = () => ({ + type: AtlasSignInActions.Success, +}); + +export default reducer; diff --git a/packages/compass-generative-ai/src/store/atlas-signin-store.ts b/packages/compass-generative-ai/src/store/atlas-signin-store.ts new file mode 100644 index 00000000000..389a92549bd --- /dev/null +++ b/packages/compass-generative-ai/src/store/atlas-signin-store.ts @@ -0,0 +1,54 @@ +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import reducer, { + atlasServiceSignedOut, + atlasServiceSignedIn, + atlasServiceTokenRefreshFailed, +} from './atlas-signin-reducer'; +import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; +import type { ActivateHelpers } from 'hadron-app-registry'; + +let store: CompassGenerativeAIServiceStore; +export function getStore() { + if (!store) { + throw new Error('CompassGenerativeAIPlugin not activated'); + } + return store; +} + +export type CompassGenerativeAIPluginServices = { + atlasAuthService: AtlasAuthService; +}; +export function activatePlugin( + _: Record, + services: CompassGenerativeAIPluginServices, + { 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(atlasServiceTokenRefreshFailed()); + }); + + return { store, deactivate: cleanup }; +} + +export function configureStore({ + atlasAuthService, +}: CompassGenerativeAIPluginServices) { + const store = createStore( + reducer, + applyMiddleware(thunk.withExtraArgument({ atlasAuthService })) + ); + return store; +} + +export type CompassGenerativeAIServiceStore = ReturnType; diff --git a/packages/compass-query-bar/src/index.tsx b/packages/compass-query-bar/src/index.tsx index c9867acdbe0..f7e55a69ee9 100644 --- a/packages/compass-query-bar/src/index.tsx +++ b/packages/compass-query-bar/src/index.tsx @@ -20,7 +20,6 @@ import type { QueryBarService } from './components/hooks'; import QueryBarComponent from './components/query-bar'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; -import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provider'; import { favoriteQueryStorageAccessLocator, @@ -55,7 +54,6 @@ const QueryBarPlugin = registerHadronPlugin( track: telemetryLocator, connectionInfoRef: connectionInfoRefLocator, atlasAiService: atlasAiServiceLocator, - atlasAuthService: atlasAuthServiceLocator, favoriteQueryStorageAccess: favoriteQueryStorageAccessLocator, recentQueryStorageAccess: recentQueryStorageAccessLocator, } diff --git a/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts b/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts index 601b2eed2cd..515c03795fe 100644 --- a/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts +++ b/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts @@ -61,7 +61,6 @@ describe('aiQueryReducer', function () { { dataService: mockDataService, connectionInfoRef, - atlasAuthService: { on: sandbox.stub() }, atlasAiService: mockAtlasAiService, preferences, logger: createNoopLogger(), @@ -104,7 +103,6 @@ describe('aiQueryReducer', function () { }; const store = createStore({}, { - atlasAuthService: { on: sandbox.stub() }, atlasAiService: mockAtlasAiService, connectionInfoRef, dataService: { @@ -132,7 +130,6 @@ describe('aiQueryReducer', function () { getQueryFromUserInput: sandbox.stub().rejects(authError), }; const store = createStore({}, { - atlasAuthService: { on: sandbox.stub() }, atlasAiService: mockAtlasAiService, dataService: { sample() { @@ -182,7 +179,6 @@ describe('aiQueryReducer', function () { { dataService: mockDataService, connectionInfoRef, - atlasAuthService: { on: sandbox.stub() }, atlasAiService: mockAtlasAiService, preferences, logger: createNoopLogger(), @@ -223,7 +219,6 @@ describe('aiQueryReducer', function () { { dataService: mockDataService, connectionInfoRef, - atlasAuthService: { on: sandbox.stub() }, atlasAiService: mockAtlasAiService, preferences, logger: createNoopLogger(), 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 9ed8735b0b2..7cb478e1a64 100644 --- a/packages/compass-query-bar/src/stores/ai-query-reducer.ts +++ b/packages/compass-query-bar/src/stores/ai-query-reducer.ts @@ -412,10 +412,10 @@ export const cancelAIQuery = (): QueryBarThunkAction< }; export const showInput = (): QueryBarThunkAction> => { - return async (dispatch, _getState, { atlasAuthService }) => { + return async (dispatch, _getState, { atlasAiService }) => { try { if (process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN !== 'true') { - await atlasAuthService.signIn({ promptType: 'ai-promo-modal' }); + await atlasAiService.ensureAiFeatureAccess(); } dispatch({ type: AIQueryActionTypes.ShowInput }); } catch { diff --git a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts index 38e23249db7..e181190bbe5 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts @@ -55,7 +55,6 @@ describe('createQueryWithHistoryAutocompleter', function () { loadAll: loadAllStub, }), }, - atlasAuthService: { on: sinon.stub() }, atlasAiService: mockService, preferences, logger: createNoopLogger(), diff --git a/packages/compass-query-bar/src/stores/query-bar-store.ts b/packages/compass-query-bar/src/stores/query-bar-store.ts index 6160fec1b0c..52425866cc2 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -27,7 +27,6 @@ import type { ActivateHelpers } from 'hadron-app-registry'; import type { MongoDBInstance } from 'mongodb-instance-model'; import { QueryBarStoreContext } from './context'; import type { Logger } from '@mongodb-js/compass-logging/provider'; -import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; import type { FavoriteQueryStorageAccess, @@ -49,7 +48,6 @@ type QueryBarServices = { logger: Logger; track: TrackFunction; connectionInfoRef: ConnectionInfoRef; - atlasAuthService: AtlasAuthService; atlasAiService: AtlasAiService; favoriteQueryStorageAccess?: FavoriteQueryStorageAccess; recentQueryStorageAccess?: RecentQueryStorageAccess; @@ -58,7 +56,7 @@ type QueryBarServices = { // TODO(COMPASS-7412): this doesn't have service injector // implemented yet, so we're keeping it separate from the type above type QueryBarExtraServices = { - atlasAuthService?: AtlasAuthService; + atlasAIService?: AtlasAiService; favoriteQueryStorage?: FavoriteQueryStorage; recentQueryStorage?: RecentQueryStorage; }; @@ -76,7 +74,6 @@ export type QueryBarExtraArgs = { globalAppRegistry: AppRegistry; localAppRegistry: AppRegistry; dataService: Pick; - atlasAuthService: AtlasAuthService; preferences: PreferencesAccess; favoriteQueryStorage?: FavoriteQueryStorage; recentQueryStorage?: RecentQueryStorage; @@ -126,7 +123,6 @@ export function activatePlugin( logger, track, connectionInfoRef, - atlasAuthService, atlasAiService, favoriteQueryStorageAccess, recentQueryStorageAccess, @@ -156,7 +152,6 @@ export function activatePlugin( globalAppRegistry, recentQueryStorage, favoriteQueryStorage, - atlasAuthService, preferences, logger, track, diff --git a/packages/compass-schema/src/components/field.spec.tsx b/packages/compass-schema/src/components/field.spec.tsx index 95e15a91b35..68c2ca09196 100644 --- a/packages/compass-schema/src/components/field.spec.tsx +++ b/packages/compass-schema/src/components/field.spec.tsx @@ -34,7 +34,6 @@ const MockQueryBarPlugin = QueryBarPlugin.withMockServices({ favoriteQueryStorageAccess: compassFavoriteQueryStorageAccess, recentQueryStorageAccess: compassRecentQueryStorageAccess, atlasAiService: {} as any, - atlasAuthService: { on() {}, removeListener() {} } as any, }); function renderField( diff --git a/packages/compass-schema/src/components/schema-toolbar/schema-toolbar.spec.tsx b/packages/compass-schema/src/components/schema-toolbar/schema-toolbar.spec.tsx index 4a197970e15..633350e5fb3 100644 --- a/packages/compass-schema/src/components/schema-toolbar/schema-toolbar.spec.tsx +++ b/packages/compass-schema/src/components/schema-toolbar/schema-toolbar.spec.tsx @@ -23,7 +23,6 @@ const MockQueryBarPlugin = QueryBarPlugin.withMockServices({ favoriteQueryStorageAccess: compassFavoriteQueryStorageAccess, recentQueryStorageAccess: compassRecentQueryStorageAccess, atlasAiService: {} as any, - atlasAuthService: { on() {}, removeListener() {} } as any, }); const testErrorMessage = diff --git a/packages/compass/src/app/components/home.tsx b/packages/compass/src/app/components/home.tsx index 58660f7c992..fbbd2b97f21 100644 --- a/packages/compass/src/app/components/home.tsx +++ b/packages/compass/src/app/components/home.tsx @@ -32,6 +32,7 @@ import { getExtraConnectionData } from '../utils/telemetry'; import { CompassInstanceStorePlugin } from '@mongodb-js/compass-app-stores'; import FieldStorePlugin from '@mongodb-js/compass-field-store'; import { AtlasAuthPlugin } from '@mongodb-js/atlas-service/renderer'; +import { CompassGenerativeAIPlugin } from '@mongodb-js/compass-generative-ai'; import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; import { ConnectionStorageProvider } from '@mongodb-js/connection-storage/provider'; import { @@ -220,6 +221,7 @@ function Home({ +