Search in sources :

Example 1 with DecisionReasons

use of com.optimizely.ab.optimizelydecision.DecisionReasons in project java-sdk by optimizely.

the class Bucketer method bucket.

/**
 * Assign a {@link Variation} of an {@link Experiment} to a user based on hashed value from murmurhash3.
 *
 * @param experiment  The Experiment in which the user is to be bucketed.
 * @param bucketingId string A customer-assigned value used to create the key for the murmur hash.
 * @param projectConfig      The current projectConfig
 * @return A {@link DecisionResponse} including the {@link Variation} that user is bucketed into (or null) and the decision reasons
 */
@Nonnull
public DecisionResponse<Variation> bucket(@Nonnull Experiment experiment, @Nonnull String bucketingId, @Nonnull ProjectConfig projectConfig) {
    DecisionReasons reasons = DefaultDecisionReasons.newInstance();
    // ---------- Bucket User ----------
    String groupId = experiment.getGroupId();
    // check whether the experiment belongs to a group
    if (!groupId.isEmpty()) {
        Group experimentGroup = projectConfig.getGroupIdMapping().get(groupId);
        // bucket to an experiment only if group entities are to be mutually exclusive
        if (experimentGroup.getPolicy().equals(Group.RANDOM_POLICY)) {
            Experiment bucketedExperiment = bucketToExperiment(experimentGroup, bucketingId, projectConfig);
            if (bucketedExperiment == null) {
                String message = reasons.addInfo("User with bucketingId \"%s\" is not in any experiment of group %s.", bucketingId, experimentGroup.getId());
                logger.info(message);
                return new DecisionResponse(null, reasons);
            } else {
            }
            // don't perform further bucketing within the experiment
            if (!bucketedExperiment.getId().equals(experiment.getId())) {
                String message = reasons.addInfo("User with bucketingId \"%s\" is not in experiment \"%s\" of group %s.", bucketingId, experiment.getKey(), experimentGroup.getId());
                logger.info(message);
                return new DecisionResponse(null, reasons);
            }
            String message = reasons.addInfo("User with bucketingId \"%s\" is in experiment \"%s\" of group %s.", bucketingId, experiment.getKey(), experimentGroup.getId());
            logger.info(message);
        }
    }
    DecisionResponse<Variation> decisionResponse = bucketToVariation(experiment, bucketingId);
    reasons.merge(decisionResponse.getReasons());
    return new DecisionResponse<>(decisionResponse.getResult(), reasons);
}
Also used : DecisionResponse(com.optimizely.ab.optimizelydecision.DecisionResponse) DefaultDecisionReasons(com.optimizely.ab.optimizelydecision.DefaultDecisionReasons) DecisionReasons(com.optimizely.ab.optimizelydecision.DecisionReasons) Nonnull(javax.annotation.Nonnull)

Example 2 with DecisionReasons

use of com.optimizely.ab.optimizelydecision.DecisionReasons in project java-sdk by optimizely.

the class DecisionService method getVariationFromDeliveryRule.

/**
 * @param projectConfig     The Project config
 * @param flagKey           The flag key for the feature flag
 * @param rules             The experiments belonging to a rollout
 * @param ruleIndex         The index of the rule
 * @param user              The OptimizelyUserContext
 * @return                  Returns a DecisionResponse Object containing a AbstractMap.SimpleEntry<Variation, Boolean>
 *                          where the Variation is the result and the Boolean is the skipToEveryoneElse.
 */
