diff --git a/packages/sui-pde/src/adapters/optimizely/index.js b/packages/sui-pde/src/adapters/optimizely/index.js index 639dabbbd..7e7c7ce59 100644 --- a/packages/sui-pde/src/adapters/optimizely/index.js +++ b/packages/sui-pde/src/adapters/optimizely/index.js @@ -125,7 +125,7 @@ export default class OptimizelyAdapter { * @param {object} [params.attributes] * @returns {object} decision */ - decide({name, attributes}) { + decide({name, attributes, isEventDisabled}) { const user = this._optimizely.createUserContext(this._userId, { ...this._applicationAttributes, ...attributes @@ -133,7 +133,9 @@ export default class OptimizelyAdapter { return user.decide( name, - !this._hasUserConsents ? [optimizelySDK.OptimizelyDecideOption.DISABLE_DECISION_EVENT] : undefined + !this._hasUserConsents || isEventDisabled + ? [optimizelySDK.OptimizelyDecideOption.DISABLE_DECISION_EVENT] + : undefined ) } diff --git a/packages/sui-pde/src/hooks/common/platformStrategies.js b/packages/sui-pde/src/hooks/common/platformStrategies.js index c8a6842a5..40105122e 100644 --- a/packages/sui-pde/src/hooks/common/platformStrategies.js +++ b/packages/sui-pde/src/hooks/common/platformStrategies.js @@ -6,8 +6,8 @@ const getServerStrategy = () => ({ getVariation: ({pde, experimentName, attributes, adapterId}) => { return pde.getVariation({pde, name: experimentName, attributes, adapterId}) }, - decide: ({pde, name, attributes, adapterId}) => { - return pde.decide({pde, name, attributes, adapterId}) + decide: ({pde, name, attributes, adapterId, isEventDisabled}) => { + return pde.decide({pde, name, attributes, adapterId, isEventDisabled}) }, trackExperiment: () => {}, getForcedValue: ({key, queryString}) => { @@ -30,8 +30,8 @@ const getBrowserStrategy = ({customTrackExperimentViewed, cache}) => ({ return variationName }, - decide: ({pde, name, attributes, adapterId}) => { - return pde.decide({pde, name, attributes, adapterId}) + decide: ({pde, name, attributes, adapterId, isEventDisabled}) => { + return pde.decide({pde, name, attributes, adapterId, isEventDisabled}) }, trackExperiment: ({variationName, experimentName}) => { if (customTrackExperimentViewed) { diff --git a/packages/sui-pde/src/hooks/useDecision.js b/packages/sui-pde/src/hooks/useDecision.js index cd2e63df3..b5c858ac9 100644 --- a/packages/sui-pde/src/hooks/useDecision.js +++ b/packages/sui-pde/src/hooks/useDecision.js @@ -1,7 +1,6 @@ -import {useContext, useMemo} from 'react' +import {useMemo} from 'react' -import PdeContext from '../contexts/PdeContext.js' -import {getPlatformStrategy} from './common/platformStrategies.js' +import useDecisionCallback from './useDecisionCallback.js' /** * Hook to use a feature test @@ -13,56 +12,15 @@ import {getPlatformStrategy} from './common/platformStrategies.js' * @param {string} param.adapterId Adapter id to be executed * @return {object} */ -export default function useDecision(name, {attributes, trackExperimentViewed, queryString, adapterId} = {}) { - const {pde} = useContext(PdeContext) - - if (pde === null) { - throw new Error('[sui-pde: useDecision] sui-pde provider is required to work') - } +export default function useDecision( + name, + {attributes, trackExperimentViewed, isEventDisabled, queryString, adapterId} = {} +) { + const {decide} = useDecisionCallback() const data = useMemo(() => { - try { - const strategy = getPlatformStrategy({ - customTrackExperimentViewed: trackExperimentViewed - }) - - const forced = strategy.getForcedValue({ - key: name, - queryString - }) - - if (forced) { - if (['on', 'off'].includes(forced)) { - return {enabled: forced === 'on', flagKey: name} - } - - return {enabled: true, flagKey: name, variationKey: forced} - } - - const notificationId = pde.addDecideListener({ - onDecide: ({type, decisionInfo: decision}) => { - const {ruleKey, variationKey, decisionEventDispatched} = decision - - if (type === 'flag' && decisionEventDispatched) { - strategy.trackExperiment({variationName: variationKey, experimentName: ruleKey}) - } - } - }) - - const data = strategy.decide({ - pde, - name, - attributes, - adapterId - }) - - pde.removeNotificationListener({notificationId}) - - return data - } catch (error) { - return {enabled: false, flagKey: name} - } - }, [trackExperimentViewed, name, queryString, pde, attributes, adapterId]) + return decide(name, {attributes, trackExperimentViewed, isEventDisabled, queryString, adapterId}) + }, [name, attributes, trackExperimentViewed, isEventDisabled, queryString, adapterId]) return data } diff --git a/packages/sui-pde/src/hooks/useDecisionCallback.js b/packages/sui-pde/src/hooks/useDecisionCallback.js new file mode 100644 index 000000000..ae5e3e0c0 --- /dev/null +++ b/packages/sui-pde/src/hooks/useDecisionCallback.js @@ -0,0 +1,66 @@ +import {useCallback, useContext} from 'react' + +import PdeContext from '../contexts/PdeContext.js' +import {getPlatformStrategy} from './common/platformStrategies.js' + +/** + * Hook to call decide + * @return {function} + */ +export default function useDecisionCallback() { + const {pde} = useContext(PdeContext) + + if (pde === null) { + throw new Error('[sui-pde: useDecision] sui-pde provider is required to work') + } + + const decide = useCallback( + (name, {attributes, trackExperimentViewed, isEventDisabled, queryString, adapterId} = {}) => { + try { + const strategy = getPlatformStrategy({ + customTrackExperimentViewed: trackExperimentViewed + }) + + const forced = strategy.getForcedValue({ + key: name, + queryString + }) + + if (forced) { + if (['on', 'off'].includes(forced)) { + return {enabled: forced === 'on', flagKey: name} + } + + return {enabled: true, flagKey: name, variationKey: forced} + } + + const notificationId = pde.addDecideListener({ + onDecide: ({type, decisionInfo: decision}) => { + const {ruleKey, variationKey, decisionEventDispatched} = decision + + if (type === 'flag' && decisionEventDispatched) { + strategy.trackExperiment({variationName: variationKey, experimentName: ruleKey}) + } + } + }) + + const data = strategy.decide({ + pde, + name, + attributes, + adapterId, + isEventDisabled + }) + + pde.removeNotificationListener({notificationId}) + + return data + } catch (error) { + return {enabled: false, flagKey: name} + } + }, + [] + ) + + return {decide} +} diff --git a/packages/sui-pde/src/index.js b/packages/sui-pde/src/index.js index d23c8f35f..b3aca0688 100644 --- a/packages/sui-pde/src/index.js +++ b/packages/sui-pde/src/index.js @@ -3,6 +3,7 @@ export {default as useFeature} from './hooks/useFeature.js' export {default as PdeContext} from './contexts/PdeContext.js' export {default as useExperiment} from './hooks/useExperiment.js' export {default as useDecision} from './hooks/useDecision.js' +export {default as useDecisionCallback} from './hooks/useDecisionCallback.js' export {default as Experiment} from './components/experiment.js' export {default as Feature} from './components/feature.js' export {default as Decision} from './components/decision.js' diff --git a/packages/sui-pde/test/common/useDecisionSpec.js b/packages/sui-pde/test/common/useDecisionSpec.js index 15649fad1..182ae9730 100644 --- a/packages/sui-pde/test/common/useDecisionSpec.js +++ b/packages/sui-pde/test/common/useDecisionSpec.js @@ -28,19 +28,17 @@ describe('useDecision hook', () => { describe('when pde context is set', () => { let wrapper let decide + const decision = { + variationKey: 'variation', + enabled: true, + variables: {}, + ruleKey: 'rule', + flagKey: 'flag', + userContext: {}, + reasons: [] + } before(() => { - const decision = { - variationKey: 'variation', - enabled: true, - variables: {}, - ruleKey: 'rule', - flagKey: 'flag', - userContext: {}, - reasons: [] - } - decide = sinon.stub().returns(decision) - const addDecideListener = ({onDecide}) => onDecide({type: 'flag', decisionInfo: {...decision, decisionEventDispatched: true}}) const removeNotificationListener = sinon.stub() @@ -53,6 +51,10 @@ describe('useDecision hook', () => { ) }) + beforeEach(() => { + decide = sinon.stub().returns(decision) + }) + describe.client('and the hook is executed by the browser', () => { describe('and window.analytics.track exists', () => { beforeEach(() => { @@ -200,6 +202,35 @@ describe('useDecision hook', () => { sinon.assert.callCount(customTrack, 1) }) }) + + describe('and event is disabled', () => { + before(() => { + window.analytics = { + ready: cb => cb(), + track: sinon.spy() + } + sinon.spy(console, 'error') + }) + + after(() => { + console.error.restore() + }) + + it('should not send event', () => { + renderHook( + () => + useDecision('test_experiment_id', { + isEventDisabled: true + }), + { + wrapper + } + ) + + sinon.assert.callCount(decide, 1) + sinon.assert.calledWith(decide, sinon.match.has('isEventDisabled', true)) + }) + }) }) describe.server('and the hook is executed by the server', () => {