Skip to content

Commit

Permalink
feat(compass-preferences-model): add preference for enabling ai featu…
Browse files Browse the repository at this point in the history
  • Loading branch information
Anemy authored Sep 18, 2023
1 parent da59aa9 commit a48aee7
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 29 deletions.
5 changes: 4 additions & 1 deletion packages/atlas-service/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export async function throwIfNotOk(
}

function throwIfAINotEnabled(atlasService: typeof AtlasService) {
if (!preferences.getPreferences().cloudFeatureRolloutAccess?.GEN_AI_COMPASS) {
if (
!preferences.getPreferences().cloudFeatureRolloutAccess?.GEN_AI_COMPASS ||
!preferences.getPreferences().enableAIFeatures
) {
throw new Error(
"Compass' AI functionality is not currently enabled. Please try again later."
);
Expand Down
3 changes: 3 additions & 0 deletions packages/compass-preferences-model/src/preferences.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ describe('Preferences class', function () {
enableDevTools: 'set-global',
networkTraffic: 'set-global',
trackUsageStatistics: 'set-global',
enableAIFeatures: 'set-global',
enableMaps: 'set-cli',
enableShell: 'set-cli',
readOnly: 'set-global',
Expand Down Expand Up @@ -214,6 +215,7 @@ describe('Preferences class', function () {
},
{
networkTraffic: false,
enableAIFeatures: false,
enableMaps: false,
enableFeedbackPanel: false,
trackUsageStatistics: false,
Expand Down Expand Up @@ -246,6 +248,7 @@ describe('Preferences class', function () {

const states = preferences.getPreferenceStates();
expect(states).to.deep.equal({
enableAIFeatures: 'hardcoded',
enableDevTools: 'set-global',
enableMaps: 'set-cli',
enableFeedbackPanel: 'hardcoded',
Expand Down
15 changes: 14 additions & 1 deletion packages/compass-preferences-model/src/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags &
FeatureFlags & {
// User-facing preferences
autoUpdates: boolean;
enableAIFeatures: boolean;
enableMaps: boolean;
trackUsageStatistics: boolean;
enableFeedbackPanel: boolean;
Expand Down Expand Up @@ -429,6 +430,18 @@ export const storedUserPreferencesProps: Required<{
validator: z.boolean().default(false),
type: 'boolean',
},
enableAIFeatures: {
ui: true,
cli: true,
global: true,
description: {
short: 'Enable AI Features',
long: 'Allow the use of AI features in Compass which make requests to 3rd party services. These features are currently experimental and offered as a preview to only a limited number of users.',
},
deriveValue: deriveNetworkTrafficOptionState('enableAIFeatures'),
validator: z.boolean().default(true),
type: 'boolean',
},
/**
* Switch to enable/disable Intercom panel (renamed from `intercom`).
*/
Expand Down Expand Up @@ -617,7 +630,6 @@ export const storedUserPreferencesProps: Required<{
validator: z.boolean().default(false),
type: 'boolean',
},

/**
* Chooses atlas service backend configuration from preset
* - compas-dev: locally running compass kanopy backend (localhost)
Expand Down Expand Up @@ -1009,6 +1021,7 @@ export class Preferences {
if (!showedNetworkOptIn) {
await this.savePreferences({
autoUpdates: true,
enableAIFeatures: true,
enableMaps: true,
trackUsageStatistics: true,
enableFeedbackPanel: true,
Expand Down
3 changes: 2 additions & 1 deletion packages/compass-preferences-model/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export function useIsAIFeatureEnabled(React: ReactHooks) {
'cloudFeatureRolloutAccess',
React
)?.GEN_AI_COMPASS;
const enableAIFeatures = usePreference('enableAIFeatures', React);

return enableAIExperience && isAIFeatureEnabled;
return enableAIExperience && isAIFeatureEnabled && enableAIFeatures;
}
30 changes: 29 additions & 1 deletion packages/compass-query-bar/src/components/query-bar.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ describe('QueryBar Component', function () {
sandbox = sinon.createSandbox();
sandbox.stub(preferencesAccess, 'getPreferences').returns({
enableAIExperience: true,
enableAIFeatures: true,
cloudFeatureRolloutAccess: {
GEN_AI_COMPASS: true,
},
Expand Down Expand Up @@ -171,13 +172,40 @@ describe('QueryBar Component', function () {
});
});

describe('with ai disabled', function () {
describe('with enableAIExperience ai disabled', function () {
let sandbox: sinon.SinonSandbox;

beforeEach(function () {
sandbox = sinon.createSandbox();
sandbox.stub(preferencesAccess, 'getPreferences').returns({
enableAIExperience: false,
enableAIFeatures: true,
cloudFeatureRolloutAccess: {
GEN_AI_COMPASS: true,
},
} as any);
renderQueryBar({
queryOptionsLayout: ['filter'],
});
});

afterEach(function () {
return sandbox.restore();
});

it('does not render the ask ai button', function () {
expect(screen.queryByText('Ask AI')).to.not.exist;
});
});

describe('with enableAIFeatures ai disabled', function () {
let sandbox: sinon.SinonSandbox;

beforeEach(function () {
sandbox = sinon.createSandbox();
sandbox.stub(preferencesAccess, 'getPreferences').returns({
enableAIExperience: true,
enableAIFeatures: false,
cloudFeatureRolloutAccess: {
GEN_AI_COMPASS: true,
},
Expand Down
68 changes: 47 additions & 21 deletions packages/compass-settings/src/components/settings/privacy.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ import { PrivacySettings } from './privacy';
import { configureStore } from '../../stores';
import { fetchSettings } from '../../stores/settings';

function renderPrivacySettings(
store,
props: Partial<React.ComponentProps<typeof PrivacySettings>> = {}
) {
const component = () => (
<Provider store={store}>
<PrivacySettings {...props} />
</Provider>
);
render(component());
return screen.getByTestId('privacy-settings');
}

describe('PrivacySettings', function () {
let container: HTMLElement;
let store: ReturnType<typeof configureStore>;
Expand All @@ -18,35 +31,48 @@ describe('PrivacySettings', function () {
beforeEach(async function () {
store = configureStore();
await store.dispatch(fetchSettings());
const component = () => (
<Provider store={store}>
<PrivacySettings />
</Provider>
);
render(component());
container = screen.getByTestId('privacy-settings');
});

afterEach(function () {
cleanup();
});

[
'autoUpdates',
'enableMaps',
'trackUsageStatistics',
'enableFeedbackPanel',
].forEach((option) => {
it(`renders ${option}`, function () {
expect(within(container).getByTestId(option)).to.exist;
describe('when rendered', function () {
beforeEach(function () {
container = renderPrivacySettings(store);
});
it(`changes ${option} value when option is clicked`, function () {
const checkbox = within(container).getByTestId(option);
const initialValue = getSettings()[option];
userEvent.click(checkbox, undefined, {
skipPointerEventsCheck: true,

[
'autoUpdates',
'enableMaps',
'trackUsageStatistics',
'enableFeedbackPanel',
].forEach((option) => {
it(`renders ${option}`, function () {
expect(within(container).getByTestId(option)).to.exist;
});
it(`changes ${option} value when option is clicked`, function () {
const checkbox = within(container).getByTestId(option);
const initialValue = getSettings()[option];
userEvent.click(checkbox, undefined, {
skipPointerEventsCheck: true,
});
expect(getSettings()).to.have.property(option, !initialValue);
});
expect(getSettings()).to.have.property(option, !initialValue);
});
});

it('does not render enableAIFeatures when isAIFeatureRolledOutToUser is false', function () {
container = renderPrivacySettings(store, {
isAIFeatureRolledOutToUser: false,
});
expect(within(container).queryByTestId('enableAIFeatures')).to.not.exist;
});

it('renders enableAIFeatures when GisAIFeatureRolledOutToUser is true', function () {
container = renderPrivacySettings(store, {
isAIFeatureRolledOutToUser: true,
});
expect(within(container).getByTestId('enableAIFeatures')).to.be.visible;
});
});
38 changes: 34 additions & 4 deletions packages/compass-settings/src/components/settings/privacy.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Link } from '@mongodb-js/compass-components';
import { connect } from 'react-redux';
import type { UserPreferences } from 'compass-preferences-model';
import { withPreferences } from 'compass-preferences-model';

import SettingsList from './settings-list';
import type { RootState } from '../../stores';

const privacyFields = [
'autoUpdates',
'enableMaps',
'enableAIFeatures',
'trackUsageStatistics',
'enableFeedbackPanel',
] as const;

export const PrivacySettings: React.FunctionComponent = () => {
export const PrivacySettings: React.FunctionComponent<{
isAIFeatureRolledOutToUser?: boolean;
}> = ({ isAIFeatureRolledOutToUser }) => {
const privacyFieldsShown = useMemo(() => {
return isAIFeatureRolledOutToUser
? privacyFields
: privacyFields.filter((field) => field !== 'enableAIFeatures');
}, [isAIFeatureRolledOutToUser]);

return (
<div data-testid="privacy-settings">
<div>
To enhance the user experience, Compass can integrate with 3rd party
services, which requires external network requests. Please choose from
the settings below:
</div>
<SettingsList fields={privacyFields} />
<SettingsList fields={privacyFieldsShown} />
<div>
With any of these options, none of your personal information or stored
data will be submitted.
Expand All @@ -31,4 +45,20 @@ export const PrivacySettings: React.FunctionComponent = () => {
);
};

export default PrivacySettings;
export default withPreferences(
connect(
(
state: RootState,
ownProps: {
cloudFeatureRolloutAccess?: UserPreferences['cloudFeatureRolloutAccess'];
}
) => {
return {
isAIFeatureRolledOutToUser:
ownProps.cloudFeatureRolloutAccess?.GEN_AI_COMPASS,
};
}
)(PrivacySettings),
['cloudFeatureRolloutAccess'],
React
);

0 comments on commit a48aee7

Please sign in to comment.