diff --git a/packages/sui-pde/src/adapters/default.js b/packages/sui-pde/src/adapters/default.js
index c89c0f185..dd41c1c03 100644
--- a/packages/sui-pde/src/adapters/default.js
+++ b/packages/sui-pde/src/adapters/default.js
@@ -15,6 +15,14 @@ export default class DefaultAdapter {
return null
}
+ addDecideListener() {
+ return null
+ }
+
+ removeNotificationListener() {
+ return null
+ }
+
decide() {
return null
}
diff --git a/packages/sui-pde/src/adapters/optimizely/index.js b/packages/sui-pde/src/adapters/optimizely/index.js
index 3ba274c50..3bee49244 100644
--- a/packages/sui-pde/src/adapters/optimizely/index.js
+++ b/packages/sui-pde/src/adapters/optimizely/index.js
@@ -14,9 +14,9 @@ const DEFAULT_EVENTS_OPTIONS = {
const DEFAULT_TIMEOUT = 500
-const {enums: LOG_LEVEL} = optimizelySDK
+const {enums} = optimizelySDK
-const LOGGER_LEVEL = process.env.NODE_ENV === 'production' ? LOG_LEVEL.error : LOG_LEVEL.info
+const LOGGER_LEVEL = process.env.NODE_ENV === 'production' ? enums.error : enums.info
export default class OptimizelyAdapter {
/**
@@ -123,10 +123,12 @@ export default class OptimizelyAdapter {
* @param {Object} params
* @param {string} params.name
* @param {object} [params.attributes]
- * @returns {string=} variation name
+ * @returns {object} decision
*/
decide({name, attributes}) {
- if (!this._hasUserConsents) return null
+ if (!this._hasUserConsents) {
+ return {enabled: false, flagKey: name}
+ }
const user = this._optimizely.createUserContext(this._userId, {
...this._applicationAttributes,
@@ -136,6 +138,23 @@ export default class OptimizelyAdapter {
return user.decide(name)
}
+ /**
+ * @param {Object} params
+ * @param {function} params.onDecide
+ * @returns {number} notificationId
+ */
+ addDecideListener({onDecide}) {
+ return this._optimizely.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, onDecide)
+ }
+
+ /**
+ * @param {Object} params
+ * @param {number} params.notificationId
+ */
+ removeNotificationListener({notificationId}) {
+ this._optimizely.notificationCenter.removeNotificationListener(notificationId)
+ }
+
/**
* Gets the variation without tracking the impression
* @param {Object} params
diff --git a/packages/sui-pde/src/adapters/optimizely/multiple.js b/packages/sui-pde/src/adapters/optimizely/multiple.js
index 741afcb08..04991c3d4 100644
--- a/packages/sui-pde/src/adapters/optimizely/multiple.js
+++ b/packages/sui-pde/src/adapters/optimizely/multiple.js
@@ -64,6 +64,14 @@ class MultipleOptimizelyAdapter {
return this.#adapters[adapterId].decide(props)
}
+ addDecideListener({adapterId = defaultAdapterId, ...props}) {
+ return this.#adapters[adapterId].addDecideListener(props)
+ }
+
+ removeNotificationListener({adapterId = defaultAdapterId, ...props}) {
+ this.#adapters[adapterId].removeNotificationListener(props)
+ }
+
getVariation({adapterId = defaultAdapterId, ...props}) {
return this.#adapters[adapterId].getVariation(props)
}
diff --git a/packages/sui-pde/src/hooks/useDecision.js b/packages/sui-pde/src/hooks/useDecision.js
index 41bce419f..cd2e63df3 100644
--- a/packages/sui-pde/src/hooks/useDecision.js
+++ b/packages/sui-pde/src/hooks/useDecision.js
@@ -39,6 +39,16 @@ export default function useDecision(name, {attributes, trackExperimentViewed, qu
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,
@@ -46,13 +56,7 @@ export default function useDecision(name, {attributes, trackExperimentViewed, qu
adapterId
})
- const {ruleKey, variationKey} = data || {}
-
- const isExperiment = !!ruleKey
-
- if (isExperiment) {
- strategy.trackExperiment({variationName: variationKey, experimentName: ruleKey})
- }
+ pde.removeNotificationListener({notificationId})
return data
} catch (error) {
diff --git a/packages/sui-pde/src/pde.js b/packages/sui-pde/src/pde.js
index 9d4298d85..fb9075d95 100644
--- a/packages/sui-pde/src/pde.js
+++ b/packages/sui-pde/src/pde.js
@@ -37,6 +37,23 @@ export default class PDE {
return this._adapter.decide({name, attributes, adapterId})
}
+ /**
+ * @param {Object} params
+ * @param {function} params.onDecide
+ * @returns {string} notificationId
+ */
+ addDecideListener({onDecide}) {
+ return this._adapter.addDecideListener({onDecide})
+ }
+
+ /**
+ * @param {Object} params
+ * @param {number} params.notificationId
+ */
+ removeNotificationListener({notificationId}) {
+ this._adapter.removeNotificationListener({notificationId})
+ }
+
getInitialData() {
return this._adapter.getInitialData()
}
diff --git a/packages/sui-pde/test/common/useDecisionSpec.js b/packages/sui-pde/test/common/useDecisionSpec.js
index 7ab093891..15649fad1 100644
--- a/packages/sui-pde/test/common/useDecisionSpec.js
+++ b/packages/sui-pde/test/common/useDecisionSpec.js
@@ -30,7 +30,7 @@ describe('useDecision hook', () => {
let decide
before(() => {
- decide = sinon.stub().returns({
+ const decision = {
variationKey: 'variation',
enabled: true,
variables: {},
@@ -38,10 +38,18 @@ describe('useDecision hook', () => {
flagKey: 'flag',
userContext: {},
reasons: []
- })
+ }
+ decide = sinon.stub().returns(decision)
+
+ const addDecideListener = ({onDecide}) =>
+ onDecide({type: 'flag', decisionInfo: {...decision, decisionEventDispatched: true}})
+ const removeNotificationListener = sinon.stub()
+
// eslint-disable-next-line react/prop-types
wrapper = ({children}) => (
- {children}
+
+ {children}
+
)
})
@@ -226,9 +234,14 @@ describe('useDecision hook', () => {
let wrapper
beforeEach(() => {
decide = sinon.stub().throws(new Error('fake activation error'))
+ const addDecideListener = sinon.stub()
+ const removeNotificationListener = sinon.stub()
+
// eslint-disable-next-line react/prop-types
wrapper = ({children}) => (
- {children}
+
+ {children}
+
)
})
@@ -247,11 +260,14 @@ describe('useDecision hook', () => {
ready: cb => cb(),
track: sinon.spy()
}
+ const removeNotificationListener = sinon.stub()
- stubFactory = decide => {
+ stubFactory = ({decide, addDecideListener}) => {
// eslint-disable-next-line react/prop-types
wrapper = ({children}) => (
- {children}
+
+ {children}
+
)
}
})
@@ -263,8 +279,8 @@ describe('useDecision hook', () => {
describe('when the second time returns the same value as the first time', () => {
beforeEach(() => {
const decide = sinon.stub()
-
- decide.onCall(0).returns({
+ const addDecideListener = sinon.stub()
+ const decision = {
variationKey: 'variation',
enabled: true,
variables: {},
@@ -272,7 +288,19 @@ describe('useDecision hook', () => {
flagKey: 'flag',
userContext: {},
reasons: []
- })
+ }
+
+ decide.onCall(0).returns(decision)
+ addDecideListener.onCall(0).callsFake(({onDecide}) =>
+ onDecide({
+ type: 'flag',
+ decisionInfo: {
+ ...decision,
+ decisionEventDispatched: true
+ }
+ })
+ )
+
decide.onCall(1).returns({
variationKey: 'variation',
enabled: true,
@@ -282,8 +310,17 @@ describe('useDecision hook', () => {
userContext: {},
reasons: []
})
+ addDecideListener.onCall(1).callsFake(({onDecide}) =>
+ onDecide({
+ type: 'flag',
+ decisionInfo: {
+ ...decision,
+ decisionEventDispatched: true
+ }
+ })
+ )
- stubFactory(decide)
+ stubFactory({decide, addDecideListener})
})
it('should send only one experiment viewed event', () => {
@@ -300,8 +337,8 @@ describe('useDecision hook', () => {
describe('when the second time returns a different value as the first time', () => {
beforeEach(() => {
const decide = sinon.stub()
-
- decide.onCall(0).returns({
+ const addDecideListener = sinon.stub()
+ const decision = {
variationKey: 'variation_a',
enabled: true,
variables: {},
@@ -309,18 +346,35 @@ describe('useDecision hook', () => {
flagKey: 'flag',
userContext: {},
reasons: []
- })
+ }
+
+ decide.onCall(0).returns(decision)
+ addDecideListener.onCall(0).callsFake(({onDecide}) =>
+ onDecide({
+ type: 'flag',
+ decisionInfo: {
+ ...decision,
+ decisionEventDispatched: true
+ }
+ })
+ )
+
decide.onCall(1).returns({
- variationKey: 'variation_b',
- enabled: true,
- variables: {},
- ruleKey: 'rule',
- flagKey: 'flag',
- userContext: {},
- reasons: []
+ ...decision,
+ variationKey: 'variation_b'
})
+ addDecideListener.onCall(1).callsFake(({onDecide}) =>
+ onDecide({
+ type: 'flag',
+ decisionInfo: {
+ ...decision,
+ variationKey: 'variation_b',
+ decisionEventDispatched: true
+ }
+ })
+ )
- stubFactory(decide)
+ stubFactory({decide, addDecideListener})
})
it('should send two experiment viewed events', () => {