DecisionResponse<AbstractMap.SimpleEntry> getVariationFromDeliveryRule(@Nonnull ProjectConfig projectConfig, @Nonnull String flagKey, @Nonnull List<Experiment> rules, @Nonnull int ruleIndex, @Nonnull OptimizelyUserContext user) {
    DecisionReasons reasons = DefaultDecisionReasons.newInstance();
    Boolean skipToEveryoneElse = false;
    AbstractMap.SimpleEntry<Variation, Boolean> variationToSkipToEveryoneElsePair;
    // Check forced-decisions first
    Experiment rule = rules.get(ruleIndex);
    OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flagKey, rule.getKey());
    DecisionResponse<Variation> forcedDecisionResponse = validatedForcedDecision(optimizelyDecisionContext, projectConfig, user);
    reasons.merge(forcedDecisionResponse.getReasons());
    Variation variation = forcedDecisionResponse.getResult();
    if (variation != null) {
        variationToSkipToEveryoneElsePair = new AbstractMap.SimpleEntry<>(variation, false);
        return new DecisionResponse(variationToSkipToEveryoneElsePair, reasons);
    }
    // Handle a regular decision
    String bucketingId = getBucketingId(user.getUserId(), user.getAttributes());
    Boolean everyoneElse = (ruleIndex == rules.size() - 1);
    String loggingKey = everyoneElse ? "Everyone Else" : String.valueOf(ruleIndex + 1);
    Variation bucketedVariation = null;
    DecisionResponse<Boolean> audienceDecisionResponse = ExperimentUtils.doesUserMeetAudienceConditions(projectConfig, rule, user.getAttributes(), RULE, String.valueOf(ruleIndex + 1));
    reasons.merge(audienceDecisionResponse.getReasons());
    String message;
    if (audienceDecisionResponse.getResult()) {
        message = reasons.addInfo("User \"%s\" meets conditions for targeting rule \"%s\".", user.getUserId(), loggingKey);
        reasons.addInfo(message);
        logger.debug(message);
        DecisionResponse<Variation> decisionResponse = bucketer.bucket(rule, bucketingId, projectConfig);
        reasons.merge(decisionResponse.getReasons());
        bucketedVariation = decisionResponse.getResult();
        if (bucketedVariation != null) {
            message = reasons.addInfo("User \"%s\" bucketed for targeting rule \"%s\".", user.getUserId(), loggingKey);
            logger.debug(message);
            reasons.addInfo(message);
        } else if (!everyoneElse) {
            message = reasons.addInfo("User \"%s\" is not bucketed for targeting rule \"%s\".", user.getUserId(), loggingKey);
            logger.debug(message);
            reasons.addInfo(message);
            // Skip the rest of rollout rules to the everyone-else rule if audience matches but not bucketed.
            skipToEveryoneElse = true;
        }
    } else {
        message = reasons.addInfo("User \"%s\" does not meet conditions for targeting rule \"%d\".", user.getUserId(), ruleIndex + 1);
        reasons.addInfo(message);
        logger.debug(message);
    }
    variationToSkipToEveryoneElsePair = new AbstractMap.SimpleEntry<>(bucketedVariation, skipToEveryoneElse);
    return new DecisionResponse(variationToSkipToEveryoneElsePair, reasons);
}
Also used : DecisionResponse(com.optimizely.ab.optimizelydecision.DecisionResponse) OptimizelyDecisionContext(com.optimizely.ab.OptimizelyDecisionContext) DefaultDecisionReasons(com.optimizely.ab.optimizelydecision.DefaultDecisionReasons) DecisionReasons(com.optimizely.ab.optimizelydecision.DecisionReasons)

Example 3 with DecisionReasons

use of com.optimizely.ab.optimizelydecision.DecisionReasons in project java-sdk by optimizely.

the class DecisionService method getVariationFromExperimentRule.

public DecisionResponse<Variation> getVariationFromExperimentRule(@Nonnull ProjectConfig projectConfig, @Nonnull String flagKey, @Nonnull Experiment rule, @Nonnull OptimizelyUserContext user, @Nonnull List<OptimizelyDecideOption> options) {
    DecisionReasons reasons = DefaultDecisionReasons.newInstance();
    String ruleKey = rule != null ? rule.getKey() : null;
    // Check Forced-Decision
    OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(flagKey, ruleKey);
    DecisionResponse<Variation> forcedDecisionResponse = validatedForcedDecision(optimizelyDecisionContext, projectConfig, user);
    reasons.merge(forcedDecisionResponse.getReasons());
    Variation variation = forcedDecisionResponse.getResult();
    if (variation != null) {
        return new DecisionResponse(variation, reasons);
    }
    // regular decision
    DecisionResponse<Variation> decisionResponse = getVariation(rule, user, projectConfig, options);
    reasons.merge(decisionResponse.getReasons());
    variation = decisionResponse.getResult();
    return new DecisionResponse(variation, reasons);
}
Also used : DecisionResponse(com.optimizely.ab.optimizelydecision.DecisionResponse) OptimizelyDecisionContext(com.optimizely.ab.OptimizelyDecisionContext) DefaultDecisionReasons(com.optimizely.ab.optimizelydecision.DefaultDecisionReasons) DecisionReasons(com.optimizely.ab.optimizelydecision.DecisionReasons)

Example 4 with DecisionReasons

use of com.optimizely.ab.optimizelydecision.DecisionReasons in project java-sdk by optimizely.

the class DecisionService method validatedForcedDecision.

/**
 * Find a validated forced decision
 *
 * @param optimizelyDecisionContext The OptimizelyDecisionContext containing flagKey and ruleKey
 * @param projectConfig             The Project config
 * @param user                      The OptimizelyUserContext
 * @return Returns a DecisionResponse structure of type Variation, otherwise null result with reasons
 */
