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(flag-decisions): Add support for sending flag decisions along with decision metadata. #405

Merged
merged 8 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,31 +216,46 @@ private Variation activate(@Nullable ProjectConfig projectConfig,
return null;
}

sendImpression(projectConfig, experiment, userId, copiedAttributes, variation);
sendImpression(projectConfig, experiment, userId, copiedAttributes, variation, experiment.getKey(), "experiment");
Copy link
Contributor

Choose a reason for hiding this comment

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

please make experiment const or enum


return variation;
}

/**
* Creates and sends impression event.
*
* @param projectConfig the current projectConfig
* @param experiment the experiment user bucketed into and dispatch an impression event
* @param userId the ID of the user
* @param filteredAttributes the attributes of the user
* @param variation the variation that was returned from activate.
* @param flagKey It can either be experiment key in case if flagType is experiment or it's feature key in case flagType is feature-test or rollout
* @param flagType It can either be experiment in case impression event is sent from activate or it's feature-test or rollout
*/
private void sendImpression(@Nonnull ProjectConfig projectConfig,
@Nonnull Experiment experiment,
@Nonnull String userId,
@Nonnull Map<String, ?> filteredAttributes,
@Nonnull Variation variation) {
if (!experiment.isRunning()) {
logger.info("Experiment has \"Launched\" status so not dispatching event during activation.");
return;
}
@Nonnull Variation variation,
Copy link
Contributor

Choose a reason for hiding this comment

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

I will appreciate if you can add header doc because of no. of args.

@Nonnull String flagKey,
Copy link
Contributor

Choose a reason for hiding this comment

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

it should be featureFlagKey or featureKey

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are calling it flagKey and flagType in all other sdks. So should I change it in java-sdk?

Copy link
Contributor

Choose a reason for hiding this comment

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

I like flagKey which we use in Decide-API

@Nonnull String flagType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It should be featureSource,


UserEvent userEvent = UserEventFactory.createImpressionEvent(
projectConfig,
experiment,
variation,
userId,
filteredAttributes);
filteredAttributes,
flagKey,
flagType);

if (userEvent == null) {
return;
}
eventProcessor.process(userEvent);
logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey());

if (experiment != null) {
mnoman09 marked this conversation as resolved.
Show resolved Hide resolved
logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey());
}
// Kept For backwards compatibility.
// This notification is deprecated and the new DecisionNotifications
// are sent via their respective method calls.
Expand Down Expand Up @@ -386,16 +401,22 @@ private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig);
Boolean featureEnabled = false;
SourceInfo sourceInfo = new RolloutSourceInfo();
if (featureDecision.decisionSource != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

in which case, decisionSource can be null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

when experiment and variation is null.
the cases are

  1. When The feature flag is not used in a rollout
  2. The rollout with id was not found in the datafile for feature flag

decisionSource = featureDecision.decisionSource;
}
sendImpression(
projectConfig,
featureDecision.experiment,
userId,
copiedAttributes,
featureDecision.variation,
featureKey,
decisionSource.toString());

