diff --git a/packages/compass-e2e-tests/helpers/commands/index.ts b/packages/compass-e2e-tests/helpers/commands/index.ts index 080501905f9..a686c300720 100644 --- a/packages/compass-e2e-tests/helpers/commands/index.ts +++ b/packages/compass-e2e-tests/helpers/commands/index.ts @@ -62,6 +62,7 @@ export * from './unhide-index'; export * from './hide-visible-modal'; export * from './hide-visible-toasts'; export * from './sidebar-collection'; +export * from './switch-pipeline-mode'; export * from './read-first-document-content'; export * from './read-stage-operators'; export * from './click-confirmation-action'; diff --git a/packages/compass-e2e-tests/helpers/commands/set-feature.ts b/packages/compass-e2e-tests/helpers/commands/set-feature.ts index 34e0ecb6ca8..c02297ab01e 100644 --- a/packages/compass-e2e-tests/helpers/commands/set-feature.ts +++ b/packages/compass-e2e-tests/helpers/commands/set-feature.ts @@ -1,11 +1,34 @@ import type { CompassBrowser } from '../compass-browser'; -import type { UserPreferences } from 'compass-preferences-model'; +import type { + AllPreferences, + UserPreferences, +} from 'compass-preferences-model'; +import { isTestingWeb } from '../test-runner-context'; export async function setFeature( browser: CompassBrowser, name: K, value: UserPreferences[K] ): Promise { + if (isTestingWeb()) { + // When running in Compass web we cannot use the IPC to update the + // preferences so we use a global function. + await browser.execute( + async (_name, _value) => { + const attributes: Partial = { + [_name]: _value === null ? undefined : _value, + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (globalThis as any).__compassWebE2ETestSavePreferences( + attributes + ); + }, + name, + value + ); + return; + } + await browser.execute( async (_name, _value) => { // eslint-disable-next-line @typescript-eslint/no-var-requires diff --git a/packages/compass-e2e-tests/helpers/commands/switch-pipeline-mode.ts b/packages/compass-e2e-tests/helpers/commands/switch-pipeline-mode.ts new file mode 100644 index 00000000000..0c08d89d25d --- /dev/null +++ b/packages/compass-e2e-tests/helpers/commands/switch-pipeline-mode.ts @@ -0,0 +1,10 @@ +import type { CompassBrowser } from '../compass-browser'; +import * as Selectors from '../selectors'; + +export async function switchPipelineMode( + browser: CompassBrowser, + mode: 'as-text' | 'builder-ui' +): Promise { + await browser.clickVisible(Selectors.aggregationPipelineModeToggle(mode)); + await browser.waitForAnimations(Selectors.AggregationBuilderWorkspace); +} diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index 6e09247e9ed..3c6253c5c10 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1239,12 +1239,11 @@ export const queryBarExportToLanguageButton = (tabName: string): string => { const tabSelector = collectionContent(tabName); return `${tabSelector} [data-testid="query-bar-open-export-to-language-button"]`; }; -export const QueryBarAIEntryButton = - '[data-testid="open-ai-query-entry-button"]'; -export const QueryBarAITextInput = '[data-testid="ai-user-text-input"]'; -export const QueryBarAIGenerateQueryButton = - '[data-testid="ai-generate-button"]'; -export const QueryBarAIErrorMessageBanner = '[data-testid="ai-error-msg"]'; +export const GenAIEntryButton = '[data-testid="open-ai-query-entry-button"]'; +export const GenAITextInput = '[data-testid="ai-user-text-input"]'; +export const GenAIGenerateQueryButton = '[data-testid="ai-generate-button"]'; +export const GenAIErrorMessageBanner = '[data-testid="ai-error-msg"]'; +export const GenAIOpenButton = '[data-testid="open-gen-ai-button"]'; // Workspace tabs export const WorkspaceTabsContainer = diff --git a/packages/compass-e2e-tests/helpers/test-runner-context.ts b/packages/compass-e2e-tests/helpers/test-runner-context.ts index 2c73d855471..b05e059ba32 100644 --- a/packages/compass-e2e-tests/helpers/test-runner-context.ts +++ b/packages/compass-e2e-tests/helpers/test-runner-context.ts @@ -344,6 +344,10 @@ process.env.HADRON_DISTRIBUTION ??= context.hadronDistribution; process.env.COMPASS_WEB_HTTP_PROXY_CLOUD_CONFIG ??= context.atlasCloudSandboxCloudConfig ?? 'dev'; +if (isTestingAtlasCloudSandbox(context)) { + process.env.E2E_TEST_CLOUD_WEB_ENABLE_PREFERENCE_SAVING ??= 'true'; +} + const testServerVersion = process.env.MONGODB_VERSION ?? process.env.MONGODB_RUNNER_VERSION; 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 new file mode 100644 index 00000000000..efbddc5477d --- /dev/null +++ b/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts @@ -0,0 +1,188 @@ +import { expect } from 'chai'; + +import type { CompassBrowser } from '../../helpers/compass-browser'; +import { + init, + cleanup, + screenshotIfFailed, + DEFAULT_CONNECTION_NAME_1, +} from '../../helpers/compass'; +import type { Compass } from '../../helpers/compass'; +import * as Selectors from '../../helpers/selectors'; +import { createNumbersCollection } from '../../helpers/insert-data'; +import { isTestingAtlasCloudSandbox } from '../../helpers/test-runner-context'; +import { switchPipelineMode } from '../../helpers/commands/switch-pipeline-mode'; + +describe('Collection ai query', function () { + let compass: Compass; + let browser: CompassBrowser; + + before(function () { + if (!isTestingAtlasCloudSandbox()) { + this.skip(); + } + }); + + afterEach(async function () { + await screenshotIfFailed(compass, this.currentTest); + await cleanup(compass); + }); + + describe('when the feature is enabled', function () { + beforeEach(async function () { + compass = await init(this.test?.fullTitle()); + browser = compass.browser; + await browser.setupDefaultConnections(); + + await createNumbersCollection(); + await browser.connectToDefaults(); + await browser.navigateToCollectionTab( + DEFAULT_CONNECTION_NAME_1, + 'test', + 'numbers', + 'Documents' + ); + + await browser.setFeature('enableGenAIFeaturesAtlasProject', true); + await browser.setFeature( + 'enableGenAISampleDocumentPassingOnAtlasProject', + true + ); + await browser.setFeature('enableGenAIFeaturesAtlasOrg', true); + await browser.setFeature('optInDataExplorerGenAIFeatures', true); + }); + + describe('on the documents tab', function () { + beforeEach(async function () { + await browser.navigateToCollectionTab( + DEFAULT_CONNECTION_NAME_1, + 'test', + 'numbers', + 'Documents' + ); + }); + + it('should update the query bar with a generated query', async function () { + // Click the ai entry button. + await browser.clickVisible(Selectors.GenAIEntryButton); + + // Enter the ai prompt. + await browser.clickVisible(Selectors.GenAITextInput); + + const testUserInput = 'find all documents where i is greater than 50'; + await browser.setValueVisible(Selectors.GenAITextInput, testUserInput); + + // Click generate. + await browser.clickVisible(Selectors.GenAIGenerateQueryButton); + + // Wait for the ipc events to succeed. + await browser.waitUntil(async function () { + // Make sure the query bar was updated. + const queryBarFilterContent = await browser.getCodemirrorEditorText( + Selectors.queryBarOptionInputFilter('Documents') + ); + return ( + queryBarFilterContent.includes('$gt') && + queryBarFilterContent.includes('50') + ); + }); + + // Run it and check that the correct documents are shown. + await browser.runFind('Documents', true); + const modifiedResult = await browser.getFirstListDocument(); + expect(modifiedResult.i).to.be.equal('51'); + }); + }); + + describe('on the aggregations tab', function () { + beforeEach(async function () { + await browser.navigateToCollectionTab( + DEFAULT_CONNECTION_NAME_1, + 'test', + 'numbers', + 'Aggregations' + ); + + await switchPipelineMode(browser, 'as-text'); + }); + + it('should update the aggregation editor with a generated aggregation', async function () { + // Click the ai entry button. + await browser.clickVisible(Selectors.GenAIOpenButton); + + // Enter the ai prompt. + await browser.clickVisible(Selectors.GenAITextInput); + + const testUserInput = 'find all documents where i is 99'; + await browser.setValueVisible(Selectors.GenAITextInput, testUserInput); + + // Click generate. + await browser.clickVisible(Selectors.GenAIGenerateQueryButton); + + await browser.waitUntil(async function () { + const textEditor = browser.$(Selectors.AggregationAsTextEditor); + const textContent = await textEditor.getText(); + return textContent.includes('$match'); + }); + + // Run it and check that the correct documents are shown. + await browser.clickVisible(Selectors.RunPipelineButton); + const resultsWorkspace = browser.$( + Selectors.AggregationResultsWorkspace + ); + await resultsWorkspace.waitForDisplayed(); + + await browser.clickVisible( + Selectors.AggregationResultsJSONListSwitchButton + ); + const rawDocuments = await browser.getCodemirrorEditorTextAll( + Selectors.DocumentJSONEntry + ); + const documents = rawDocuments.map((text) => { + return JSON.parse(text); + }); + + expect(documents).to.have.lengthOf(1); + expect(documents[0]).to.have.property('_id'); + expect(documents[0]).to.have.property('i', 99); + }); + }); + }); + + describe('when the org feature is disabled', function () { + beforeEach(async function () { + compass = await init(this.test?.fullTitle()); + browser = compass.browser; + await browser.setupDefaultConnections(); + + await createNumbersCollection(); + await browser.connectToDefaults(); + await browser.navigateToCollectionTab( + DEFAULT_CONNECTION_NAME_1, + 'test', + 'numbers', + 'Documents' + ); + + await browser.setFeature('enableGenAIFeaturesAtlasProject', true); + await browser.setFeature( + 'enableGenAISampleDocumentPassingOnAtlasProject', + true + ); + await browser.setFeature('enableGenAIFeaturesAtlasOrg', false); + await browser.setFeature('optInDataExplorerGenAIFeatures', true); + }); + + it('should not show the gen ai intro button', async function () { + // Ensure the query bar is shown. + await browser + .$(Selectors.queryBarOptionInputFilter('Documents')) + .waitForDisplayed(); + + const aiIntroButton = browser.$(Selectors.GenAIEntryButton); + const isSidebarCreateCollectionButtonExisting = + await aiIntroButton.isExisting(); + expect(isSidebarCreateCollectionButtonExisting).to.be.equal(false); + }); + }); +}); diff --git a/packages/compass-e2e-tests/tests/atlas-login.test.ts b/packages/compass-e2e-tests/tests/atlas-login.test.ts index 1972325e6f5..8e539acea58 100644 --- a/packages/compass-e2e-tests/tests/atlas-login.test.ts +++ b/packages/compass-e2e-tests/tests/atlas-login.test.ts @@ -295,7 +295,7 @@ describe('Atlas Login', function () { // control over it await browser.clickVisible('span=Not now'); - const aiInput = browser.$(Selectors.QueryBarAITextInput); + const aiInput = browser.$(Selectors.GenAITextInput); expect(await aiInput.isExisting()).to.eq(false); expect(await generateQueryButton.isDisplayed()).to.eq(true); }); @@ -328,7 +328,7 @@ describe('Atlas Login', function () { // control over it await browser.clickVisible('span=Not now'); - const aiInput = browser.$(Selectors.QueryBarAITextInput); + 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-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index 030cf3977dd..46ad52f3575 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -19,6 +19,7 @@ import { import { saveAggregationPipeline } from '../helpers/commands/save-aggregation-pipeline'; import { Key } from 'webdriverio'; import type { ChainablePromiseElement } from 'webdriverio'; +import { switchPipelineMode } from '../helpers/commands/switch-pipeline-mode'; const { expect } = chai; @@ -83,14 +84,6 @@ async function waitForTab(browser: CompassBrowser, namespace: string) { ); } -async function switchPipelineMode( - browser: CompassBrowser, - mode: 'as-text' | 'builder-ui' -) { - await browser.clickVisible(Selectors.aggregationPipelineModeToggle(mode)); - await browser.waitForAnimations(Selectors.AggregationBuilderWorkspace); -} - async function deleteStage( browser: CompassBrowser, index: number 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 b4a1d93b506..8c901b4132f 100644 --- a/packages/compass-e2e-tests/tests/collection-ai-query.test.ts +++ b/packages/compass-e2e-tests/tests/collection-ai-query.test.ts @@ -45,7 +45,6 @@ describe('Collection ai query', function () { clearRequests = _clearRequests; setMockAtlasServerResponse = _setMockAtlasServerResponse; - process.env.COMPASS_ATLAS_SERVICE_BASE_URL_OVERRIDE = endpoint; process.env.COMPASS_ATLAS_SERVICE_UNAUTH_BASE_URL_OVERRIDE = endpoint; telemetry = await startTelemetryServer(); @@ -73,7 +72,6 @@ describe('Collection ai query', function () { await stopMockAtlasServer(); - delete process.env.COMPASS_ATLAS_SERVICE_BASE_URL_OVERRIDE; delete process.env.COMPASS_E2E_SKIP_ATLAS_SIGNIN; await cleanup(compass); @@ -101,19 +99,16 @@ describe('Collection ai query', function () { it('makes request to the server and updates the query bar with the response', async function () { // Click the ai entry button. - await browser.clickVisible(Selectors.QueryBarAIEntryButton); + await browser.clickVisible(Selectors.GenAIEntryButton); // Enter the ai prompt. - await browser.clickVisible(Selectors.QueryBarAITextInput); + await browser.clickVisible(Selectors.GenAITextInput); const testUserInput = 'find all documents where i is greater than 50'; - await browser.setValueVisible( - Selectors.QueryBarAITextInput, - testUserInput - ); + await browser.setValueVisible(Selectors.GenAITextInput, testUserInput); // Click generate. - await browser.clickVisible(Selectors.QueryBarAIGenerateQueryButton); + await browser.clickVisible(Selectors.GenAIGenerateQueryButton); // Wait for the ipc events to succeed. await browser.waitUntil(async function () { @@ -163,22 +158,19 @@ describe('Collection ai query', function () { it('the error is shown to the user', async function () { // Click the ai entry button. - await browser.clickVisible(Selectors.QueryBarAIEntryButton); + await browser.clickVisible(Selectors.GenAIEntryButton); // Enter the ai prompt. - await browser.clickVisible(Selectors.QueryBarAITextInput); + await browser.clickVisible(Selectors.GenAITextInput); const testUserInput = 'find all documents where i is greater than 50'; - await browser.setValueVisible( - Selectors.QueryBarAITextInput, - testUserInput - ); + await browser.setValueVisible(Selectors.GenAITextInput, testUserInput); // Click generate. - await browser.clickVisible(Selectors.QueryBarAIGenerateQueryButton); + await browser.clickVisible(Selectors.GenAIGenerateQueryButton); // Check that the error is shown. - const errorBanner = browser.$(Selectors.QueryBarAIErrorMessageBanner); + const errorBanner = browser.$(Selectors.GenAIErrorMessageBanner); await errorBanner.waitForDisplayed(); expect(await errorBanner.getText()).to.equal( 'Sorry, we were unable to generate the query, please try again. If the error persists, try changing your prompt.' diff --git a/packages/compass-generative-ai/src/components/ai-experience-entry.tsx b/packages/compass-generative-ai/src/components/ai-experience-entry.tsx index 5333eab0b46..e5a0890301c 100644 --- a/packages/compass-generative-ai/src/components/ai-experience-entry.tsx +++ b/packages/compass-generative-ai/src/components/ai-experience-entry.tsx @@ -86,7 +86,7 @@ const aiEntryLightModeStyles = css( ); function AIExperienceEntry({ - 'data-testid': dataTestId, + 'data-testid': dataTestId = 'open-gen-ai-button', type, onClick, }: { 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 2db16c89ab9..31a2d253606 100644 --- a/packages/compass-preferences-model/src/compass-web-preferences-access.ts +++ b/packages/compass-preferences-model/src/compass-web-preferences-access.ts @@ -5,6 +5,16 @@ import { type AllPreferences } from './preferences-schema'; import { InMemoryStorage } from './preferences-in-memory-storage'; import { getActiveUser } from './utils'; +const editablePreferences: (keyof UserPreferences)[] = [ + 'optInDataExplorerGenAIFeatures', + 'cloudFeatureRolloutAccess', + + // Exposed for testing purposes. + 'enableGenAISampleDocumentPassingOnAtlasProject', + 'enableGenAIFeaturesAtlasOrg', + 'enableGenAIFeaturesAtlasProject', +]; + export class CompassWebPreferencesAccess implements PreferencesAccess { private _preferences: Preferences; constructor(preferencesOverrides?: Partial) { @@ -15,15 +25,12 @@ export class CompassWebPreferencesAccess implements PreferencesAccess { } savePreferences(_attributes: Partial) { - // Only allow saving the optInDataExplorerGenAIFeatures preference. + // Only allow runtime updating certain preferences. if ( Object.keys(_attributes).length === 1 && - 'optInDataExplorerGenAIFeatures' in _attributes - ) { - return Promise.resolve(this._preferences.savePreferences(_attributes)); - } else if ( - Object.keys(_attributes).length === 1 && - 'cloudFeatureRolloutAccess' in _attributes + editablePreferences.includes( + Object.keys(_attributes)[0] as keyof UserPreferences + ) ) { return Promise.resolve(this._preferences.savePreferences(_attributes)); } @@ -54,17 +61,12 @@ export class CompassWebPreferencesAccess implements PreferencesAccess { preferenceName: K, callback: (value: AllPreferences[K]) => void ) { - return ( - this._preferences?.onPreferencesChanged?.( - (preferences: Partial) => { - if (Object.keys(preferences).includes(preferenceName)) { - return callback((preferences as AllPreferences)[preferenceName]); - } + return this._preferences.onPreferencesChanged( + (preferences: Partial) => { + if (Object.keys(preferences).includes(preferenceName)) { + return callback((preferences as AllPreferences)[preferenceName]); } - ) ?? - (() => { - /* no fallback */ - }) + } ); } diff --git a/packages/compass-web/sandbox/index.tsx b/packages/compass-web/sandbox/index.tsx index f7fbd05e9f8..848a941ffbe 100644 --- a/packages/compass-web/sandbox/index.tsx +++ b/packages/compass-web/sandbox/index.tsx @@ -1,6 +1,7 @@ -import React, { useLayoutEffect } from 'react'; +import React, { useLayoutEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; import { resetGlobalCSS, css, Body } from '@mongodb-js/compass-components'; +import type { AllPreferences } from 'compass-preferences-model'; import { CompassWeb } from '../src/index'; import { SandboxConnectionStorageProvider } from '../src/connection-storage'; import { sandboxLogger } from './sandbox-logger'; @@ -8,6 +9,10 @@ import { sandboxTelemetry } from './sandbox-telemetry'; import { useAtlasProxySignIn } from './sandbox-atlas-sign-in'; import { sandboxConnectionStorage } from './sandbox-connection-storage'; import { useWorkspaceTabRouter } from './sandbox-workspace-tab-router'; +import { + SandboxPreferencesUpdateProvider, + type SandboxPreferencesUpdateTrigger, +} from '../src/preferences'; const sandboxContainerStyles = css({ width: '100%', @@ -31,7 +36,15 @@ function getMetaEl(name: string) { const App = () => { const [currentTab, updateCurrentTab] = useWorkspaceTabRouter(); const { status, projectParams } = useAtlasProxySignIn(); - const { projectId, csrfToken, csrfTime } = projectParams ?? {}; + const { + projectId, + csrfToken, + csrfTime, + enableGenAIFeaturesAtlasProject, + enableGenAISampleDocumentPassingOnAtlasProject, + enableGenAIFeaturesAtlasOrg, + optInDataExplorerGenAIFeatures, + } = projectParams ?? {}; const atlasServiceSandboxBackendVariant = process.env.COMPASS_WEB_HTTP_PROXY_CLOUD_CONFIG === 'local' @@ -41,8 +54,32 @@ const App = () => { ? 'web-sandbox-atlas-dev' : 'web-sandbox-atlas'; - const overrideGenAIEnablement = - process.env.COMPASS_WEB_GEN_AI_ENABLEMENT === 'true'; + const sandboxPreferencesUpdateTrigger = + useRef(null); + + const enablePreferencesUpdateTrigger = + process.env.E2E_TEST_CLOUD_WEB_ENABLE_PREFERENCE_SAVING === 'true'; + if ( + enablePreferencesUpdateTrigger && + sandboxPreferencesUpdateTrigger.current === null + ) { + sandboxPreferencesUpdateTrigger.current = ( + updatePreference: (preferences: Partial) => Promise + ) => { + // Useful for e2e test to override preferences. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).__compassWebE2ETestSavePreferences = async ( + attributes: Partial + ) => { + await updatePreference(attributes); + }; + + return () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (globalThis as any).__compassWebE2ETestSavePreferences; + }; + }; + } useLayoutEffect(() => { getMetaEl('csrf-token').setAttribute('content', csrfToken ?? ''); @@ -66,31 +103,38 @@ const App = () => { : {} } > - - - + + + + + ); }; diff --git a/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx b/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx index f75dce93c3d..a642ca7eb25 100644 --- a/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx +++ b/packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx @@ -16,6 +16,10 @@ type ProjectParams = { projectId: string; csrfToken: string; csrfTime: string; + enableGenAIFeaturesAtlasProject: boolean; + enableGenAISampleDocumentPassingOnAtlasProject: boolean; + enableGenAIFeaturesAtlasOrg: boolean; + optInDataExplorerGenAIFeatures: boolean; }; type AtlasLoginReturnValue = @@ -112,12 +116,32 @@ export function useAtlasProxySignIn(): AtlasLoginReturnValue { if (!projectId) { throw new Error('failed to get projectId'); } - const { csrfToken, csrfTime } = await fetch( - `/cloud-mongodb-com/v2/${projectId}/params` - ).then((res) => { - return res.json(); + const { + csrfToken, + csrfTime, + appUser: { isOptedIntoDataExplorerGenAIFeatures }, + currentOrganization: { genAIFeaturesEnabled }, + featureFlags: { groupEnabledFeatureFlags }, + } = await fetch(`/cloud-mongodb-com/v2/${projectId}/params`).then( + (res) => { + return res.json(); + } + ); + setProjectParams({ + projectId, + csrfToken, + csrfTime, + optInDataExplorerGenAIFeatures: + isOptedIntoDataExplorerGenAIFeatures, + enableGenAIFeaturesAtlasOrg: genAIFeaturesEnabled, + enableGenAISampleDocumentPassingOnAtlasProject: + groupEnabledFeatureFlags.includes( + 'ENABLE_DATA_EXPLORER_GEN_AI_SAMPLE_DOCUMENT_PASSING' + ), + enableGenAIFeaturesAtlasProject: groupEnabledFeatureFlags.includes( + 'ENABLE_DATA_EXPLORER_GEN_AI_FEATURES' + ), }); - setProjectParams({ projectId, csrfToken, csrfTime }); setStatus('signed-in'); if (IS_CI) { return; diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index b7c679cda4c..a4efefed8d2 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -38,10 +38,7 @@ import { DropNamespacePlugin, RenameCollectionPlugin, } from '@mongodb-js/compass-databases-collections'; -import { - PreferencesProvider, - CompassWebPreferencesAccess, -} from 'compass-preferences-model/provider'; +import { PreferencesProvider } from 'compass-preferences-model/provider'; import type { AllPreferences } from 'compass-preferences-model/provider'; import FieldStorePlugin from '@mongodb-js/compass-field-store'; import { AtlasServiceProvider } from '@mongodb-js/atlas-service/provider'; @@ -59,6 +56,7 @@ import type { import { useCompassWebLoggerAndTelemetry } from './logger-and-telemetry'; import { type TelemetryServiceOptions } from '@mongodb-js/compass-telemetry'; import { WorkspaceTab as WelcomeWorkspaceTab } from '@mongodb-js/compass-welcome'; +import { useCompassWebPreferences } from './preferences'; const WithAtlasProviders: React.FC = ({ children }) => { return ( @@ -262,31 +260,8 @@ const CompassWeb = ({ onDebug, }); - const preferencesAccess = useRef( - new CompassWebPreferencesAccess({ - maxTimeMS: 10_000, - enableExplainPlan: true, - enableAggregationBuilderRunPipeline: true, - enableAggregationBuilderExtraOptions: true, - enableImportExport: false, - enableGenAIFeatures: true, - enableGenAIFeaturesAtlasProject: false, - enableGenAISampleDocumentPassingOnAtlasProject: false, - enableGenAIFeaturesAtlasOrg: false, - enableMultipleConnectionSystem: true, - enablePerformanceAdvisorBanner: true, - cloudFeatureRolloutAccess: { - GEN_AI_COMPASS: false, - }, - maximumNumberOfActiveConnections: 10, - trackUsageStatistics: true, - enableShell: false, - enableCreatingNewConnections: false, - enableGlobalWrites: false, - optInDataExplorerGenAIFeatures: false, - ...initialPreferences, - }) - ); + const preferencesAccess = useCompassWebPreferences(initialPreferences); + const initialWorkspaceRef = useRef(initialWorkspace); const initialWorkspaceTabsRef = useRef( initialWorkspaceRef.current ? [initialWorkspaceRef.current] : [] diff --git a/packages/compass-web/src/preferences.tsx b/packages/compass-web/src/preferences.tsx new file mode 100644 index 00000000000..fe2a89999d5 --- /dev/null +++ b/packages/compass-web/src/preferences.tsx @@ -0,0 +1,75 @@ +import React, { useContext, useRef, useEffect } from 'react'; +import type { AllPreferences } from 'compass-preferences-model/provider'; +import { CompassWebPreferencesAccess } from 'compass-preferences-model/provider'; + +export type SandboxPreferencesUpdateTrigger = ( + updatePreference: (preferences: Partial) => Promise +) => () => void; + +const SandboxPreferencesUpdateTriggerContext = + React.createContext(null); + +function useSandboxPreferencesUpdateTrigger() { + const updateTrigger = useContext(SandboxPreferencesUpdateTriggerContext); + return updateTrigger; +} + +/** + * Only used in the sandbox to provide a way to update preferences. + * @internal + */ +export const SandboxPreferencesUpdateProvider = ({ + value, + children, +}: { + value: SandboxPreferencesUpdateTrigger | null; + children: React.ReactNode; +}) => { + return ( + + {children} + + ); +}; + +export function useCompassWebPreferences( + initialPreferences?: Partial +): React.MutableRefObject { + const preferencesAccess = useRef( + new CompassWebPreferencesAccess({ + maxTimeMS: 10_000, + enableExplainPlan: true, + enableAggregationBuilderRunPipeline: true, + enableAggregationBuilderExtraOptions: true, + enableImportExport: false, + enableGenAIFeatures: true, + enableGenAIFeaturesAtlasProject: false, + enableGenAISampleDocumentPassingOnAtlasProject: false, + enableGenAIFeaturesAtlasOrg: false, + enableMultipleConnectionSystem: true, + enablePerformanceAdvisorBanner: true, + cloudFeatureRolloutAccess: { + GEN_AI_COMPASS: false, + }, + maximumNumberOfActiveConnections: 10, + trackUsageStatistics: true, + enableShell: false, + enableCreatingNewConnections: false, + enableGlobalWrites: false, + optInDataExplorerGenAIFeatures: false, + ...initialPreferences, + }) + ); + + const onPreferencesUpdateTriggered = useSandboxPreferencesUpdateTrigger(); + + useEffect(() => { + // This is used by our e2e tests so that we can call a global function in the browser + // from the testing runtime to update preferences. + return onPreferencesUpdateTriggered?.(async (preferences) => { + await preferencesAccess.current.savePreferences(preferences); + }); + }, [onPreferencesUpdateTriggered]); + + return preferencesAccess; +} diff --git a/packages/compass-web/webpack.config.js b/packages/compass-web/webpack.config.js index f339ccd703d..6a0ec622284 100644 --- a/packages/compass-web/webpack.config.js +++ b/packages/compass-web/webpack.config.js @@ -194,6 +194,10 @@ module.exports = (env, args) => { 'process.env.COMPASS_WEB_HTTP_PROXY_CLOUD_CONFIG': JSON.stringify( process.env.COMPASS_WEB_HTTP_PROXY_CLOUD_CONFIG ?? 'dev' ), + 'process.env.E2E_TEST_CLOUD_WEB_ENABLE_PREFERENCE_SAVING': + JSON.stringify( + process.env.E2E_TEST_CLOUD_WEB_ENABLE_PREFERENCE_SAVING ?? 'false' + ), }), ], }); diff --git a/packages/compass/src/index.d.ts b/packages/compass/src/index.d.ts index f61c2afdaa2..cc5353cbbfd 100644 --- a/packages/compass/src/index.d.ts +++ b/packages/compass/src/index.d.ts @@ -32,9 +32,9 @@ declare module 'process' { HADRON_METRICS_SEGMENT_API_KEY?: string; HADRON_METRICS_SEGMENT_HOST?: string; HADRON_AUTO_UPDATE_ENDPOINT: string; - COMPASS_ATLAS_SERVICE_BASE_URL_OVERRIDE?: string; COMPASS_ATLAS_SERVICE_UNAUTH_BASE_URL_OVERRIDE?: string; COMPASS_CLIENT_ID_OVERRIDE?: string; + COMPASS_E2E_SKIP_ATLAS_SIGNIN?: string; COMPASS_OIDC_ISSUER_OVERRIDE?: string; COMPASS_ATLAS_AUTH_PORTAL_URL_OVERRIDE?: string; }