public DecisionResponse<Variation> validatedForcedDecision(@Nonnull OptimizelyDecisionContext optimizelyDecisionContext, @Nonnull ProjectConfig projectConfig, @Nonnull OptimizelyUserContext user) {
    DecisionReasons reasons = DefaultDecisionReasons.newInstance();
    String userId = user.getUserId();
    OptimizelyForcedDecision optimizelyForcedDecision = user.findForcedDecision(optimizelyDecisionContext);
    String variationKey = optimizelyForcedDecision != null ? optimizelyForcedDecision.getVariationKey() : null;
    if (projectConfig != null && variationKey != null) {
        Variation variation = projectConfig.getFlagVariationByKey(optimizelyDecisionContext.getFlagKey(), variationKey);
        String ruleKey = optimizelyDecisionContext.getRuleKey();
        String flagKey = optimizelyDecisionContext.getFlagKey();
        String info;
        String target = ruleKey != OptimizelyDecisionContext.OPTI_NULL_RULE_KEY ? String.format("flag (%s), rule (%s)", flagKey, ruleKey) : String.format("flag (%s)", flagKey);
        if (variation != null) {
            info = String.format("Variation (%s) is mapped to %s and user (%s) in the forced decision map.", variationKey, target, userId);
            logger.debug(info);
            reasons.addInfo(info);
            return new DecisionResponse(variation, reasons);
        } else {
            info = String.format("Invalid variation is mapped to %s and user (%s) in the forced decision map.", target, userId);
            logger.debug(info);
            reasons.addInfo(info);
        }
    }
    return new DecisionResponse<>(null, reasons);
}
Also used : DecisionResponse(com.optimizely.ab.optimizelydecision.DecisionResponse) DefaultDecisionReasons(com.optimizely.ab.optimizelydecision.DefaultDecisionReasons) DecisionReasons(com.optimizely.ab.optimizelydecision.DecisionReasons) OptimizelyForcedDecision(com.optimizely.ab.OptimizelyForcedDecision)

Example 5 with DecisionReasons

use of com.optimizely.ab.optimizelydecision.DecisionReasons in project java-sdk by optimizely.

the class DecisionService method getStoredVariation.

/**
 * Get the {@link Variation} that has been stored for the user in the {@link UserProfileService} implementation.
 *
 * @param experiment  {@link Experiment} in which the user was bucketed.
 * @param userProfile {@link UserProfile} of the user.
 * @param projectConfig      The current projectConfig
 * @return A {@link DecisionResponse} including the {@link Variation} that user was previously bucketed into (or null)
 * and the decision reasons. The variation can be null if the {@link UserProfileService} implementation is null or the user was not previously bucketed.
 */
@Nonnull
DecisionResponse<Variation> getStoredVariation(@Nonnull Experiment experiment, @Nonnull UserProfile userProfile, @Nonnull ProjectConfig projectConfig) {
    DecisionReasons reasons = DefaultDecisionReasons.newInstance();
    // ---------- Check User Profile for Sticky Bucketing ----------
    // If a user profile instance is present then check it for a saved variation
    String experimentId = experiment.getId();
    String experimentKey = experiment.getKey();
    Decision decision = userProfile.experimentBucketMap.get(experimentId);
    if (decision != null) {
        String variationId = decision.variationId;
        Variation savedVariation = projectConfig.getExperimentIdMapping().get(experimentId).getVariationIdToVariationMap().get(variationId);
        if (savedVariation != null) {
            String message = reasons.addInfo("Returning previously activated variation \"%s\" of experiment \"%s\" for user \"%s\" from user profile.", savedVariation.getKey(), experimentKey, userProfile.userId);
            logger.info(message);
            // A variation is stored for this combined bucket id
            return new DecisionResponse(savedVariation, reasons);
        } else {
            String message = reasons.addInfo("User \"%s\" was previously bucketed into variation with ID \"%s\" for experiment \"%s\", but no matching variation was found for that user. We will re-bucket the user.", userProfile.userId, variationId, experimentKey);
            logger.info(message);
            return new DecisionResponse(null, reasons);
        }
    } else {
        String message = reasons.addInfo("No previously activated variation of experiment \"%s\" for user \"%s\" found in user profile.", experimentKey, userProfile.userId);
        logger.info(message);
        return new DecisionResponse(null, reasons);
    }
}
Also used : DecisionResponse(com.optimizely.ab.optimizelydecision.DecisionResponse) DefaultDecisionReasons(com.optimizely.ab.optimizelydecision.DefaultDecisionReasons) DecisionReasons(com.optimizely.ab.optimizelydecision.DecisionReasons) OptimizelyForcedDecision(com.optimizely.ab.OptimizelyForcedDecision) Nonnull(javax.annotation.Nonnull)

Aggregations

DecisionReasons (com.optimizely.ab.optimizelydecision.DecisionReasons)15 DecisionResponse (com.optimizely.ab.optimizelydecision.DecisionResponse)15 DefaultDecisionReasons (com.optimizely.ab.optimizelydecision.DefaultDecisionReasons)15 Nonnull (javax.annotation.Nonnull)12 OptimizelyDecisionContext (com.optimizely.ab.OptimizelyDecisionContext)2 OptimizelyForcedDecision (com.optimizely.ab.OptimizelyForcedDecision)2 AudienceIdCondition (com.optimizely.ab.config.audience.AudienceIdCondition)2 Condition (com.optimizely.ab.config.audience.Condition)2 OrCondition (com.optimizely.ab.config.audience.OrCondition)2 OptimizelyRuntimeException (com.optimizely.ab.OptimizelyRuntimeException)1 ArrayList (java.util.ArrayList)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1