diff --git a/packages/compass-preferences-model/src/compass-web-preferences-access.ts b/packages/compass-preferences-model/src/compass-web-preferences-access.ts new file mode 100644 index 00000000000..9e4c87d5dc3 --- /dev/null +++ b/packages/compass-preferences-model/src/compass-web-preferences-access.ts @@ -0,0 +1,75 @@ +import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; +import { Preferences, type PreferencesAccess } from './preferences'; +import type { UserPreferences } from './preferences-schema'; +import { type AllPreferences } from './preferences-schema'; +import { InMemoryStorage } from './preferences-in-memory-storage'; +import { getActiveUser } from './utils'; + +export class CompassWebPreferencesAccess implements PreferencesAccess { + private _preferences: Preferences; + constructor(preferencesOverrides?: Partial) { + this._preferences = new Preferences({ + logger: createNoopLogger(), + preferencesStorage: new InMemoryStorage(preferencesOverrides), + }); + } + + savePreferences(_attributes: Partial) { + if ( + Object.keys(_attributes).length === 1 && + 'optInDataExplorerGenAIFeatures' in _attributes + ) { + return Promise.resolve(this._preferences.savePreferences(_attributes)); + } + // do not save any attributes other than the optInDataExplorerGenAIFeatures setting + return Promise.resolve(this._preferences.getPreferences()); + } + + refreshPreferences() { + return Promise.resolve(this._preferences.getPreferences()); + } + + getPreferences() { + return this._preferences.getPreferences(); + } + + ensureDefaultConfigurableUserPreferences() { + return this._preferences.ensureDefaultConfigurableUserPreferences(); + } + + getConfigurableUserPreferences() { + return Promise.resolve(this._preferences.getConfigurableUserPreferences()); + } + + getPreferenceStates() { + return Promise.resolve(this._preferences.getPreferenceStates()); + } + + onPreferenceValueChanged( + 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]); + } + } + ) ?? + (() => { + /* no fallback */ + }) + ); + } + + createSandbox() { + return Promise.resolve( + new CompassWebPreferencesAccess(this.getPreferences()) + ); + } + + getPreferencesUser() { + return getActiveUser(this); + } +} diff --git a/packages/compass-preferences-model/src/preferences-schema.ts b/packages/compass-preferences-model/src/preferences-schema.ts index 60b5d900484..0241f44b48f 100644 --- a/packages/compass-preferences-model/src/preferences-schema.ts +++ b/packages/compass-preferences-model/src/preferences-schema.ts @@ -56,6 +56,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & | 'web-sandbox-atlas-local' | 'web-sandbox-atlas-dev' | 'web-sandbox-atlas'; + optInDataExplorerGenAIFeatures: boolean; // Features that are enabled by default in Compass, but are disabled in Data // Explorer enableExplainPlan: boolean; @@ -115,7 +116,9 @@ export type NonUserPreferences = { export type AllPreferences = UserPreferences & CliOnlyPreferences & NonUserPreferences & - PermanentFeatureFlags; + PermanentFeatureFlags & + AtlasProjectPreferences & + AtlasOrgPreferences; // Types related to PreferenceDefinition type PostProcessFunction = ( @@ -210,6 +213,15 @@ export type StoredPreferencesValidator = ReturnType< export type StoredPreferences = z.output; +export type AtlasProjectPreferences = { + enableGenAIFeaturesAtlasProject?: boolean; + enableGenAISampleDocumentPassingOnAtlasProject?: boolean; +}; + +export type AtlasOrgPreferences = { + enableGenAIFeaturesAtlasOrg?: boolean; +}; + // Preference definitions const featureFlagsProps: Required<{ [K in keyof FeatureFlags]: PreferenceDefinition; @@ -461,7 +473,10 @@ export const storedUserPreferencesProps: Required<{ short: 'Enable AI Features', long: 'Allow the use of AI features in Compass which make requests to 3rd party services.', }, - deriveValue: deriveNetworkTrafficOptionState('enableGenAIFeatures'), + deriveValue: deriveValueFromOtherPreferences('enableGenAIFeatures', [ + 'enableGenAIFeaturesAtlasOrg', + 'networkTraffic', + ]), validator: z.boolean().default(true), type: 'boolean', }, @@ -679,6 +694,16 @@ export const storedUserPreferencesProps: Required<{ .default('atlas'), type: 'string', }, + optInDataExplorerGenAIFeatures: { + ui: true, + cli: false, + global: false, + description: { + short: 'User Opt-in for Data Explorer Gen AI Features', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, enableAtlasSearchIndexes: { ui: true, @@ -1007,12 +1032,54 @@ const nonUserPreferences: Required<{ }, }; +const atlasProjectPreferencesProps: Required<{ + [K in keyof NonUserPreferences]: PreferenceDefinition; +}> = { + enableGenAIFeaturesAtlasProject: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Gen AI Features on Atlas Project Level', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + enableGenAISampleDocumentPassingOnAtlasProject: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Gen AI Sample Document Passing on Atlas Project Level', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, +}; + +const atlasOrgPreferencesProps: Required<{ + [K in keyof NonUserPreferences]: PreferenceDefinition; +}> = { + enableGenAIFeaturesAtlasOrg: { + ui: true, + cli: true, + global: true, + description: { + short: 'Enable Gen AI Features on Atlas Org Level', + }, + validator: z.boolean().default(true), + type: 'boolean', + }, +}; + export const allPreferencesProps: Required<{ [K in keyof AllPreferences]: PreferenceDefinition; }> = { ...storedUserPreferencesProps, ...cliOnlyPreferencesProps, ...nonUserPreferences, + ...atlasProjectPreferencesProps, + ...atlasOrgPreferencesProps, }; /** Helper for defining how to derive value/state for networkTraffic-affected preferences */ @@ -1027,6 +1094,22 @@ function deriveNetworkTrafficOptionState( }); } +/** Helper for deriving value/state for preferences from other preferences */ +function deriveValueFromOtherPreferences( + property: K, + preferencesToDeriveFrom: K[] +): DeriveValueFunction { + return (v, s) => ({ + value: v(property) && preferencesToDeriveFrom.every((p) => v(p)), + state: + s(property) ?? + (preferencesToDeriveFrom.every((p) => v(p)) + ? preferencesToDeriveFrom.map((p) => s(p)).filter(Boolean)?.[0] ?? + 'derived' + : undefined), + }); +} + /** Helper for defining how to derive value/state for feature-restricting preferences */ function deriveFeatureRestrictingOptionsState( property: K diff --git a/packages/compass-preferences-model/src/provider.ts b/packages/compass-preferences-model/src/provider.ts index b5f3bfc92dc..ef2fa77b65c 100644 --- a/packages/compass-preferences-model/src/provider.ts +++ b/packages/compass-preferences-model/src/provider.ts @@ -1,5 +1,6 @@ export * from './react'; export { ReadOnlyPreferenceAccess } from './read-only-preferences-access'; +export { CompassWebPreferencesAccess } from './compass-web-preferences-access'; export { useIsAIFeatureEnabled, isAIFeatureEnabled,