Skip to content
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

Merged
merged 8 commits into from
Oct 28, 2021

Conversation

yavorona
Copy link
Contributor

@yavorona yavorona commented Sep 23, 2021

Summary

Add a set of new APIs for forced-decisions to OptimizelyUserContext:

  • setForcedDecision
  • getForcedDecision
  • removeForcedDecision
  • removeAllForcedDecisions

Test plan

@coveralls
Copy link

coveralls commented Sep 23, 2021

Coverage Status

Coverage increased (+0.04%) to 97.157% when pulling 5bf613c on pnguen/forced-decisions into 6e1f6d6 on master.

@yavorona yavorona marked this pull request as ready for review October 1, 2021 03:39
@yavorona yavorona requested a review from a team as a code owner October 1, 2021 03:39
Copy link
Contributor

@jaeopt jaeopt left a 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.

Copy link
Contributor

@jaeopt jaeopt left a 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.

@yavorona yavorona force-pushed the pnguen/forced-decisions branch 4 times, most recently from f9ac1f6 to af836c2 Compare October 11, 2021 22:59
@yavorona yavorona removed their assignment Oct 11, 2021
Copy link
Contributor

@jaeopt jaeopt left a 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.

@yavorona yavorona force-pushed the pnguen/forced-decisions branch 2 times, most recently from 1f10508 to 540f0d5 Compare October 14, 2021 18:34
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
@yavorona yavorona force-pushed the pnguen/forced-decisions branch from 540f0d5 to 0d0f876 Compare October 14, 2021 22:32
@yavorona yavorona removed their assignment Oct 15, 2021
Copy link
Contributor

@jaeopt jaeopt left a 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.

Comment on lines 149 to 150
{ flagKey, variationKey } :
{ flagKey, ruleKey, variationKey };
Copy link
Contributor

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.

Comment on lines +192 to +201
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];
}
}
Copy link
Contributor

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)]

Copy link
Contributor

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

Copy link
Contributor Author

@yavorona yavorona Oct 19, 2021

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?

Copy link
Contributor

@zashraf1985 zashraf1985 left a 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) {
Copy link
Contributor

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

Comment on lines 152 to 157
if (this.forcedDecisionsMap[flagKey]) {
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision;
} else {
this.forcedDecisionsMap[flagKey] = {};
this.forcedDecisionsMap[flagKey][ruleKey] = forcedDecision;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Contributor

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.

Copy link
Contributor Author

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

Comment on lines +192 to +201
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];
}
}
Copy link
Contributor

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

Comment on lines 225 to 230
if (this.forcedDecisionsMap.hasOwnProperty(flagKey)) {
const forcedDecisionByRuleKey = this.forcedDecisionsMap[flagKey];
if (forcedDecisionByRuleKey.hasOwnProperty(validRuleKey)) {
variationKey = forcedDecisionByRuleKey[validRuleKey].variationKey;
}
}
Copy link
Contributor

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 };
Copy link
Contributor

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(
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@yavorona yavorona force-pushed the pnguen/forced-decisions branch 2 times, most recently from 985950e to 2886188 Compare October 20, 2021 22:54
@yavorona yavorona force-pushed the pnguen/forced-decisions branch from fc2ad9e to f685d3d Compare October 21, 2021 00:00
Copy link
Contributor

@zashraf1985 zashraf1985 left a 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;

Copy link
Contributor

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

Copy link
Contributor

@jaeopt jaeopt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@msohailhussain msohailhussain merged commit 94794ee into master Oct 28, 2021
@msohailhussain msohailhussain deleted the pnguen/forced-decisions branch October 28, 2021 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants