diff --git a/package-lock.json b/package-lock.json index 5c416678b65..07e10125a23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8755,6 +8755,20 @@ "resolved": "packages/explain-plan-helper", "link": true }, + "node_modules/@mongodb-js/mdb-experiment-js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/mdb-experiment-js/-/mdb-experiment-js-1.9.0.tgz", + "integrity": "sha512-4JcsdyjmbUxzBRADGCPWH9ySif5nda7JW6wNChqd7gYagEK7+I76p24sd4rTo9Ub8+JVkNyfB+eeF9CHuM4y3Q==", + "license": "Apache-2.0", + "dependencies": { + "deepmerge": "^4.2.2", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + } + }, "node_modules/@mongodb-js/mocha-config-compass": { "resolved": "configs/mocha-config-compass", "link": true @@ -20359,6 +20373,15 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deepmerge-ts": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", @@ -47683,6 +47706,7 @@ "dependencies": { "@mongodb-js/compass-app-registry": "^9.4.18", "@mongodb-js/compass-logging": "^1.7.9", + "@mongodb-js/mdb-experiment-js": "1.9.0", "hadron-ipc": "^3.5.8", "react": "^17.0.2" }, @@ -59411,6 +59435,7 @@ "@mongodb-js/compass-app-registry": "^9.4.18", "@mongodb-js/compass-logging": "^1.7.9", "@mongodb-js/eslint-config-compass": "^1.4.5", + "@mongodb-js/mdb-experiment-js": "1.9.0", "@mongodb-js/mocha-config-compass": "^1.7.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/tsconfig-compass": "^1.2.9", @@ -60696,6 +60721,15 @@ } } }, + "@mongodb-js/mdb-experiment-js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/mdb-experiment-js/-/mdb-experiment-js-1.9.0.tgz", + "integrity": "sha512-4JcsdyjmbUxzBRADGCPWH9ySif5nda7JW6wNChqd7gYagEK7+I76p24sd4rTo9Ub8+JVkNyfB+eeF9CHuM4y3Q==", + "requires": { + "deepmerge": "^4.2.2", + "use-sync-external-store": "^1.2.0" + } + }, "@mongodb-js/mocha-config-compass": { "version": "file:configs/mocha-config-compass", "requires": { @@ -70625,6 +70659,11 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, "deepmerge-ts": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", diff --git a/packages/compass-telemetry/.eslintrc.js b/packages/compass-telemetry/.eslintrc.js index e4cf824b6ac..49d6d33c515 100644 --- a/packages/compass-telemetry/.eslintrc.js +++ b/packages/compass-telemetry/.eslintrc.js @@ -5,4 +5,26 @@ module.exports = { tsconfigRootDir: __dirname, project: ['./tsconfig-lint.json'], }, + overrides: [ + { + files: ['./src/**/*.ts', './src/**/*.tsx'], + rules: { + 'no-restricted-imports': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { + name: '@mongodb-js/mdb-experiment-js', + message: + 'Use type-only imports from @mongodb-js/mdb-experiment-js', + allowTypeImports: true, + }, + ], + }, + ], + '@typescript-eslint/no-redundant-type-constituents': 'off', + }, + }, + ], }; diff --git a/packages/compass-telemetry/package.json b/packages/compass-telemetry/package.json index 0851b88b0cd..725f5717981 100644 --- a/packages/compass-telemetry/package.json +++ b/packages/compass-telemetry/package.json @@ -55,7 +55,8 @@ "@mongodb-js/compass-logging": "^1.7.9", "@mongodb-js/compass-app-registry": "^9.4.18", "hadron-ipc": "^3.5.8", - "react": "^17.0.2" + "react": "^17.0.2", + "@mongodb-js/mdb-experiment-js": "1.9.0" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.4.5", diff --git a/packages/compass-telemetry/src/experimentation-provider.tsx b/packages/compass-telemetry/src/experimentation-provider.tsx new file mode 100644 index 00000000000..6609565fa0a --- /dev/null +++ b/packages/compass-telemetry/src/experimentation-provider.tsx @@ -0,0 +1,62 @@ +import React, { createContext, useContext, useRef } from 'react'; +import type { types } from '@mongodb-js/mdb-experiment-js'; +import type { typesReact } from '@mongodb-js/mdb-experiment-js/react'; + +type UseAssignmentHook = ( + experimentName: string, + trackIsInSample: boolean, + options?: typesReact.UseAssignmentOptions +) => typesReact.UseAssignmentResponse; + +type AssignExperimentFn = ( + experimentName: string, + options?: types.AssignOptions +) => Promise; + +interface CompassExperimentationProviderContextValue { + useAssignment: UseAssignmentHook; + assignExperiment: AssignExperimentFn; +} + +const initialContext: CompassExperimentationProviderContextValue = { + useAssignment() { + return { + assignment: null, + asyncStatus: null, + error: null, + isLoading: false, + isError: false, + isSuccess: true, + }; + }, + assignExperiment() { + return Promise.resolve(null); + }, +}; + +const ExperimentationContext = + createContext(initialContext); + +// Provider component that accepts MMS experiment utils as props +export const CompassExperimentationProvider: React.FC<{ + children: React.ReactNode; + useAssignment: UseAssignmentHook; + assignExperiment: AssignExperimentFn; +}> = ({ children, useAssignment, assignExperiment }) => { + // Use useRef to keep the functions up-to-date; Use mutation pattern to maintain the + // same object reference to prevent unnecessary re-renders of consuming components + const { current: contextValue } = useRef({ useAssignment, assignExperiment }); + contextValue.useAssignment = useAssignment; + contextValue.assignExperiment = assignExperiment; + + return ( + + {children} + + ); +}; + +// Hook for components to access experiment assignment +export const useAssignment = (...args: Parameters) => { + return useContext(ExperimentationContext).useAssignment(...args); +}; diff --git a/packages/compass-telemetry/src/index.ts b/packages/compass-telemetry/src/index.ts index a2e155c8073..e7cadf9d208 100644 --- a/packages/compass-telemetry/src/index.ts +++ b/packages/compass-telemetry/src/index.ts @@ -5,3 +5,5 @@ export type { IdentifyTraits, ExtraConnectionData, } from './types'; + +export { CompassExperimentationProvider } from './experimentation-provider'; diff --git a/packages/compass-web/src/index.tsx b/packages/compass-web/src/index.tsx index 2c6525f50c2..e9078380492 100644 --- a/packages/compass-web/src/index.tsx +++ b/packages/compass-web/src/index.tsx @@ -4,3 +4,5 @@ export type { OpenWorkspaceOptions, WorkspaceTab, } from '@mongodb-js/compass-workspaces'; + +export { CompassExperimentationProvider } from '@mongodb-js/compass-telemetry';