feat(ForcedDecisions): Add forced-decisions APIs to OptimizelyUserContext#705
Conversation
jaeopt
left a comment
There was a problem hiding this comment.
It overall looks good. A few changes suggested mostly for OptimizelyUserContext.
d7f9907 to
fc7b87a
Compare
74f7e89 to
2519ef4
Compare
jaeopt
left a comment
There was a problem hiding this comment.
Looks good. A few more suggestions on top-level UserContext. We can discuss offline for some of them.
f9ac1f6 to
af836c2
Compare
jaeopt
left a comment
There was a problem hiding this comment.
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
jaeopt
left a comment
There was a problem hiding this comment.
API changes not finalized yet. I just reviewed map hashkey parts.
| { flagKey, variationKey } : | ||
| { flagKey, ruleKey, variationKey }; |
There was a problem hiding this comment.
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.
The flat map above will help to simplify this as well -
delete this.forcedDecisionsMap[decisionKey(flagKey, ruleKey)]
There was a problem hiding this comment.
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.
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?
zashraf1985
left a comment
There was a problem hiding this comment.
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.
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.
| 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.
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.
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.
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.
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.
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.
A comment will be helpful here explaining what exactly it does and how its different from the normal get one
| } | ||
|
|
||
| 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.
why is explicit casting needed here?
There was a problem hiding this comment.
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
zashraf1985
left a comment
There was a problem hiding this comment.
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.
NIT: remove this extra line break
Summary
Add a set of new APIs for forced-decisions to OptimizelyUserContext:
Test plan