if (featureDecision.variation != null) {
// This information is only necessary for feature tests.
// For rollouts experiments and variations are an implementation detail only.
if (featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.FEATURE_TEST)) {
sendImpression(
projectConfig,
featureDecision.experiment,
userId,
copiedAttributes,
featureDecision.variation);
decisionSource = featureDecision.decisionSource;
sourceInfo = new FeatureTestSourceInfo(featureDecision.experiment.getKey(), featureDecision.variation.getKey());
} else {
logger.info("The user \"{}\" is not included in an experiment for feature \"{}\".",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class DatafileProjectConfig implements ProjectConfig {
private final String revision;
private final String version;
private final boolean anonymizeIP;
private final boolean sendFlagDecisions;
private final Boolean botFiltering;
private final List<Attribute> attributes;
private final List<Audience> audiences;
Expand Down Expand Up @@ -103,6 +104,7 @@ public DatafileProjectConfig(String accountId, String projectId, String version,
this(
accountId,
anonymizeIP,
false,
null,
projectId,
revision,
Expand All @@ -121,6 +123,7 @@ public DatafileProjectConfig(String accountId, String projectId, String version,
// v4 constructor
public DatafileProjectConfig(String accountId,
boolean anonymizeIP,
boolean sendFlagDecisions,
Boolean botFiltering,
String projectId,
String revision,
Expand All @@ -139,6 +142,7 @@ public DatafileProjectConfig(String accountId,
this.version = version;
this.revision = revision;
this.anonymizeIP = anonymizeIP;
this.sendFlagDecisions = sendFlagDecisions;
this.botFiltering = botFiltering;

this.attributes = Collections.unmodifiableList(attributes);
Expand Down Expand Up @@ -322,6 +326,9 @@ public String getRevision() {
return revision;
}

@Override
public boolean getSendFlagDecisions() { return sendFlagDecisions; }

@Override
public boolean getAnonymizeIP() {
return anonymizeIP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Experiment getExperimentForKey(@Nonnull String experimentKey,

String getRevision();

boolean getSendFlagDecisions();

boolean getAnonymizeIP();

Boolean getBotFiltering();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -83,9 +83,11 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa
anonymizeIP = jsonObject.get("anonymizeIP").getAsBoolean();
}


List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
boolean sendFlagDecisions = false;
if (datafileVersion >= Integer.parseInt(DatafileProjectConfig.Version.V4.toString())) {
Type featureFlagsType = new TypeToken<List<FeatureFlag>>() {
}.getType();
Expand All @@ -95,11 +97,14 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa
rollouts = context.deserialize(jsonObject.get("rollouts").getAsJsonArray(), rolloutsType);
if (jsonObject.has("botFiltering"))
botFiltering = jsonObject.get("botFiltering").getAsBoolean();
if (jsonObject.has("sendFlagDecisions"))
sendFlagDecisions = jsonObject.get("sendFlagDecisions").getAsBoolean();
}

return new DatafileProjectConfig(
accountId,
anonymizeIP,
sendFlagDecisions,
botFiltering,
projectId,
revision,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -64,17 +64,22 @@ public DatafileProjectConfig deserialize(JsonParser parser, DeserializationConte
List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
boolean sendFlagDecisions = false;
if (datafileVersion >= Integer.parseInt(DatafileProjectConfig.Version.V4.toString())) {
featureFlags = JacksonHelpers.arrayNodeToList(node.get("featureFlags"), FeatureFlag.class, codec);
rollouts = JacksonHelpers.arrayNodeToList(node.get("rollouts"), Rollout.class, codec);
if (node.hasNonNull("botFiltering")) {
botFiltering = node.get("botFiltering").asBoolean();
}
if (node.hasNonNull("sendFlagDecisions")) {
sendFlagDecisions = node.get("sendFlagDecisions").asBoolean();
}
}

return new DatafileProjectConfig(
accountId,
anonymizeIP,
sendFlagDecisions,
botFiltering,
projectId,
revision,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,20 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
boolean sendFlagDecisions = false;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags(rootObject.getJSONArray("featureFlags"));
rollouts = parseRollouts(rootObject.getJSONArray("rollouts"));
if (rootObject.has("botFiltering"))
botFiltering = rootObject.getBoolean("botFiltering");
if (rootObject.has("sendFlagDecisions"))
sendFlagDecisions = rootObject.getBoolean("sendFlagDecisions");
}

return new DatafileProjectConfig(
accountId,
anonymizeIP,
sendFlagDecisions,
botFiltering,
projectId,
revision,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,20 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
List<FeatureFlag> featureFlags = null;
List<Rollout> rollouts = null;
Boolean botFiltering = null;
boolean sendFlagDecisions = false;
if (datafileVersion >= Integer.parseInt(DatafileProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags((JSONArray) rootObject.get("featureFlags"));
rollouts = parseRollouts((JSONArray) rootObject.get("rollouts"));
if (rootObject.containsKey("botFiltering"))
botFiltering = (Boolean) rootObject.get("botFiltering");
if (rootObject.containsKey("sendFlagDecisions"))
sendFlagDecisions = (Boolean) rootObject.get("sendFlagDecisions");
}

return new DatafileProjectConfig(
accountId,
anonymizeIP,
sendFlagDecisions,
botFiltering,
projectId,
revision,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2016-2019, Optimizely and contributors
* Copyright 2016-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -99,6 +99,7 @@ private static Visitor createVisitor(ImpressionEvent impressionEvent) {
.setCampaignId(impressionEvent.getLayerId())
.setExperimentId(impressionEvent.getExperimentId())
.setVariationId(impressionEvent.getVariationId())
.setMetadata(impressionEvent.getMetadata())
.setIsCampaignHoldback(false)
.build();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
*
* Copyright 2019, Optimizely and contributors
* Copyright 2019-2020, Optimizely and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,8 @@
*/
package com.optimizely.ab.event.internal;

import com.optimizely.ab.event.internal.payload.DecisionMetadata;

import java.util.StringJoiner;

/**
Expand All @@ -28,19 +30,22 @@ public class ImpressionEvent extends BaseEvent implements UserEvent {
private final String experimentKey;
private final String variationKey;
private final String variationId;
private final DecisionMetadata metadata;

private ImpressionEvent(UserContext userContext,
String layerId,
String experimentId,
String experimentKey,
String variationKey,
String variationId) {
String variationId,
DecisionMetadata metadata) {
this.userContext = userContext;
this.layerId = layerId;
this.experimentId = experimentId;
this.experimentKey = experimentKey;
this.variationKey = variationKey;
this.variationId = variationId;
this.metadata = metadata;
}

@Override
Expand Down Expand Up @@ -68,6 +73,8 @@ public String getVariationId() {
return variationId;
}

public DecisionMetadata getMetadata() { return metadata; }

public static class Builder {

private UserContext userContext;
Expand All @@ -76,6 +83,7 @@ public static class Builder {
private String experimentKey;
private String variationKey;
private String variationId;
private DecisionMetadata metadata;

public Builder withUserContext(UserContext userContext) {
this.userContext = userContext;
Expand Down Expand Up @@ -107,8 +115,13 @@ public Builder withVariationId(String variationId) {
return this;
}

public Builder withMetadata(DecisionMetadata metadata) {
this.metadata = metadata;
return this;
}

public ImpressionEvent build() {
return new ImpressionEvent(userContext, layerId, experimentId, experimentKey, variationKey, variationId);
return new ImpressionEvent(userContext, layerId, experimentId, experimentKey, variationKey, variationId, metadata);
}
}

Expand Down
Loading