Skip to content

Commit f22ad21

Browse files
authored
feat(flag-decisions): Add support for sending flag decisions along with decision metadata. (#244)
1 parent bbdb629 commit f22ad21

20 files changed

+474
-70
lines changed

OptimizelySDK.Net35/OptimizelySDK.Net35.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@
266266
<Compile Include="..\OptimizelySDK\Event\Entity\Visitor.cs">
267267
<Link>Event\Entity\Visitor.cs</Link>
268268
</Compile>
269+
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
270+
<Link>Event\Entity\DecisionMetadata.cs</Link>
271+
</Compile>
269272
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
270273
<Link>Event\Entity\VisitorAttribute.cs</Link>
271274
</Compile>

OptimizelySDK.Net40/OptimizelySDK.Net40.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@
280280
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
281281
<Link>Event\Entity\VisitorAttribute.cs</Link>
282282
</Compile>
283+
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
284+
<Link>Event\Entity\DecisionMetadata.cs</Link>
285+
</Compile>
283286
<Compile Include="..\OptimizelySDK\Event\EventFactory.cs">
284287
<Link>Event\EventFactory.cs</Link>
285288
</Compile>

OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@
121121
<Compile Include="..\OptimizelySDK\Event\Entity\VisitorAttribute.cs">
122122
<Link>VisitorAttribute.cs</Link>
123123
</Compile>
124+
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
125+
<Link>DecisionMetadata.cs</Link>
126+
</Compile>
124127
<Compile Include="..\OptimizelySDK\Event\Entity\Decision.cs">
125128
<Link>DecisionEvent.cs</Link>
126129
</Compile>

OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@
174174
</Compile>
175175
<Compile Include="..\OptimizelySDK\Event\Entity\Decision.cs">
176176
<Link>Event\Entity\Decision.cs</Link>
177+
</Compile>
178+
<Compile Include="..\OptimizelySDK\Event\Entity\DecisionMetadata.cs">
179+
<Link>Event\Entity\DecisionMetadata.cs</Link>
177180
</Compile>
178181
<Compile Include="..\OptimizelySDK\Event\Entity\EventBatch.cs">
179182
<Link>Event\Entity\EventBatch.cs</Link>

OptimizelySDK.Tests/EventTests/EventEntitiesTest.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
using System;
1+
/**
2+
*
3+
* Copyright 2019-2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
19+
using System;
220
using System.Collections.Generic;
321
using Newtonsoft.Json.Linq;
422
using Newtonsoft.Json.Schema;
@@ -104,7 +122,7 @@ public void TestImpressionEventEqualsSerializedPayload()
104122
.WithTimeStamp(timeStamp)
105123
.WithEventTags(null)
106124
.Build();
107-
125+
var metadata = new DecisionMetadata("experiment", "experiment_key", "7716830082");
108126
var decision = new Decision("7719770039", "7716830082", "77210100090");
109127
var snapshot = new Snapshot(events: new SnapshotEvent[] { snapshotEvent }, decisions: new Decision[] { decision });
110128

OptimizelySDK.Tests/EventTests/EventFactoryTest.cs

Lines changed: 231 additions & 22 deletions
Large diffs are not rendered by default.

OptimizelySDK.Tests/EventTests/UserEventFactoryTest.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
1-
using Moq;
1+
/**
2+
*
3+
* Copyright 2019-2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using Moq;
219
using NUnit.Framework;
320
using OptimizelySDK.Config;
421
using OptimizelySDK.Entity;
@@ -32,7 +49,7 @@ public void ImpressionEventTest()
3249
var variation = Config.GetVariationFromId(experiment.Key, "77210100090");
3350
var userId = TestUserId;
3451

35-
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, null);
52+
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, null, "test_experiment", "experiment");
3653

3754
Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId);
3855
Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision);
@@ -58,7 +75,7 @@ public void ImpressionEventTestWithAttributes()
5875
{ "company", "Optimizely" }
5976
};
6077

61-
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, userAttributes);
78+
var impressionEvent = UserEventFactory.CreateImpressionEvent(projectConfig, experiment, variation, userId, userAttributes, "test_experiment", "experiment");
6279

6380
Assert.AreEqual(Config.ProjectId, impressionEvent.Context.ProjectId);
6481
Assert.AreEqual(Config.Revision, impressionEvent.Context.Revision);

OptimizelySDK.Tests/OptimizelyTest.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ public void TestActivateNoAudienceNoAttributes()
395395
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "User [user_1] is in experiment [group_experiment_1] of group [7722400015]."), Times.Once);
396396
LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once);
397397
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);
398-
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
398+
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
399399

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

435435
Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation));
436436
}
@@ -490,7 +490,7 @@ public void TestActivateWithTypedAttributes()
490490

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

495495
Assert.IsTrue(TestData.CompareObjects(VariationWithKeyControl, variation));
496496
}
@@ -683,8 +683,8 @@ public void TestInvalidDispatchImpressionEvent()
683683

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

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

@@ -1079,7 +1079,7 @@ public void TestActivateNoAudienceNoAttributesAfterSetForcedVariation()
10791079
LoggerMock.Verify(l => l.Log(LogLevel.DEBUG, "Assigned bucket [9525] to user [user_1] with bucketing ID [user_1]."), Times.Once);
10801080
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);
10811081
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null."), Times.Once);
1082-
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
1082+
LoggerMock.Verify(l => l.Log(LogLevel.INFO, "Activating user user_1 in experiment group_experiment_1."), Times.Once);
10831083

10841084
Assert.IsTrue(TestData.CompareObjects(GroupVariation, variation));
10851085
}

OptimizelySDK.Tests/ProjectConfigTest.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public void TestInit()
6363
Assert.AreEqual("7720880029", Config.ProjectId);
6464
// Check Revision
6565
Assert.AreEqual("15", Config.Revision);
66+
// Check SendFlagDecision
67+
Assert.IsTrue(Config.SendFlagDecisions);
6668

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

420+
421+
[Test]
422+
public void TestIfSendFlagDecisionKeyIsMissingItShouldReturnFalse()
423+
{
424+
var tempConfig = DatafileProjectConfig.Create(TestData.SimpleABExperimentsDatafile, LoggerMock.Object, ErrorHandlerMock.Object);
425+
Assert.IsFalse(tempConfig.SendFlagDecisions);
426+
}
427+
418428
[Test]
419429
public void TestGetAccountId()
420430
{

OptimizelySDK.Tests/TestData.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@
565565
"key": "integer_variable",
566566
"type": "integer",
567567
"defaultValue": "7"
568-
}
568+
}
569569
]
570570
},
571571
{
@@ -940,5 +940,6 @@
940940
],
941941
"revision": "15",
942942
"anonymizeIP": false,
943+
"sendFlagDecisions": true,
943944
"botFiltering": true
944945
}

OptimizelySDK/Config/DatafileProjectConfig.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public enum OPTLYSDKVersion
6969
/// </summary>
7070
public string Revision { get; set; }
7171

72+
/// <summary>
73+
/// SendFlagDecisions determines whether impressions events are sent for ALL decision types.
74+
/// </summary>
75+
public bool SendFlagDecisions { get; set; }
76+
7277
/// <summary>
7378
/// Allow Anonymize IP by truncating the last block of visitors' IP address.
7479
/// </summary>

OptimizelySDK/Event/Builder/Params.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017, 2019, Optimizely
2+
* Copyright 2017, 2019-2020, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
using OptimizelySDK.Event.Entity;
1617
using System;
1718

1819
namespace OptimizelySDK.Event.Builder
@@ -30,6 +31,7 @@ public static class Params
3031
public const string ENTITY_ID = "entity_id";
3132
public const string EVENTS = "events";
3233
public const string EXPERIMENT_ID = "experiment_id";
34+
public const string METADATA = "metadata";
3335
public const string PROJECT_ID = "project_id";
3436
public const string REVISION = "revision";
3537
public const string TIME = "timestamp";

OptimizelySDK/Event/Entity/Decision.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019, Optimizely
2+
* Copyright 2019-2020, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,15 +23,17 @@ public class Decision
2323
public string CampaignId { get; private set; }
2424
[JsonProperty("experiment_id")]
2525
public string ExperimentId { get; private set; }
26+
[JsonProperty("metadata")]
27+
public DecisionMetadata Metadata { get; private set; }
2628
[JsonProperty("variation_id")]
2729
public string VariationId { get; private set; }
28-
2930
public Decision() {}
3031

31-
public Decision(string campaignId, string experimentId, string variationId)
32+
public Decision(string campaignId, string experimentId, string variationId, DecisionMetadata metadata = null)
3233
{
3334
CampaignId = campaignId;
3435
ExperimentId = experimentId;
36+
Metadata = metadata;
3537
VariationId = variationId;
3638
}
3739
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using Newtonsoft.Json;
19+
20+
namespace OptimizelySDK.Event.Entity
21+
{
22+
/// <summary>
23+
/// DecisionMetadata captures additional information regarding the decision
24+
/// </summary>
25+
public class DecisionMetadata
26+
{
27+
[JsonProperty("flag_key")]
28+
public string FlagKey { get; private set; }
29+
[JsonProperty("rule_key")]
30+
public string RuleKey { get; private set; }
31+
[JsonProperty("rule_type")]
32+
public string RuleType { get; private set; }
33+
[JsonProperty("variation_key")]
34+
public string VariationKey { get; private set; }
35+
36+
public DecisionMetadata(string flagKey, string ruleKey, string ruleType, string variationKey = "")
37+
{
38+
FlagKey = flagKey;
39+
RuleKey = ruleKey;
40+
RuleType = ruleType;
41+
VariationKey = variationKey;
42+
}
43+
}
44+
}

OptimizelySDK/Event/Entity/ImpressionEvent.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019, Optimizely
2+
* Copyright 2019-2020, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ public class ImpressionEvent : UserEvent
2828
public VisitorAttribute[] VisitorAttributes { get; private set; }
2929

3030
public Experiment Experiment { get; set; }
31+
public DecisionMetadata Metadata { get; set; }
3132
public Variation Variation { get; set; }
3233
public bool? BotFiltering { get; set; }
3334

@@ -42,6 +43,7 @@ public class Builder
4243
public VisitorAttribute[] VisitorAttributes;
4344
private Experiment Experiment;
4445
private Variation Variation;
46+
private DecisionMetadata Metadata;
4547
private bool? BotFiltering;
4648

4749
public Builder WithUserId(string userId)
@@ -65,6 +67,13 @@ public Builder WithExperiment(Experiment experiment)
6567
return this;
6668
}
6769

70+
public Builder WithMetadata(DecisionMetadata metadata)
71+
{
72+
Metadata = metadata;
73+
74+
return this;
75+
}
76+
6877
public Builder WithVisitorAttributes(VisitorAttribute[] visitorAttributes)
6978
{
7079
VisitorAttributes = visitorAttributes;
@@ -102,6 +111,7 @@ public ImpressionEvent Build()
102111
impressionEvent.VisitorAttributes = VisitorAttributes;
103112
impressionEvent.UserId = UserId;
104113
impressionEvent.Variation = Variation;
114+
impressionEvent.Metadata = Metadata;
105115
impressionEvent.BotFiltering = BotFiltering;
106116

107117
return impressionEvent;

OptimizelySDK/Event/EventFactory.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019, Optimizely
2+
* Copyright 2019-2020, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -115,11 +115,12 @@ private static Visitor CreateVisitor(ImpressionEvent impressionEvent) {
115115

116116
Decision decision = new Decision(impressionEvent.Experiment?.LayerId,
117117
impressionEvent.Experiment?.Id,
118-
impressionEvent.Variation?.Id);
118+
impressionEvent.Variation?.Id,
119+
impressionEvent.Metadata);
119120

120121
SnapshotEvent snapshotEvent = new SnapshotEvent.Builder()
121122
.WithUUID(impressionEvent.UUID)
122-
.WithEntityId(impressionEvent.Experiment.LayerId)
123+
.WithEntityId(impressionEvent.Experiment?.LayerId)
123124
.WithKey(ACTIVATE_EVENT_KEY)
124125
.WithTimeStamp(impressionEvent.Timestamp)
125126
.Build();

0 commit comments

Comments
 (0)