Skip to content

feat(flag-decisions): Add support for sending flag decisions along with decision metadata. #244

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

Merged
merged 16 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions OptimizelySDK.Net35/OptimizelySDK.Net35.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@
<Compile Include="..\OptimizelySDK\Event\Entity\Visitor.cs">
<Link>Event\Entity\Visitor.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
<Link>Event\Entity\DecisionMetadata.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
<Link>Event\Entity\VisitorAttribute.cs</Link>
</Compile>
Expand Down
3 changes: 3 additions & 0 deletions OptimizelySDK.Net40/OptimizelySDK.Net40.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
<Link>Event\Entity\VisitorAttribute.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
<Link>Event\Entity\DecisionMetadata.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\EventFactory.cs">
<Link>Event\EventFactory.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
<Link>VisitorAttribute.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
<Link>DecisionMetadata.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\Decision.cs">
<Link>DecisionEvent.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\Decision.cs">
<Link>Event\Entity\Decision.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
<Link>Event\Entity\DecisionMetadata.cs</Link>
</Compile>
<Compile Include="..\OptimizelySDK\Event\Entity\EventBatch.cs">
<Link>Event\Entity\EventBatch.cs</Link>
Expand Down
22 changes: 20 additions & 2 deletions OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
using System;
/**
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
Expand Down Expand Up @@ -104,7 +122,7 @@ public void TestImpressionEventEqualsSerializedPayload()
.WithTimeStamp(timeStamp)
.WithEventTags(null)
.Build();

var metadata = new DecisionMetadata("experiment", "experiment_key", "7716830082");
var decision = new Decision("7719770039", "7716830082", "77210100090");
var snapshot = new Snapshot(events: new SnapshotEvent[] { snapshotEvent }, decisions: new Decision[] { decision });

Expand Down
253 changes: 231 additions & 22 deletions OptimizelySDK.Tests/EventTests/EventFactoryTest.cs

Large diffs are not rendered by default.

23 changes: 20 additions & 3 deletions OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
using Moq;
/**
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Moq;
using NUnit.Framework;
using OptimizelySDK.Config;
using OptimizelySDK.Entity;
Expand Down Expand Up @@ -32,7 +49,7 @@ public void ImpressionEventTest()
var variation = Config.GetVariationFromId(experiment.Key, "77210100090");
var userId = TestUserId;

var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, null);
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, null, "test_experiment", "experiment");

Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId);
Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision);
Expand All @@ -58,7 +75,7 @@ public void ImpressionEventTestWithAttributes()
{ "company", "Optimizely" }
};

var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, userAttributes);
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, userAttributes, "test_experiment", "experiment");

Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId);
Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision);
Expand Down
10 changes: 5 additions & 5 deletions OptimizelySDK.Tests/OptimizelyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ public void TestActivateNoAudienceNoAttributes()
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);

Assert.IsTrue(TestData.CompareObjects(GroupVariation, variation));
}
Expand Down Expand Up @@ -430,7 +430,7 @@ public void TestActivateWithAttributes()
LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."));
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);

Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation));
}
Expand Down Expand Up @@ -490,7 +490,7 @@ public void TestActivateWithTypedAttributes()

LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);

Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation));
}
Expand Down Expand Up @@ -683,8 +683,8 @@ public void TestInvalidDispatchImpressionEvent()

LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [3037] to user [test_user] with bucketing ID [test_user]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [test_user] is in variation [control] of experiment [test_experiment]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);

LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user test_user in experiment test_experiment."), Times.Once);
// Need to see how error handler can be verified.
LoggerMock.Verify(l => l.Log(LogLevel.ERROR, It.IsAny<string>()), Times.Once);

Expand Down Expand Up @@ -1079,7 +1079,7 @@ public void TestActivateNoAudienceNoAttributesAfterSetForcedVariation()
LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in variation [group_exp_1_var_2] of experiment [group_experiment_1]."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);

Assert.IsTrue(TestData.CompareObjects(GroupVariation, variation));
}
Expand Down
10 changes: 10 additions & 0 deletions OptimizelySDK.Tests/ProjectConfigTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public void TestInit()
Assert.AreEqual("7720880029", Config.ProjectId);
// Check Revision
Assert.AreEqual("15", Config.Revision);
// Check SendFlagDecision
Assert.IsTrue(Config.SendFlagDecisions);

// Check Group ID Map
var expectedGroupId = CreateDictionary("7722400015", Config.GetGroup("7722400015"));
Expand Down Expand Up @@ -415,6 +417,14 @@ public void TestInit()
Assert.IsTrue(TestData.CompareObjects(expectedRolloutIdMap, Config.RolloutIdMap));
}


[Test]
public void TestIfSendFlagDecisionKeyIsMissingItShouldReturnFalse()
{
var tempConfig = DatafileProjectConfig.Create(TestData.SimpleABExperimentsDatafile, LoggerMock.Object, ErrorHandlerMock.Object);
Assert.IsFalse(tempConfig.SendFlagDecisions);
}

[Test]
public void TestGetAccountId()
{
Expand Down
3 changes: 2 additions & 1 deletion OptimizelySDK.Tests/TestData.json
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@
"key": "integer_variable",
"type": "integer",
"defaultValue": "7"
}
}
]
},
{
Expand Down Expand Up @@ -940,5 +940,6 @@
],
"revision": "15",
"anonymizeIP": false,
"sendFlagDecisions": true,
"botFiltering": true
}
5 changes: 5 additions & 0 deletions OptimizelySDK/Config/DatafileProjectConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public enum OPTLYSDKVersion
/// </summary>
public string Revision { get; set; }

/// <summary>
/// SendFlagDecisions determines whether impressions events are sent for ALL decision types.
/// </summary>
public bool SendFlagDecisions { get; set; }

/// <summary>
/// Allow Anonymize IP by truncating the last block of visitors' IP address.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion OptimizelySDK/Event/Builder/Params.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017, 2019, Optimizely
* Copyright 2017, 2019-2020, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using OptimizelySDK.Event.Entity;
using System;

namespace OptimizelySDK.Event.Builder
Expand All @@ -30,6 +31,7 @@ public static class Params
public const string ENTITY_ID = "entity_id";
public const string EVENTS = "events";
public const string EXPERIMENT_ID = "experiment_id";
public const string METADATA = "metadata";
public const string PROJECT_ID = "project_id";
public const string REVISION = "revision";
public const string TIME = "timestamp";
Expand Down
8 changes: 5 additions & 3 deletions OptimizelySDK/Event/Entity/Decision.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019, Optimizely
* Copyright 2019-2020, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,15 +23,17 @@ public class Decision
public string CampaignId { get; private set; }
[JsonProperty("experiment_id")]
public string ExperimentId { get; private set; }
[JsonProperty("metadata")]
public DecisionMetadata Metadata { get; private set; }
[JsonProperty("variation_id")]
public string VariationId { get; private set; }

public Decision() {}

public Decision(string campaignId, string experimentId, string variationId)
public Decision(string campaignId, string experimentId, string variationId, DecisionMetadata metadata = null)
{
CampaignId = campaignId;
ExperimentId = experimentId;
Metadata = metadata;
VariationId = variationId;
}
}
Expand Down
44 changes: 44 additions & 0 deletions OptimizelySDK/Event/Entity/DecisionMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using Newtonsoft.Json;

namespace OptimizelySDK.Event.Entity
{
/// <summary>
/// DecisionMetadata captures additional information regarding the decision
/// </summary>
public class DecisionMetadata
{
[JsonProperty("flag_key")]
public string FlagKey { get; private set; }
[JsonProperty("rule_key")]
public string RuleKey { get; private set; }
[JsonProperty("rule_type")]
public string RuleType { get; private set; }
[JsonProperty("variation_key")]
public string VariationKey { get; private set; }

public DecisionMetadata(string flagKey, string ruleKey, string ruleType, string variationKey = "")
{
FlagKey = flagKey;
RuleKey = ruleKey;
RuleType = ruleType;
VariationKey = variationKey;
}
}
}
12 changes: 11 additions & 1 deletion OptimizelySDK/Event/Entity/ImpressionEvent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019, Optimizely
* Copyright 2019-2020, Optimizely
*
* 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 @@ -28,6 +28,7 @@ public class ImpressionEvent : UserEvent
public VisitorAttribute[] VisitorAttributes { get; private set; }

public Experiment Experiment { get; set; }
public DecisionMetadata Metadata { get; set; }
public Variation Variation { get; set; }
public bool? BotFiltering { get; set; }

Expand All @@ -42,6 +43,7 @@ public class Builder
public VisitorAttribute[] VisitorAttributes;
private Experiment Experiment;
private Variation Variation;
private DecisionMetadata Metadata;
private bool? BotFiltering;

public Builder WithUserId(string userId)
Expand All @@ -65,6 +67,13 @@ public Builder WithExperiment(Experiment experiment)
return this;
}

public Builder WithMetadata(DecisionMetadata metadata)
{
Metadata = metadata;

return this;
}

public Builder WithVisitorAttributes(VisitorAttribute[] visitorAttributes)
{
VisitorAttributes = visitorAttributes;
Expand Down Expand Up @@ -102,6 +111,7 @@ public ImpressionEvent Build()
impressionEvent.VisitorAttributes = VisitorAttributes;
impressionEvent.UserId = UserId;
impressionEvent.Variation = Variation;
impressionEvent.Metadata = Metadata;
impressionEvent.BotFiltering = BotFiltering;

return impressionEvent;
Expand Down
7 changes: 4 additions & 3 deletions OptimizelySDK/Event/EventFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019, Optimizely
* Copyright 2019-2020, Optimizely
*
* 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 @@ -115,11 +115,12 @@ private static Visitor CreateVisitor(ImpressionEvent impressionEvent) {

Decision decision = new Decision(impressionEvent.Experiment?.LayerId,
impressionEvent.Experiment?.Id,
impressionEvent.Variation?.Id);
impressionEvent.Variation?.Id,
impressionEvent.Metadata);

SnapshotEvent snapshotEvent = new SnapshotEvent.Builder()
.WithUUID(impressionEvent.UUID)
.WithEntityId(impressionEvent.Experiment.LayerId)
.WithEntityId(impressionEvent.Experiment?.LayerId)
.WithKey(ACTIVATE_EVENT_KEY)
.WithTimeStamp(impressionEvent.Timestamp)
.Build();
Expand Down
Loading