From 8a5885c1fe28baad3b6d1e32b9763113c1ce22a9 Mon Sep 17 00:00:00 2001 From: Maurizio Casimirri Date: Mon, 27 Nov 2023 22:00:31 +0100 Subject: [PATCH] chore(connection-form): split connection form from compass preferences (#5162) --- package-lock.json | 2 - .../src/components/connections.tsx | 45 +++++++++++- .../src/stores/connections-store.ts | 4 ++ packages/connection-form/package.json | 1 - .../authentication-gssapi.spec.tsx | 29 +++----- .../authentication-gssapi.tsx | 7 +- .../authentication-oidc.spec.tsx | 31 +++------ .../authentication-oidc.tsx | 7 +- .../authentication-tab/authentication-tab.tsx | 4 +- .../csfle-tab/csfle-tab.tsx | 7 +- .../src/components/connection-form.spec.tsx | 68 ++++++++----------- .../src/components/connection-form.tsx | 36 ++++++++-- .../hooks/use-connect-form-preferences.tsx | 35 ++++++++++ .../src/hooks/use-connect-form.ts | 27 +++++--- .../utils/force-connection-options.spec.ts | 56 +++++++-------- .../src/utils/force-connection-options.ts | 32 ++++----- .../src/utils/oidc-handler.spec.ts | 31 ++------- .../connection-form/src/utils/oidc-handler.ts | 14 ++-- 18 files changed, 246 insertions(+), 190 deletions(-) create mode 100644 packages/connection-form/src/hooks/use-connect-form-preferences.tsx diff --git a/package-lock.json b/package-lock.json index c79a5cf6cb2..f0db62da074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47872,7 +47872,6 @@ "@mongodb-js/compass-editor": "^0.18.0", "@mongodb-js/connection-storage": "^0.6.6", "@testing-library/react-hooks": "^7.0.2", - "compass-preferences-model": "^2.15.6", "lodash": "^4.17.21", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", @@ -61009,7 +61008,6 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.2.0", "chai": "^4.3.4", - "compass-preferences-model": "^2.15.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", "lodash": "^4.17.21", diff --git a/packages/compass-connections/src/components/connections.tsx b/packages/compass-connections/src/components/connections.tsx index facda69124d..1ad5effe832 100644 --- a/packages/compass-connections/src/components/connections.tsx +++ b/packages/compass-connections/src/components/connections.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { ImportConnectionsModal, ExportConnectionsModal, @@ -23,6 +23,7 @@ import { useConnections } from '../stores/connections-store'; import { cloneDeep } from 'lodash'; import ConnectionList from './connection-list/connection-list'; import { LegacyConnectionsModal } from './legacy-connections-modal'; +import { usePreference } from 'compass-preferences-model'; const { log, mongoLogId } = createLoggerAndTelemetry( 'mongodb-compass:connections:connections' @@ -123,6 +124,47 @@ function Connections({ [] ); + const protectConnectionStrings = usePreference( + 'protectConnectionStrings', + React + ); + const forceConnectionOptions = usePreference('forceConnectionOptions', React); + const showKerberosPasswordField = usePreference( + 'showKerberosPasswordField', + React + ); + const showOIDCDeviceAuthFlow = usePreference('showOIDCDeviceAuthFlow', React); + const enableOidc = usePreference('enableOidc', React); + const enableDebugUseCsfleSchemaMap = usePreference( + 'enableDebugUseCsfleSchemaMap', + React + ); + const protectConnectionStringsForNewConnections = usePreference( + 'protectConnectionStringsForNewConnections', + React + ); + + const preferences = useMemo( + () => ({ + protectConnectionStrings, + forceConnectionOptions, + showKerberosPasswordField, + showOIDCDeviceAuthFlow, + enableOidc, + enableDebugUseCsfleSchemaMap, + protectConnectionStringsForNewConnections, + }), + [ + protectConnectionStrings, + forceConnectionOptions, + showKerberosPasswordField, + showOIDCDeviceAuthFlow, + enableOidc, + enableDebugUseCsfleSchemaMap, + protectConnectionStringsForNewConnections, + ] + ); + return (
@@ -166,6 +208,7 @@ function Connections({ onSaveConnectionClicked={saveConnection} initialConnectionInfo={activeConnectionInfo} connectionErrorMessage={connectionErrorMessage} + preferences={preferences} /> diff --git a/packages/compass-connections/src/stores/connections-store.ts b/packages/compass-connections/src/stores/connections-store.ts index d424043c0d3..3f3212b0b8a 100644 --- a/packages/compass-connections/src/stores/connections-store.ts +++ b/packages/compass-connections/src/stores/connections-store.ts @@ -541,11 +541,15 @@ export function useConnections({ }; } + const { forceConnectionOptions = [], browserCommandForOIDCAuth } = + preferences.getPreferences(); + const newConnectionDataService = await newConnectionAttempt.connect( adjustConnectionOptionsBeforeConnect({ connectionOptions: connectionInfo.connectionOptions, defaultAppName: appName, notifyDeviceFlow, + preferences: { forceConnectionOptions, browserCommandForOIDCAuth }, }) ); connectingConnectionAttempt.current = undefined; diff --git a/packages/connection-form/package.json b/packages/connection-form/package.json index 498a7edbcea..4ee8f2794ff 100644 --- a/packages/connection-form/package.json +++ b/packages/connection-form/package.json @@ -56,7 +56,6 @@ "@mongodb-js/compass-editor": "^0.18.0", "@mongodb-js/connection-storage": "^0.6.6", "@testing-library/react-hooks": "^7.0.2", - "compass-preferences-model": "^2.15.6", "lodash": "^4.17.21", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.spec.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.spec.tsx index 36736c14b02..ffbe3284c12 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.spec.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.spec.tsx @@ -3,11 +3,11 @@ import { render, screen, fireEvent, cleanup } from '@testing-library/react'; import { expect } from 'chai'; import sinon from 'sinon'; import ConnectionStringUrl from 'mongodb-connection-string-url'; -import preferences from 'compass-preferences-model'; import AuthenticationGssapi from './authentication-gssapi'; import type { ConnectionFormError } from '../../../utils/validation'; import type { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; +import { ConnectionFormPreferencesContext } from '../../../hooks/use-connect-form-preferences'; function renderComponent({ errors = [], @@ -19,11 +19,15 @@ function renderComponent({ updateConnectionFormField: UpdateConnectionFormField; }) { render( - + + + ); } @@ -172,19 +176,6 @@ describe('AuthenticationGssapi Component', function () { }); describe('Kerberos password support', function () { - let sandbox: sinon.SinonSandbox; - - before(function () { - sandbox = sinon.createSandbox(); - sandbox - .stub(preferences, 'getPreferences') - .returns({ showKerberosPasswordField: true } as any); - }); - - after(function () { - return sandbox.restore(); - }); - describe('when password is not in the connection string', function () { beforeEach(function () { renderComponent({ diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.tsx index 8c841090224..88c9feecb44 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-gssapi.tsx @@ -7,7 +7,6 @@ import { RadioBox, Checkbox, } from '@mongodb-js/compass-components'; -import { usePreference } from 'compass-preferences-model'; import type ConnectionStringUrl from 'mongodb-connection-string-url'; import type { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; @@ -18,6 +17,7 @@ import { getConnectionStringUsername, parseAuthMechanismProperties, } from '../../../utils/connection-string-helpers'; +import { useConnectionFormPreference } from '../../../hooks/use-connect-form-preferences'; const GSSAPI_CANONICALIZE_HOST_NAME_OPTIONS: Record< string, @@ -56,9 +56,8 @@ function AuthenticationGSSAPI({ const [showPassword, setShowPassword] = useState(false); - const showKerberosPasswordField = !!usePreference( - 'showKerberosPasswordField', - React + const showKerberosPasswordField = !!useConnectionFormPreference( + 'showKerberosPasswordField' ); useEffect(() => { diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.spec.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.spec.tsx index 9c5c55173e9..b8c93f646e2 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.spec.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.spec.tsx @@ -9,13 +9,15 @@ import { import { expect } from 'chai'; import sinon from 'sinon'; import type { ConnectionOptions } from 'mongodb-data-service'; -import preferences from 'compass-preferences-model'; import ConnectionForm from '../../../'; const deviceAuthFlowText = 'Enable Device Authentication Flow'; -async function renderConnectionForm(connectSpy) { +async function renderConnectionForm( + connectSpy, + { showOIDCDeviceAuthFlow }: { showOIDCDeviceAuthFlow: boolean } +) { render( { connectSpy(connectionInfo.connectionOptions); }} + preferences={{ enableOidc: true, showOIDCDeviceAuthFlow }} /> ); @@ -55,23 +58,10 @@ const openOptionsAccordion = () => describe('AuthenticationOIDC Connection Form', function () { let expectToConnectWith; - let sandbox: sinon.SinonSandbox; - let getPreferencesStub: sinon.SinonStub; let connectSpy: sinon.SinonSpy; beforeEach(function () { connectSpy = sinon.spy(); - - sandbox = sinon.createSandbox(); - getPreferencesStub = sinon.stub(); - getPreferencesStub.returns({ - enableOidc: true, - }); - // TODO(COMPASS-6803): Remove feature flag, remove this sandbox. - sandbox - .stub(preferences, 'getPreferences') - .callsFake(() => getPreferencesStub()); - expectToConnectWith = async ( expected: ConnectionOptions | ((ConnectionOptions) => void) ): Promise => { @@ -93,14 +83,14 @@ describe('AuthenticationOIDC Connection Form', function () { } }; }); + afterEach(function () { - sandbox.restore(); cleanup(); }); describe('when rendered', function () { beforeEach(async function () { - await renderConnectionForm(connectSpy); + await renderConnectionForm(connectSpy, { showOIDCDeviceAuthFlow: false }); }); it('handles principal (username) changes', async function () { @@ -156,12 +146,7 @@ describe('AuthenticationOIDC Connection Form', function () { describe('when rendered and the showOIDCDeviceAuthFlow setting is enabled', function () { beforeEach(async function () { - getPreferencesStub.returns({ - enableOidc: true, - showOIDCDeviceAuthFlow: true, - }); - - await renderConnectionForm(connectSpy); + await renderConnectionForm(connectSpy, { showOIDCDeviceAuthFlow: true }); }); it('handles the enable device authentication flow checkbox', async function () { diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.tsx index 318f11f07a2..9ac91596774 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-oidc.tsx @@ -9,13 +9,13 @@ import { } from '@mongodb-js/compass-components'; import type ConnectionStringUrl from 'mongodb-connection-string-url'; import type { ConnectionOptions } from 'mongodb-data-service'; -import { usePreference } from 'compass-preferences-model'; import type { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; import type { ConnectionFormError } from '../../../utils/validation'; import { errorMessageByFieldName } from '../../../utils/validation'; import { getConnectionStringUsername } from '../../../utils/connection-string-helpers'; import type { OIDCOptions } from '../../../utils/oidc-handler'; +import { useConnectionFormPreference } from '../../../hooks/use-connect-form-preferences'; type AuthFlowType = NonNullable[number]; @@ -47,9 +47,8 @@ function AuthenticationOIDC({ const hasEnabledDeviceAuthFlow = !!connectionOptions.oidc?.allowedFlows?.includes?.('device-auth'); - const showOIDCDeviceAuthFlow = !!usePreference( - 'showOIDCDeviceAuthFlow', - React + const showOIDCDeviceAuthFlow = !!useConnectionFormPreference( + 'showOIDCDeviceAuthFlow' ); return ( diff --git a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-tab.tsx b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-tab.tsx index 9cd54f5f37b..3e321ac85e1 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-tab.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/authentication-tab/authentication-tab.tsx @@ -9,7 +9,6 @@ import { } from '@mongodb-js/compass-components'; import type ConnectionStringUrl from 'mongodb-connection-string-url'; import { AuthMechanism } from 'mongodb'; -import { usePreference } from 'compass-preferences-model'; import type { ConnectionOptions } from 'mongodb-data-service'; import type { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; @@ -21,6 +20,7 @@ import AuthenticationGSSAPI from './authentication-gssapi'; import AuthenticationPlain from './authentication-plain'; import AuthenticationAWS from './authentication-aws'; import AuthenticationOidc from './authentication-oidc'; +import { useConnectionFormPreference } from '../../../hooks/use-connect-form-preferences'; type AUTH_TABS = | 'DEFAULT' // Username/Password (SCRAM-SHA-1 + SCRAM-SHA-256 + DEFAULT) @@ -108,7 +108,7 @@ function AuthenticationTab({ updateConnectionFormField: UpdateConnectionFormField; connectionOptions: ConnectionOptions; }): React.ReactElement { - const enableOIDC = !!usePreference('enableOidc', React); + const enableOIDC = !!useConnectionFormPreference('enableOidc'); const enabledAuthOptions = useMemo(() => { if (enableOIDC) { return options; diff --git a/packages/connection-form/src/components/advanced-options-tabs/csfle-tab/csfle-tab.tsx b/packages/connection-form/src/components/advanced-options-tabs/csfle-tab/csfle-tab.tsx index 39b4e59c3b3..bd02f2eec32 100644 --- a/packages/connection-form/src/components/advanced-options-tabs/csfle-tab/csfle-tab.tsx +++ b/packages/connection-form/src/components/advanced-options-tabs/csfle-tab/csfle-tab.tsx @@ -32,7 +32,7 @@ import type { KMSField, } from '../../../utils/csfle-kms-fields'; import { KMSProviderFields } from '../../../utils/csfle-kms-fields'; -import { usePreference } from 'compass-preferences-model'; +import { useConnectionFormPreference } from '../../../hooks/use-connect-form-preferences'; const kmsProviderComponentWrapperStyles = css({ paddingLeft: spacing[3], @@ -96,9 +96,8 @@ function CSFLETab({ const autoEncryptionOptions = connectionOptions.fleOptions?.autoEncryption ?? {}; - const enableSchemaMapDebugFlag = usePreference( - 'enableDebugUseCsfleSchemaMap', - React + const enableSchemaMapDebugFlag = useConnectionFormPreference( + 'enableDebugUseCsfleSchemaMap' ); const errors = errorsByFieldTab(errors_, 'csfle'); diff --git a/packages/connection-form/src/components/connection-form.spec.tsx b/packages/connection-form/src/components/connection-form.spec.tsx index 6ee691b2b6f..ab0c6a666bd 100644 --- a/packages/connection-form/src/components/connection-form.spec.tsx +++ b/packages/connection-form/src/components/connection-form.spec.tsx @@ -11,7 +11,6 @@ import { expect } from 'chai'; import ConnectionForm from './connection-form'; import type { ConnectionFormProps } from './connection-form'; import Sinon from 'sinon'; -import preferences from 'compass-preferences-model'; import { defaultConnectionString } from '../constants/default-connection'; import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; @@ -82,14 +81,14 @@ describe('ConnectionForm Component', function () { 'and preferences.protectConnectionStrings === false', function () { it('should render the toggle button in the off state for default connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + initialConnectionInfo: DEFAULT_CONNECTION, + preferences: { protectConnectionStringsForNewConnections: true, protectConnectionStrings: false, - } as any; + }, }); - renderForm({ initialConnectionInfo: DEFAULT_CONNECTION }); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -100,14 +99,12 @@ describe('ConnectionForm Component', function () { }); it('should render the toggle button in the off state for existing connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + preferences: { protectConnectionStringsForNewConnections: true, protectConnectionStrings: false, - } as any; + }, }); - - renderForm(); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -123,14 +120,13 @@ describe('ConnectionForm Component', function () { 'and preferences.protectConnectionStrings === true', function () { it('should render the toggle button in the off state for default connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + initialConnectionInfo: DEFAULT_CONNECTION, + preferences: { protectConnectionStringsForNewConnections: true, protectConnectionStrings: true, - } as any; + }, }); - - renderForm({ initialConnectionInfo: DEFAULT_CONNECTION }); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -141,14 +137,12 @@ describe('ConnectionForm Component', function () { }); it('should not render the toggle button for existing connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + preferences: { protectConnectionStringsForNewConnections: true, protectConnectionStrings: true, - } as any; + }, }); - - renderForm(); expect(screen.queryByTestId('toggle-edit-connection-string')).to .be.null; }); @@ -164,14 +158,13 @@ describe('ConnectionForm Component', function () { 'and preferences.protectConnectionStrings === false', function () { it('should render the toggle button in the on state for default connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + initialConnectionInfo: DEFAULT_CONNECTION, + preferences: { protectConnectionStringsForNewConnections: false, protectConnectionStrings: false, - } as any; + }, }); - - renderForm({ initialConnectionInfo: DEFAULT_CONNECTION }); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -182,14 +175,12 @@ describe('ConnectionForm Component', function () { }); it('should render the toggle button in the off state for existing connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + preferences: { protectConnectionStringsForNewConnections: false, protectConnectionStrings: false, - } as any; + }, }); - - renderForm(); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -205,14 +196,13 @@ describe('ConnectionForm Component', function () { 'and preferences.protectConnectionStrings === true', function () { it('should render the toggle button in the on state for default connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + initialConnectionInfo: DEFAULT_CONNECTION, + preferences: { protectConnectionStringsForNewConnections: false, protectConnectionStrings: true, - } as any; + }, }); - - renderForm({ initialConnectionInfo: DEFAULT_CONNECTION }); expect(screen.queryByTestId('toggle-edit-connection-string')).to .not.be.null; expect( @@ -223,14 +213,12 @@ describe('ConnectionForm Component', function () { }); it('should not render the toggle button for existing connection', function () { - sandbox.stub(preferences, 'getPreferences').callsFake(() => { - return { + renderForm({ + preferences: { protectConnectionStringsForNewConnections: false, protectConnectionStrings: true, - } as any; + }, }); - - renderForm(); expect(screen.queryByTestId('toggle-edit-connection-string')).to .be.null; }); diff --git a/packages/connection-form/src/components/connection-form.tsx b/packages/connection-form/src/components/connection-form.tsx index 2b219aded14..31bcaf7cb14 100644 --- a/packages/connection-form/src/components/connection-form.tsx +++ b/packages/connection-form/src/components/connection-form.tsx @@ -28,7 +28,11 @@ import ConnectionFormActions from './connection-form-actions'; import { useConnectForm } from '../hooks/use-connect-form'; import { validateConnectionOptionsErrors } from '../utils/validation'; import SaveConnectionModal from './save-connection-modal'; -import { usePreference } from 'compass-preferences-model'; +import type { ConnectionFormPreferences } from '../hooks/use-connect-form-preferences'; +import { + ConnectionFormPreferencesContext, + useConnectionFormPreference, +} from '../hooks/use-connect-form-preferences'; const formContainerStyles = css({ margin: 0, @@ -116,7 +120,7 @@ const connectionStringErrorStyles = css({ marginBottom: spacing[3], }); -export type ConnectionFormProps = { +type ConnectionFormPropsWithoutPreferences = { darkMode?: boolean; initialConnectionInfo: ConnectionInfo; connectionErrorMessage?: string | null; @@ -124,6 +128,10 @@ export type ConnectionFormProps = { onSaveConnectionClicked?: (connectionInfo: ConnectionInfo) => Promise; }; +export type ConnectionFormProps = ConnectionFormPropsWithoutPreferences & { + preferences?: Partial; +}; + function ConnectionForm({ initialConnectionInfo, connectionErrorMessage, @@ -131,7 +139,7 @@ function ConnectionForm({ // The connect form will not always used in an environment where // the connection info can be saved. onSaveConnectionClicked, -}: ConnectionFormProps): React.ReactElement { +}: ConnectionFormPropsWithoutPreferences): React.ReactElement { const darkMode = useDarkMode(); const [ @@ -151,12 +159,14 @@ function ConnectionForm({ const [saveConnectionModal, setSaveConnectionModal] = useState('hidden'); const protectConnectionStrings = - !!usePreference('protectConnectionStrings', React) && + !!useConnectionFormPreference('protectConnectionStrings') && !allowEditingIfProtected; const enableEditingConnectionString = _enableEditingConnectionString && !protectConnectionStrings; - const forceConnectionOptions = usePreference('forceConnectionOptions', React); + const forceConnectionOptions = useConnectionFormPreference( + 'forceConnectionOptions' + ); const warnings = useMemo(() => { if (!forceConnectionOptions?.length) return _warnings; const overriddenKeys = forceConnectionOptions.map(([key]) => key); @@ -368,4 +378,18 @@ function ConnectionForm({ ); } -export default ConnectionForm; +const ConnectionFormWithPreferences = ( + props: ConnectionFormPropsWithoutPreferences & { + preferences?: Partial; + } +) => { + const { preferences, ...rest } = props; + + return ( + + + + ); +}; + +export default ConnectionFormWithPreferences; diff --git a/packages/connection-form/src/hooks/use-connect-form-preferences.tsx b/packages/connection-form/src/hooks/use-connect-form-preferences.tsx new file mode 100644 index 00000000000..6503b61345f --- /dev/null +++ b/packages/connection-form/src/hooks/use-connect-form-preferences.tsx @@ -0,0 +1,35 @@ +import { createContext, useContext } from 'react'; + +export type ConnectionFormPreferences = { + protectConnectionStrings: boolean; + forceConnectionOptions: [key: string, value: string][]; + showKerberosPasswordField: boolean; + showOIDCDeviceAuthFlow: boolean; + enableOidc: boolean; + enableDebugUseCsfleSchemaMap: boolean; + protectConnectionStringsForNewConnections: boolean; +}; + +const defaultPreferences = { + protectConnectionStrings: false, + forceConnectionOptions: [], + showKerberosPasswordField: false, + showOIDCDeviceAuthFlow: false, + enableOidc: false, + enableDebugUseCsfleSchemaMap: false, + protectConnectionStringsForNewConnections: false, +}; + +export const ConnectionFormPreferencesContext = createContext< + Partial +>({}); + +export const useConnectionFormPreference = < + K extends keyof ConnectionFormPreferences +>( + preferenceKey: K +): ConnectionFormPreferences[K] => { + const preferences = useContext(ConnectionFormPreferencesContext); + + return preferences[preferenceKey] ?? defaultPreferences[preferenceKey]; +}; diff --git a/packages/connection-form/src/hooks/use-connect-form.ts b/packages/connection-form/src/hooks/use-connect-form.ts index c75a208b36f..5e711295e33 100644 --- a/packages/connection-form/src/hooks/use-connect-form.ts +++ b/packages/connection-form/src/hooks/use-connect-form.ts @@ -1,11 +1,9 @@ -import type { Dispatch } from 'react'; -import React, { useCallback, useEffect, useReducer } from 'react'; +import { useReducer, type Dispatch, useCallback, useEffect } from 'react'; import type { ConnectionOptions } from 'mongodb-data-service'; import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; import type { MongoClientOptions, ProxyOptions } from 'mongodb'; import { cloneDeep, isEqual } from 'lodash'; import type ConnectionStringUrl from 'mongodb-connection-string-url'; -import { usePreference } from 'compass-preferences-model'; import type { ConnectionFormError, @@ -62,6 +60,7 @@ import { import type { UpdateOIDCAction } from '../utils/oidc-handler'; import { setAppNameParamIfMissing } from '../utils/set-app-name-if-missing'; import { applyForceConnectionOptions } from '../utils/force-connection-options'; +import { useConnectionFormPreference } from './use-connect-form-preferences'; export interface ConnectFormState { connectionOptions: ConnectionOptions; @@ -577,8 +576,9 @@ export function useConnectForm( const initialFormState: ConnectFormState = { ...derivedFormState, enableEditingConnectionString: - !usePreference('protectConnectionStringsForNewConnections', React) && - derivedFormState.enableEditingConnectionString, + !useConnectionFormPreference( + 'protectConnectionStringsForNewConnections' + ) && derivedFormState.enableEditingConnectionString, }; const [state, dispatch] = useReducer(connectFormReducer, initialFormState); @@ -653,9 +653,8 @@ function setInitialState({ setErrors: (errors: ConnectionFormError[]) => void; dispatch: Dispatch; }) { - const protectConnectionStringsForNewConnections = usePreference( - 'protectConnectionStringsForNewConnections', - React + const protectConnectionStringsForNewConnections = useConnectionFormPreference( + 'protectConnectionStringsForNewConnections' ); useEffect(() => { // When the initial connection options change, like a different @@ -696,6 +695,7 @@ export function adjustConnectionOptionsBeforeConnect({ connectionOptions, defaultAppName, notifyDeviceFlow, + preferences, }: { connectionOptions: Readonly; defaultAppName?: string; @@ -703,14 +703,21 @@ export function adjustConnectionOptionsBeforeConnect({ verificationUrl: string; userCode: string; }) => void; + preferences: { + browserCommandForOIDCAuth?: string; + forceConnectionOptions: [string, string][]; + }; }): ConnectionOptions { const transformers: (( connectionOptions: Readonly ) => ConnectionOptions)[] = [ adjustCSFLEParams, setAppNameParamIfMissing(defaultAppName), - adjustOIDCConnectionOptionsBeforeConnect(notifyDeviceFlow), - applyForceConnectionOptions, + adjustOIDCConnectionOptionsBeforeConnect({ + browserCommandForOIDCAuth: preferences.browserCommandForOIDCAuth, + notifyDeviceFlow, + }), + applyForceConnectionOptions(preferences.forceConnectionOptions), ]; for (const transformer of transformers) { connectionOptions = transformer(connectionOptions); diff --git a/packages/connection-form/src/utils/force-connection-options.spec.ts b/packages/connection-form/src/utils/force-connection-options.spec.ts index 83925a31b51..8bfde33e17e 100644 --- a/packages/connection-form/src/utils/force-connection-options.spec.ts +++ b/packages/connection-form/src/utils/force-connection-options.spec.ts @@ -1,24 +1,17 @@ import { expect } from 'chai'; import { applyForceConnectionOptions } from './force-connection-options'; -import type { AllPreferences } from 'compass-preferences-model'; -import preferences from 'compass-preferences-model'; import sinon from 'sinon'; import type { ConnectionOptions } from 'mongodb-data-service'; describe('applyForceConnectionOptions', function () { let sandbox: sinon.SinonSandbox; let options: ConnectionOptions; - let prefs: Partial; beforeEach(function () { sandbox = sinon.createSandbox(); options = { connectionString: 'mongodb://localhost/', }; - prefs = {}; - sandbox - .stub(preferences, 'getPreferences') - .returns(prefs as AllPreferences); }); afterEach(function () { @@ -26,42 +19,48 @@ describe('applyForceConnectionOptions', function () { }); it('does not change options by default', function () { - expect(applyForceConnectionOptions(options)).to.deep.equal(options); + expect(applyForceConnectionOptions([])(options)).to.deep.equal(options); }); it('overrides set username and password', function () { options.connectionString = 'mongodb://a:b@localhost/'; - prefs.forceConnectionOptions = [ - ['username', 'user'], - ['password', 's€cr!t'], - ]; - expect(applyForceConnectionOptions(options)).to.deep.equal({ + + expect( + applyForceConnectionOptions([ + ['username', 'user'], + ['password', 's€cr!t'], + ])(options) + ).to.deep.equal({ connectionString: 'mongodb://user:s%E2%82%ACcr!t@localhost/', }); }); it('url-encodes username and password', function () { options.connectionString = 'mongodb://a:b@localhost/'; - prefs.forceConnectionOptions = [ - ['username', 'user'], - ['password', 's%22'], // this only makes a difference in already-url-encoded cases - ]; - expect(applyForceConnectionOptions(options)).to.deep.equal({ + expect( + applyForceConnectionOptions([ + ['username', 'user'], + ['password', 's%22'], // this only makes a difference in already-url-encoded cases + ])(options) + ).to.deep.equal({ connectionString: 'mongodb://user:s%2522@localhost/', }); }); it('can set connection string options', function () { - prefs.forceConnectionOptions = [['readPreference', 'secondary']]; - expect(applyForceConnectionOptions(options)).to.deep.equal({ + expect( + applyForceConnectionOptions([['readPreference', 'secondary']])(options) + ).to.deep.equal({ connectionString: 'mongodb://localhost/?readPreference=secondary', }); }); it('can override connection string options', function () { options.connectionString = 'mongodb://localhost/?readPreference=primary'; - prefs.forceConnectionOptions = [['readPreference', 'secondary']]; - expect(applyForceConnectionOptions(options)).to.deep.equal({ + + expect( + applyForceConnectionOptions([['readPreference', 'secondary']])(options) + ).to.deep.equal({ connectionString: 'mongodb://localhost/?readPreference=secondary', }); }); @@ -69,13 +68,14 @@ describe('applyForceConnectionOptions', function () { it('can override and set repeated connection string options', function () { options.connectionString = 'mongodb://localhost/?readPreference=primary&READPREFERENCETAGS=nodeType:NONE'; - prefs.forceConnectionOptions = [ - ['readPreference', 'secondary'], - ['readPreferenceTags', 'nodeType:ANALYTICS'], - ['readPreferenceTags', 'nodeType:READ_ONLY'], - ]; - expect(applyForceConnectionOptions(options)).to.deep.equal({ + expect( + applyForceConnectionOptions([ + ['readPreference', 'secondary'], + ['readPreferenceTags', 'nodeType:ANALYTICS'], + ['readPreferenceTags', 'nodeType:READ_ONLY'], + ])(options) + ).to.deep.equal({ connectionString: 'mongodb://localhost/?readPreference=secondary&readPreferenceTags=nodeType%3AANALYTICS&readPreferenceTags=nodeType%3AREAD_ONLY', }); diff --git a/packages/connection-form/src/utils/force-connection-options.ts b/packages/connection-form/src/utils/force-connection-options.ts index eedbf01f04f..6fc3baef9f6 100644 --- a/packages/connection-form/src/utils/force-connection-options.ts +++ b/packages/connection-form/src/utils/force-connection-options.ts @@ -1,4 +1,3 @@ -import preferences from 'compass-preferences-model'; import { ConnectionString } from 'mongodb-connection-string-url'; import type { ConnectionOptions } from 'mongodb-data-service'; @@ -7,22 +6,23 @@ function isSpecialKey(key: string): key is 'username' | 'password' { } export function applyForceConnectionOptions( - options: Readonly -): ConnectionOptions { - const url = new ConnectionString(options.connectionString); - const { forceConnectionOptions = [] } = preferences.getPreferences(); + forceConnectionOptions: [key: string, value: string][] = [] +) { + return (options: Readonly): ConnectionOptions => { + const url = new ConnectionString(options.connectionString); - for (const [key] of forceConnectionOptions) { - if (isSpecialKey(key)) continue; - url.searchParams.delete(key); - } - for (const [key, value] of forceConnectionOptions) { - if (isSpecialKey(key)) url[key] = encodeURIComponent(value); - else url.searchParams.append(key, value); - } + for (const [key] of forceConnectionOptions) { + if (isSpecialKey(key)) continue; + url.searchParams.delete(key); + } + for (const [key, value] of forceConnectionOptions) { + if (isSpecialKey(key)) url[key] = encodeURIComponent(value); + else url.searchParams.append(key, value); + } - return { - ...options, - connectionString: url.toString(), + return { + ...options, + connectionString: url.toString(), + }; }; } diff --git a/packages/connection-form/src/utils/oidc-handler.spec.ts b/packages/connection-form/src/utils/oidc-handler.spec.ts index a2212ea87b5..ea77eb90adc 100644 --- a/packages/connection-form/src/utils/oidc-handler.spec.ts +++ b/packages/connection-form/src/utils/oidc-handler.spec.ts @@ -1,6 +1,4 @@ import { expect } from 'chai'; -import sinon from 'sinon'; -import preferences from 'compass-preferences-model'; import { adjustOIDCConnectionOptionsBeforeConnect, @@ -68,9 +66,9 @@ describe('#adjustOIDCConnectionOptionsBeforeConnect', function () { it('returns oidc options with notify device flow when supplied', function () { const notifyDeviceFlowMock = () => {}; - const result = adjustOIDCConnectionOptionsBeforeConnect( - notifyDeviceFlowMock - )({ + const result = adjustOIDCConnectionOptionsBeforeConnect({ + notifyDeviceFlow: notifyDeviceFlowMock, + })({ connectionString: 'http://localhost:27017', useSystemCA: true, oidc: { @@ -89,7 +87,7 @@ describe('#adjustOIDCConnectionOptionsBeforeConnect', function () { }); it('returns oidc options without notify device flow when not supplied', function () { - const result = adjustOIDCConnectionOptionsBeforeConnect()({ + const result = adjustOIDCConnectionOptionsBeforeConnect({})({ connectionString: 'http://localhost:27017', useSystemCA: true, oidc: { @@ -107,27 +105,12 @@ describe('#adjustOIDCConnectionOptionsBeforeConnect', function () { }); describe('with the `browserCommandForOIDCAuth` preference set', function () { - let sandbox: sinon.SinonSandbox; - let getPreferencesStub: sinon.SinonStub; const mockBrowserCommand = '/usr/bin/browser'; - beforeEach(function () { - sandbox = sinon.createSandbox(); - getPreferencesStub = sinon.stub(); - getPreferencesStub.returns({ - browserCommandForOIDCAuth: mockBrowserCommand, - }); - sandbox - .stub(preferences, 'getPreferences') - .callsFake(() => getPreferencesStub()); - }); - - afterEach(function () { - sandbox.restore(); - }); - it('returns oidc options with the browser command from settings when set', function () { - const result = adjustOIDCConnectionOptionsBeforeConnect()({ + const result = adjustOIDCConnectionOptionsBeforeConnect({ + browserCommandForOIDCAuth: mockBrowserCommand, + })({ connectionString: 'http://localhost:27017', useSystemCA: true, oidc: { diff --git a/packages/connection-form/src/utils/oidc-handler.ts b/packages/connection-form/src/utils/oidc-handler.ts index 22bb226a94e..422d1b40b91 100644 --- a/packages/connection-form/src/utils/oidc-handler.ts +++ b/packages/connection-form/src/utils/oidc-handler.ts @@ -1,6 +1,5 @@ import type { ConnectionOptions } from 'mongodb-data-service'; import { cloneDeep } from 'lodash'; -import preferences from 'compass-preferences-model'; export type OIDCOptions = NonNullable; @@ -39,15 +38,18 @@ export function handleUpdateOIDCParam({ }; } -export function adjustOIDCConnectionOptionsBeforeConnect( +export function adjustOIDCConnectionOptionsBeforeConnect({ + browserCommandForOIDCAuth, + notifyDeviceFlow, +}: { + browserCommandForOIDCAuth?: string; notifyDeviceFlow?: (deviceFlowInformation: { verificationUrl: string; userCode: string; - }) => void -): (connectionOptions: Readonly) => ConnectionOptions { + }) => void; +}): (connectionOptions: Readonly) => ConnectionOptions { return (connectionOptions) => { - const browserCommand = - preferences.getPreferences().browserCommandForOIDCAuth; + const browserCommand = browserCommandForOIDCAuth; return { ...cloneDeep(connectionOptions),