-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ForcedDecisions): Add forced-decisions APIs to OptimizelyUserContext #705
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It overall looks good. A few changes suggested mostly for OptimizelyUserContext.
packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js
Outdated
Show resolved
Hide resolved
d7f9907
to
fc7b87a
Compare
74f7e89
to
2519ef4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. A few more suggestions on top-level UserContext. We can discuss offline for some of them.
f9ac1f6
to
af836c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All changes look good. One more suggestion for hashing.
1f10508
to
540f0d5
Compare
Update decisioService methods to use user instance WIP WIP WIP WIP WIP WIP Finish implementation and start updating unit tests Continue updating exsiting decision service tests Finish updating decision service tests Clean up Fix remaining tests Clean up Start adding new tests Add setForcedDecision and getForcedDecision unit tests Add removeForcedDecision implementation and tests Add removeAllForcedDecisions implementation and tests Add impression event test cases and clean up Add notifications check Add project config tests Add decision getVariationId tests Update OptimizelyUserContext to include forced decision APIs Remove deprecation notes Incorporate Jae comments part 1 Update setForcedDecision signature to use OptimizelyForcedDecisionKey interface Use dictionary for forced decisions instead of array Update unit tests Rebase, fix conficts and update unit tests Update log variables names Include OptimizelyForcedDecisionKey in type interfaces Clea up Apply OptimizelyDecisionKey in getForcedDecision and removeForcedDecision method signatures Consider case when flagKey is empty string Change to using a single forced decisions map
540f0d5
to
0d0f876
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API changes not finalized yet. I just reviewed map hashkey parts.
{ flagKey, variationKey } : | ||
{ flagKey, ruleKey, variationKey }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to save "flagKey, ruleKey" for decision value? We can consider saving "variationKey" only.
if (this.forcedDecisionsMap.hasOwnProperty(flagKey)) { | ||
const forcedDecisionByRuleKey = this.forcedDecisionsMap[flagKey]; | ||
if (forcedDecisionByRuleKey.hasOwnProperty(ruleKey)) { | ||
delete this.forcedDecisionsMap[flagKey][ruleKey]; | ||
isForcedDecisionRemoved = true; | ||
} | ||
if (Object.keys(this.forcedDecisionsMap[flagKey]).length === 0) { | ||
delete this.forcedDecisionsMap[flagKey]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flat map above will help to simplify this as well -
delete this.forcedDecisionsMap[decisionKey(flagKey, ruleKey)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flat map above will help to simplify this as well -
delete this.forcedDecisionsMap[decisionKey(flagKey, ruleKey)]
Looks like @jaeopt is talking about a simple list with composite key instead of 2D. I totally agree with that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we agreed to go with a single nested object here from performance point of view to avoid having to compute the composite key to lookup the forced decision. Wasn't that the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good Work Overall! Left some comments. Its a big diff so i have half reviewed it so far. Will take another look tomorrow morning and might leave a few more comments
* @return {boolean} true if the forced decision has been set successfully. | ||
*/ | ||
setForcedDecision(key: OptimizelyDecisionKey, variationKey: string): boolean { | ||
if (this.optimizely.getOptimizelyConfig() === null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be bad performance wise. OptimizelyConfig
is not created until the first api call. assuming the scenario where this was not created already, creating a whole config object just to make sure the SDK is initialized is a bit of overkill. it will be better to use any existing flags from optimizely object. there is isValidInstance
. you can expose that if needed
if (this.forcedDecisionsMap[flagKey]) { | ||
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; | ||
} else { | ||
this.forcedDecisionsMap[flagKey] = {}; | ||
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (this.forcedDecisionsMap[flagKey]) { | |
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; | |
} else { | |
this.forcedDecisionsMap[flagKey] = {}; | |
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; | |
} | |
if (!this.forcedDecisionsMap[flagKey]) { | |
this.forcedDecisionsMap[flagKey] = {}; | |
} | |
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; |
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; | ||
} else { | ||
this.forcedDecisionsMap[flagKey] = {}; | ||
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I m not sure about how everyone else is doing it but you could use a simple list with composite keys with concatenation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In our last meeting, as I remember we decided to go with the nested map here. Let's sync up on it once again
if (this.forcedDecisionsMap.hasOwnProperty(flagKey)) { | ||
const forcedDecisionByRuleKey = this.forcedDecisionsMap[flagKey]; | ||
if (forcedDecisionByRuleKey.hasOwnProperty(ruleKey)) { | ||
delete this.forcedDecisionsMap[flagKey][ruleKey]; | ||
isForcedDecisionRemoved = true; | ||
} | ||
if (Object.keys(this.forcedDecisionsMap[flagKey]).length === 0) { | ||
delete this.forcedDecisionsMap[flagKey]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flat map above will help to simplify this as well -
delete this.forcedDecisionsMap[decisionKey(flagKey, ruleKey)]
Looks like @jaeopt is talking about a simple list with composite key instead of 2D. I totally agree with that
if (this.forcedDecisionsMap.hasOwnProperty(flagKey)) { | ||
const forcedDecisionByRuleKey = this.forcedDecisionsMap[flagKey]; | ||
if (forcedDecisionByRuleKey.hasOwnProperty(validRuleKey)) { | ||
variationKey = forcedDecisionByRuleKey[validRuleKey].variationKey; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you use a single list, this will whole simplify a lot
}); | ||
|
||
if (Object.keys(this.forcedDecisionsMap).length > 0) { | ||
userContext.forcedDecisionsMap = { ...this.forcedDecisionsMap }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be we should cache the cloned user context. This will make a new clone everytime any API is called. and if there are manyy forced decisions, this can impact performance. May be we can leave it like this for now and circle back to it later.
return variationKey; | ||
} | ||
|
||
findValidatedForcedDecision( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment will be helpful here explaining what exactly it does and how its different from the normal get one
@@ -880,7 +884,8 @@ export default class Optimizely { | |||
return null; | |||
} | |||
|
|||
const decisionObj = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes).result; | |||
const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is explicit casting needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because getVariationForFeature
should expect a user of OptimizelyUserContext
type, where createUserContext
returns OptimizelyUserContext | null
. Validation of userId
and attributes
happens before we get to create an instance of user context, so having explicit casting seem ok here. Let me know if you think otherwise.
985950e
to
2886188
Compare
fc2ad9e
to
f685d3d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Approved with super NIT.
if (!this.optimizely.isValidInstance()) { | ||
logger.error(DECISION_MESSAGES.SDK_NOT_READY); | ||
return false; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: remove this extra line break
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Summary
Add a set of new APIs for forced-decisions to OptimizelyUserContext:
Test plan