From 3bd25d5087e0d3d00b2c882b8b3743e6c4ced5c8 Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Mon, 4 Jun 2018 13:40:48 -0700 Subject: [PATCH 1/6] bot filtering --- OptimizelySDK.Tests/DecisionServiceTest.cs | 4 +- .../EventTests/EventBuilderTest.cs | 493 +++++++++++++++++- OptimizelySDK.Tests/OptimizelyTest.cs | 5 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 54 +- OptimizelySDK.Tests/TestData.json | 3 +- OptimizelySDK/Bucketing/DecisionService.cs | 6 +- OptimizelySDK/Event/Builder/EventBuilder.cs | 44 +- OptimizelySDK/Logger/ILogger.cs | 1 + OptimizelySDK/ProjectConfig.cs | 36 +- 9 files changed, 598 insertions(+), 48 deletions(-) diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index b0c780fb..a83eaae5 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -388,13 +388,13 @@ public void TestGetVariationWithBucketingId() {"device_type", "iPhone"}, {"company", "Optimizely"}, {"location", "San Francisco"}, - {DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID, testBucketingIdVariation} + {DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} }; var invalidUserAttributesWithBucketingId = new UserAttributes { {"company", "Optimizely"}, - {DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID, testBucketingIdControl} + {DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} }; var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index 1329619c..a740beda 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -1,3 +1,19 @@ +/* + * Copyright 2017-2018, Optimizely + * + * 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 OptimizelySDK.Entity; using OptimizelySDK.Logger; using Moq; @@ -16,7 +32,7 @@ public class EventBuilderTest private string TestUserId = string.Empty; private ProjectConfig Config; private EventBuilder EventBuilder; - + [TestFixtureSetUp] public void Setup() { @@ -66,7 +82,17 @@ public void TestCreateImpressionEventNoAttributes() } } }, - {"attributes", new object[] { }}, + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + }, {"visitor_id", TestUserId} } } @@ -143,6 +169,13 @@ public void TestCreateImpressionEventWithAttributes() {"key", "device_type" }, {"type", "custom" }, {"value", "iPhone"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } } } }, @@ -221,7 +254,17 @@ public void TestCreateConversionEventNoAttributesNoValue() } }, {"visitor_id", TestUserId }, - {"attributes", new object[] {}} + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + } } } }, @@ -302,6 +345,13 @@ public void TestCreateConversionEventWithAttributesNoValue() {"key", "device_type" }, {"type", "custom" }, {"value", "iPhone"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } } } } @@ -328,7 +378,7 @@ public void TestCreateConversionEventWithAttributesNoValue() var userAttributes = new UserAttributes { { "device_type", "iPhone" }, - {"company", "Optimizely" } + { "company", "Optimizely" } }; var experimentToVariationMap = new Dictionary { @@ -388,7 +438,17 @@ public void TestCreateConversionEventNoAttributesWithValue() } } }, - { "attributes", new object[]{ } }, + { "attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + }, { "visitor_id", TestUserId } } } @@ -483,6 +543,13 @@ public void TestCreateConversionEventWithAttributesWithValue() {"key", "device_type" }, {"type", "custom" }, {"value", "iPhone"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } } } }, @@ -579,7 +646,17 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() } }, { "visitor_id", TestUserId }, - { "attributes", new object[]{ } } + { "attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + } } } }, @@ -666,7 +743,17 @@ public void TestConversionEventWithNumericTag() } } }, - { "attributes", new object[]{ } }, + { "attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + }, { "visitor_id", TestUserId } } } @@ -756,10 +843,17 @@ public void TestCreateConversionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", "$opt_bucketing_id" }, - {"key", "optimizely_bucketing_id" }, + {"entity_id", DecisionService.BUCKETING_ID_ATTRIBUTE }, + {"key", DecisionService.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } } } }, @@ -788,7 +882,7 @@ public void TestCreateConversionEventWithBucketingIDAttribute() { { "device_type", "iPhone"}, {"company", "Optimizely" }, - {DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID, "variation" } + {DecisionService.BUCKETING_ID_ATTRIBUTE, "variation" } }; var experimentToVariationMap = new Dictionary @@ -854,10 +948,17 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", "$opt_bucketing_id" }, - {"key", "optimizely_bucketing_id" }, + {"entity_id", DecisionService.BUCKETING_ID_ATTRIBUTE }, + {"key", DecisionService.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } } } }, @@ -885,7 +986,7 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() { { "device_type", "iPhone" }, { "company", "Optimizely" }, - {DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID, "variation" } + {DecisionService.BUCKETING_ID_ATTRIBUTE, "variation" } }; var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); @@ -894,5 +995,371 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); } + + [Test] + public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() + { + var guid = Guid.NewGuid(); + var timeStamp = TestData.SecondsSince1970(); + + var payloadParams = new Dictionary + { + { "visitors", new object[] + { + new Dictionary() + { + { "snapshots", new object[] + { + new Dictionary + { + { "decisions", new object[] + { + new Dictionary + { + {"campaign_id", "7719770039" }, + {"experiment_id", "7716830082" }, + {"variation_id", "7722370027" } + } + } + }, + { "events", new object[] + { + new Dictionary + { + {"entity_id", "7719770039" }, + {"timestamp", timeStamp }, + {"uuid", guid }, + {"key", "campaign_activated" } + } + } + } + } + } + }, + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"type", "custom" }, + {"value", "chrome"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + }, + { "visitor_id", TestUserId } + } + } + }, + {"project_id", "7720880029" }, + {"account_id", "1592310167" }, + {"client_name", "csharp-sdk" }, + {"client_version", Optimizely.SDK_VERSION }, + {"revision", 15 }, + {"anonymize_ip", false} + }; + + var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json" } + }); + + var userAttributes = new UserAttributes + { + {EventBuilder.USER_AGENT_ATTRIBUTE, "chrome" } + }; + + var botFilteringEnabledConfig = Config; + botFilteringEnabledConfig.BotFiltering = true; + var experiment = botFilteringEnabledConfig.GetExperimentFromKey("test_experiment"); + + var logEvent = EventBuilder.CreateImpressionEvent(botFilteringEnabledConfig, experiment, "7722370027", TestUserId, userAttributes); + TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); + + Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); + } + + [Test] + public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() + { + var guid = Guid.NewGuid(); + var timeStamp = TestData.SecondsSince1970(); + + var payloadParams = new Dictionary + { + { "visitors", new object[] + { + new Dictionary() + { + { "snapshots", new object[] + { + new Dictionary + { + { "decisions", new object[] + { + new Dictionary + { + {"campaign_id", "7719770039" }, + {"experiment_id", "7716830082" }, + {"variation_id", "7722370027" } + } + } + }, + { "events", new object[] + { + new Dictionary + { + {"entity_id", "7719770039" }, + {"timestamp", timeStamp }, + {"uuid", guid }, + {"key", "campaign_activated" } + } + } + } + } + } + }, + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"type", "custom" }, + {"value", "chrome"} + } + } + }, + { "visitor_id", TestUserId } + } + } + }, + {"project_id", "7720880029" }, + {"account_id", "1592310167" }, + {"client_name", "csharp-sdk" }, + {"client_version", Optimizely.SDK_VERSION }, + {"revision", 15 }, + {"anonymize_ip", false} + }; + + var expectedLogEvent = new LogEvent("https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json" } + }); + + var userAttributes = new UserAttributes + { + {EventBuilder.USER_AGENT_ATTRIBUTE, "chrome" } + }; + + var botFilteringDisabledConfig = Config; + botFilteringDisabledConfig.BotFiltering = null; + var experiment = botFilteringDisabledConfig.GetExperimentFromKey("test_experiment"); + + var logEvent = EventBuilder.CreateImpressionEvent(botFilteringDisabledConfig, experiment, "7722370027", TestUserId, userAttributes); + TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); + + Assert.IsTrue(TestData.CompareObjects(expectedLogEvent, logEvent)); + } + + [Test] + public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() + { + var guid = Guid.NewGuid(); + var timeStamp = TestData.SecondsSince1970(); + + var payloadParams = new Dictionary + { + {"visitors", new object[] + { + new Dictionary + { + {"snapshots", new object[] + { + new Dictionary + { + { "decisions", new object[] + { + new Dictionary + { + {"campaign_id", "7719770039"}, + {"experiment_id", "7716830082"}, + {"variation_id", "7722370027"} + } + } + }, + {"events", new object[] + { + new Dictionary + { + {"entity_id", "7718020063"}, + {"timestamp", timeStamp}, + {"uuid", guid}, + {"key", "purchase"}, + } + } + } + } + } + }, + {"visitor_id", TestUserId }, + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"type", "custom" }, + {"value", "safari"} + }, + new Dictionary + { + {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"type", "custom" }, + {"value", true } + } + } + } + } + } + }, + {"project_id", "7720880029"}, + {"account_id", "1592310167"}, + {"client_name", "csharp-sdk"}, + {"client_version", Optimizely.SDK_VERSION }, + {"revision", 15 }, + {"anonymize_ip", false} + }; + + var expectedEvent = new LogEvent( + "https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json"} + }); + + var userAttributes = new UserAttributes + { + {EventBuilder.USER_AGENT_ATTRIBUTE, "safari" } + }; + var experimentToVariationMap = new Dictionary + { + {"7716830082", new Variation{Id="7722370027", Key="control"} } + }; + + var botFilteringEnabledConfig = Config; + botFilteringEnabledConfig.BotFiltering = true; + var logEvent = EventBuilder.CreateConversionEvent(botFilteringEnabledConfig, "purchase", experimentToVariationMap, TestUserId, userAttributes, null); + + TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); + + Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); + } + + [Test] + public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() + { + var guid = Guid.NewGuid(); + var timeStamp = TestData.SecondsSince1970(); + + var payloadParams = new Dictionary + { + {"visitors", new object[] + { + new Dictionary + { + {"snapshots", new object[] + { + new Dictionary + { + { "decisions", new object[] + { + new Dictionary + { + {"campaign_id", "7719770039"}, + {"experiment_id", "7716830082"}, + {"variation_id", "7722370027"} + } + } + }, + {"events", new object[] + { + new Dictionary + { + {"entity_id", "7718020063"}, + {"timestamp", timeStamp}, + {"uuid", guid}, + {"key", "purchase"}, + } + } + } + } + } + }, + {"visitor_id", TestUserId }, + {"attributes", new object[] + { + new Dictionary + { + {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"type", "custom" }, + {"value", "safari"} + } + } + } + } + } + }, + {"project_id", "7720880029"}, + {"account_id", "1592310167"}, + {"client_name", "csharp-sdk"}, + {"client_version", Optimizely.SDK_VERSION }, + {"revision", 15 }, + {"anonymize_ip", false} + }; + + var expectedEvent = new LogEvent( + "https://logx.optimizely.com/v1/events", + payloadParams, + "POST", + new Dictionary + { + { "Content-Type", "application/json"} + }); + + var userAttributes = new UserAttributes + { + {EventBuilder.USER_AGENT_ATTRIBUTE, "safari" } + }; + var experimentToVariationMap = new Dictionary + { + {"7716830082", new Variation{Id="7722370027", Key="control"} } + }; + + var botFilteringDisabledConfig = Config; + botFilteringDisabledConfig.BotFiltering = null; + var logEvent = EventBuilder.CreateConversionEvent(botFilteringDisabledConfig, "purchase", experimentToVariationMap, TestUserId, userAttributes, null); + + TestData.ChangeGUIDAndTimeStamp(logEvent.Params, timeStamp, guid); + + Assert.IsTrue(TestData.CompareObjects(expectedEvent, logEvent)); + } } } diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 68e5d3ac..351072e0 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -515,8 +515,7 @@ public void TestTrackInvalidAttributes() Optimizely.Track("purchase", TestUserId, attributes); - //LoggerMock.Verify(l => l.Log(LogLevel.ERROR, "Provided attributes are in an invalid format."), Times.Once); - ErrorHandlerMock.Verify(e => e.HandleError(It.IsAny()), Times.Once); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Attribute key ""abc"" is not in datafile."), Times.Once); } [Test] @@ -1111,7 +1110,7 @@ public void TestGetVariationBucketingIdAttribute() { "device_type", "iPhone" }, { "company", "Optimizely" }, { "location", "San Francisco" }, - { DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID, testBucketingIdVariation } + { DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation } }; // confirm that a valid variation is bucketed without the bucketing ID diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 18fa24d8..23a57228 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017, Optimizely + * Copyright 2017-2018, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ using OptimizelySDK.Exceptions; using NUnit.Framework; using OptimizelySDK.Entity; +using Newtonsoft.Json; +using OptimizelySDK.Event.Builder; namespace OptimizelySDK.Tests { @@ -810,5 +812,55 @@ public void TestVariationFeatureEnabledProperty() var variation = Config.GetVariationFromKey("test_experiment", "control"); Assert.IsFalse(variation.IsFeatureEnabled); } + + [Test] + public void TestBotFilteringValues() + { + // Verify that bot filtering value is true as defined in Config data. + Assert.True(Config.BotFiltering.GetValueOrDefault()); + + // Remove botFilering node and verify returned value in null. + JObject projConfig = JObject.Parse(TestData.Datafile); + if (projConfig.TryGetValue("botFiltering", out JToken token)) + { + projConfig.Property("botFiltering").Remove(); + var configWithoutBotFilter = ProjectConfig.Create(JsonConvert.SerializeObject(projConfig), + LoggerMock.Object, ErrorHandlerMock.Object); + + // Verify that bot filtering is null when not defined in datafile. + Assert.Null(configWithoutBotFilter.BotFiltering); + } + } + + [Test] + public void TestGetAttributeIdWithReservedPrefix() + { + // Verify that attribute key is returned for reserved attribute key. + Assert.AreEqual(Config.GetAttributeId(EventBuilder.USER_AGENT_ATTRIBUTE), EventBuilder.USER_AGENT_ATTRIBUTE); + + // Verify that attribute Id is returned for attribute key with reserved prefix that does not exist in datafile. + Assert.AreEqual(Config.GetAttributeId(EventBuilder.USER_AGENT_ATTRIBUTE), EventBuilder.USER_AGENT_ATTRIBUTE); + + // Create config file copy with additional resered prefix attribute. + string reservedPrefixAttrKey = "$opt_user_defined_attribute"; + JObject projConfig = JObject.Parse(TestData.Datafile); + var attributes = (JArray)projConfig["attributes"]; + + var reservedAttr = new Entity.Attribute { Id = "7723348204", Key = reservedPrefixAttrKey }; + attributes.Add((JObject)JToken.FromObject(reservedAttr)); + + // Verify that attribute Id is returned and warning is logged for attribute key with reserved prefix that exists in datafile. + var reservedAttrConfig = ProjectConfig.Create(JsonConvert.SerializeObject(projConfig), LoggerMock.Object, ErrorHandlerMock.Object); + Assert.AreEqual(reservedAttrConfig.GetAttributeId(reservedPrefixAttrKey), reservedAttrConfig.GetAttribute(reservedPrefixAttrKey).Id); + LoggerMock.Verify(l => l.Log(LogLevel.WARN, $@"Attribute {reservedPrefixAttrKey} unexpectedly has reserved prefix {ProjectConfig.RESERVED_ATTRIBUTE_PREFIX}; using attribute ID instead of reserved attribute name.")); + } + + [Test] + public void TestGetAttributeIdWithInvalidAttributeKey() + { + // Verify that null is returned when provided attribute key is invalid. + Assert.Null(Config.GetAttributeId("invalid_attribute")); + LoggerMock.Verify(l => l.Log(LogLevel.ERROR, @"Attribute key ""invalid_attribute"" is not in datafile.")); + } } } diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index 500ab885..d413a4fb 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -634,5 +634,6 @@ "key": "purchase" }], "revision": "15", - "anonymizeIP": "false" + "anonymizeIP": "false", + "botFiltering": "true" } \ No newline at end of file diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index 8bfda13d..a28ee9be 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -35,7 +35,7 @@ namespace OptimizelySDK.Bucketing /// public class DecisionService { - public const string RESERVED_ATTRIBUTE_KEY_BUCKETING_ID = "$opt_bucketing_id"; + public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; private Bucketer Bucketer; private IErrorHandler ErrorHandler; @@ -401,9 +401,9 @@ private string GetBucketingId(string userId, UserAttributes filteredAttributes) string bucketingId = userId; // If the bucketing ID key is defined in attributes, then use that in place of the userID for the murmur hash key - if (filteredAttributes != null && filteredAttributes.ContainsKey(RESERVED_ATTRIBUTE_KEY_BUCKETING_ID)) + if (filteredAttributes != null && filteredAttributes.ContainsKey(BUCKETING_ID_ATTRIBUTE)) { - bucketingId = filteredAttributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID]; + bucketingId = filteredAttributes[BUCKETING_ID_ATTRIBUTE]; Logger.Log(LogLevel.DEBUG, string.Format("Setting the bucketing ID to \"{0}\"", bucketingId)); } diff --git a/OptimizelySDK/Event/Builder/EventBuilder.cs b/OptimizelySDK/Event/Builder/EventBuilder.cs index f613f370..27ea0244 100644 --- a/OptimizelySDK/Event/Builder/EventBuilder.cs +++ b/OptimizelySDK/Event/Builder/EventBuilder.cs @@ -35,7 +35,9 @@ public class EventBuilder private const string ACTIVATE_EVENT_KEY = "campaign_activated"; - public const string RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY = "optimizely_bucketing_id"; + public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; + + public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; private static readonly Dictionary HTTP_HEADERS = new Dictionary { @@ -103,37 +105,31 @@ private Dictionary GetCommonParams(ProjectConfig config, string foreach (var userAttribute in userAttributes.Where(a => !string.IsNullOrEmpty(a.Key))) { - if (string.Equals(userAttribute.Key, DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID)) + var attributeId = config.GetAttributeId(userAttribute.Key); + if (!string.IsNullOrEmpty(attributeId)) { - var userFeature = new Dictionary + userFeatures.Add(new Dictionary { - { "entity_id", DecisionService.RESERVED_ATTRIBUTE_KEY_BUCKETING_ID }, - { "key", RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY }, + { "entity_id", attributeId }, + { "key", userAttribute.Key }, { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, { "value", userAttribute.Value} - }; - userFeatures.Add(userFeature); - } - else - { - var attributeEntity = config.GetAttribute(userAttribute.Key); - if (attributeEntity != null && attributeEntity.Key != null) - { - var userFeature = new Dictionary - { - { "entity_id", attributeEntity.Id }, - { "key", attributeEntity.Key }, - { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, - { "value", userAttribute.Value} - }; - userFeatures.Add(userFeature); - } + }); } } - + if (config.BotFiltering.HasValue) + { + userFeatures.Add(new Dictionary + { + { "entity_id", BOT_FILTERING_ATTRIBUTE }, + { "key", BOT_FILTERING_ATTRIBUTE }, + { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, + { "value", config.BotFiltering} + }); + } + visitor["attributes"] = userFeatures; - return comonParams; } diff --git a/OptimizelySDK/Logger/ILogger.cs b/OptimizelySDK/Logger/ILogger.cs index cce4c6a4..ae0fd6c4 100644 --- a/OptimizelySDK/Logger/ILogger.cs +++ b/OptimizelySDK/Logger/ILogger.cs @@ -20,6 +20,7 @@ public enum LogLevel DEBUG, INFO, ERROR, + WARN, } public interface ILogger diff --git a/OptimizelySDK/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 448a27ec..0540e13e 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017, Optimizely + * Copyright 2017-2018, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ namespace OptimizelySDK { public class ProjectConfig { + public const string RESERVED_ATTRIBUTE_PREFIX = "$opt_"; + /// /// Version of the datafile. /// @@ -53,6 +55,11 @@ public class ProjectConfig /// public bool AnonymizeIP { get; set; } + /// + /// Bot filtering flag. + /// + public bool? BotFiltering { get; set; } + //========================= Mappings =========================== /// @@ -552,5 +559,32 @@ public Rollout GetRolloutFromId(string rolloutId) ErrorHandler.HandleError(new Exceptions.InvalidRolloutException("Provided rollout is not in datafile.")); return new Rollout(); } + + /// + /// Get attribute ID for the provided attribute key + /// + /// Key of the Attribute + /// Attribute ID corresponding to the provided attribute key. Attribute key if it is a reserved attribute + public string GetAttributeId(string attributeKey) + { + + var hasReservedPrefix = attributeKey.StartsWith(RESERVED_ATTRIBUTE_PREFIX); + + if (_AttributeKeyMap.ContainsKey(attributeKey)) + { + var attribute = _AttributeKeyMap[attributeKey]; + if (hasReservedPrefix) + Logger.Log(LogLevel.WARN, $@"Attribute {attributeKey} unexpectedly has reserved prefix {RESERVED_ATTRIBUTE_PREFIX}; using attribute ID instead of reserved attribute name."); + + return attribute.Id; + } + else if (hasReservedPrefix) + { + return attributeKey; + } + + Logger.Log(LogLevel.ERROR, $@"Attribute key ""{attributeKey}"" is not in datafile."); + return null; + } } } From 777f86a2b2cd8c2df7dc0e95d514cf0c07a50f2e Mon Sep 17 00:00:00 2001 From: mfahadahmed Date: Wed, 13 Jun 2018 19:47:11 +0500 Subject: [PATCH 2/6] Fixed code review issues. --- OptimizelySDK.Tests/EventTests/EventBuilderTest.cs | 2 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 4 ++-- OptimizelySDK.Tests/TestData.json | 4 ++-- OptimizelySDK/Logger/ILogger.cs | 4 ++-- OptimizelySDK/ProjectConfig.cs | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index a740beda..39dd92c9 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -32,7 +32,7 @@ public class EventBuilderTest private string TestUserId = string.Empty; private ProjectConfig Config; private EventBuilder EventBuilder; - + [TestFixtureSetUp] public void Setup() { diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 23a57228..aacc6517 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -53,14 +53,14 @@ public static Dictionary CreateDictionary(string name, object en public void TestInit() { // Check Version - Assert.AreEqual(4, Config.Version); + Assert.AreEqual("4", Config.Version); // Check Account ID Assert.AreEqual("1592310167", Config.AccountId); // Check Project ID Assert.AreEqual("7720880029", Config.ProjectId); // Check Revision - Assert.AreEqual(15, Config.Revision); + Assert.AreEqual("15", Config.Revision); // Check Group ID Map var expectedGroupId = CreateDictionary("7722400015", Config.GetGroup("7722400015")); diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index d413a4fb..648b5ec9 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -634,6 +634,6 @@ "key": "purchase" }], "revision": "15", - "anonymizeIP": "false", - "botFiltering": "true" + "anonymizeIP": false, + "botFiltering": true } \ No newline at end of file diff --git a/OptimizelySDK/Logger/ILogger.cs b/OptimizelySDK/Logger/ILogger.cs index ae0fd6c4..13c8828f 100644 --- a/OptimizelySDK/Logger/ILogger.cs +++ b/OptimizelySDK/Logger/ILogger.cs @@ -1,5 +1,5 @@ /* - * Copyright 2017, Optimizely + * Copyright 2017-2018, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ public enum LogLevel { DEBUG, INFO, - ERROR, WARN, + ERROR, } public interface ILogger diff --git a/OptimizelySDK/ProjectConfig.cs b/OptimizelySDK/ProjectConfig.cs index 0540e13e..54f5c45a 100644 --- a/OptimizelySDK/ProjectConfig.cs +++ b/OptimizelySDK/ProjectConfig.cs @@ -30,7 +30,7 @@ public class ProjectConfig /// /// Version of the datafile. /// - public int Version { get; set; } + public string Version { get; set; } /// @@ -48,7 +48,7 @@ public class ProjectConfig /// /// Revision of the dataflie. /// - public int Revision { get; set; } + public string Revision { get; set; } /// /// Allow Anonymize IP by truncating the last block of visitors' IP address. From d660fcdf4b433fba95607558a7f107f333f14d54 Mon Sep 17 00:00:00 2001 From: mfahadahmed Date: Wed, 13 Jun 2018 20:30:40 +0500 Subject: [PATCH 3/6] Fix Unit tests. --- .../EventTests/EventBuilderTest.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index 39dd92c9..4535e06f 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -101,7 +101,7 @@ public void TestCreateImpressionEventNoAttributes() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -187,7 +187,7 @@ public void TestCreateImpressionEventWithAttributes() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -272,7 +272,7 @@ public void TestCreateConversionEventNoAttributesNoValue() {"account_id", "1592310167"}, {"client_name", "csharp-sdk"}, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -362,7 +362,7 @@ public void TestCreateConversionEventWithAttributesNoValue() {"account_id", "1592310167"}, {"client_name", "csharp-sdk"}, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -457,7 +457,7 @@ public void TestCreateConversionEventNoAttributesWithValue() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -561,7 +561,7 @@ public void TestCreateConversionEventWithAttributesWithValue() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -664,7 +664,7 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -762,7 +762,7 @@ public void TestConversionEventWithNumericTag() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -865,7 +865,7 @@ public void TestCreateConversionEventWithBucketingIDAttribute() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -970,7 +970,7 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -1062,7 +1062,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -1148,7 +1148,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() {"account_id", "1592310167" }, {"client_name", "csharp-sdk" }, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -1241,7 +1241,7 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() {"account_id", "1592310167"}, {"client_name", "csharp-sdk"}, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; @@ -1331,7 +1331,7 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() {"account_id", "1592310167"}, {"client_name", "csharp-sdk"}, {"client_version", Optimizely.SDK_VERSION }, - {"revision", 15 }, + {"revision", "15" }, {"anonymize_ip", false} }; From 66396d58e36db6afc79fb5e656b7a30ae1d346d0 Mon Sep 17 00:00:00 2001 From: mfahadahmed Date: Wed, 27 Jun 2018 16:30:05 +0500 Subject: [PATCH 4/6] Fixed code-review defects. --- .../OptimizelySDK.Net35.csproj | 3 + .../OptimizelySDK.Net40.csproj | 3 + .../OptimizelySDK.NetStandard16.csproj | 1 + OptimizelySDK.Tests/DecisionServiceTest.cs | 5 +- .../EventTests/EventBuilderTest.cs | 85 +- OptimizelySDK.Tests/OptimizelyTest.cs | 3 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 5 +- OptimizelySDK.Tests/TestData.json | 1271 ++++++++++------- OptimizelySDK/Bucketing/DecisionService.cs | 6 +- OptimizelySDK/Event/Builder/EventBuilder.cs | 8 +- OptimizelySDK/OptimizelySDK.csproj | 3 +- OptimizelySDK/Utils/ReservedAttribute.cs | 25 + 12 files changed, 806 insertions(+), 612 deletions(-) create mode 100644 OptimizelySDK/Utils/ReservedAttribute.cs diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 5a1dd97d..4b3bbdec 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -147,6 +147,9 @@ Utils\Validator.cs + + + Utils\ReservedAttribute.cs diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 8204ddd6..57ae7135 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -148,6 +148,9 @@ Utils\Validator.cs + + + Utils\ReservedAttribute.cs diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index 2864d637..feb19498 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -48,6 +48,7 @@ + diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index a83eaae5..4eeb5d24 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -22,6 +22,7 @@ using OptimizelySDK.Entity; using NUnit.Framework; using OptimizelySDK.Bucketing; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests { @@ -388,13 +389,13 @@ public void TestGetVariationWithBucketingId() {"device_type", "iPhone"}, {"company", "Optimizely"}, {"location", "San Francisco"}, - {DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} + {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} }; var invalidUserAttributesWithBucketingId = new UserAttributes { {"company", "Optimizely"}, - {DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} + {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} }; var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index 4535e06f..2ce58d1b 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -23,6 +23,7 @@ using System; using NUnit.Framework; using OptimizelySDK.Bucketing; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests.EventTests { @@ -86,8 +87,8 @@ public void TestCreateImpressionEventNoAttributes() { new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -172,8 +173,8 @@ public void TestCreateImpressionEventWithAttributes() }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -258,8 +259,8 @@ public void TestCreateConversionEventNoAttributesNoValue() { new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -348,8 +349,8 @@ public void TestCreateConversionEventWithAttributesNoValue() }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -442,8 +443,8 @@ public void TestCreateConversionEventNoAttributesWithValue() { new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -546,8 +547,8 @@ public void TestCreateConversionEventWithAttributesWithValue() }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -650,8 +651,8 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() { new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -747,8 +748,8 @@ public void TestConversionEventWithNumericTag() { new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -843,15 +844,15 @@ public void TestCreateConversionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", DecisionService.BUCKETING_ID_ATTRIBUTE }, - {"key", DecisionService.BUCKETING_ID_ATTRIBUTE }, + {"entity_id", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, + {"key", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -882,7 +883,7 @@ public void TestCreateConversionEventWithBucketingIDAttribute() { { "device_type", "iPhone"}, {"company", "Optimizely" }, - {DecisionService.BUCKETING_ID_ATTRIBUTE, "variation" } + {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, "variation" } }; var experimentToVariationMap = new Dictionary @@ -948,15 +949,15 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", DecisionService.BUCKETING_ID_ATTRIBUTE }, - {"key", DecisionService.BUCKETING_ID_ATTRIBUTE }, + {"entity_id", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, + {"key", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -986,7 +987,7 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() { { "device_type", "iPhone" }, { "company", "Optimizely" }, - {DecisionService.BUCKETING_ID_ATTRIBUTE, "variation" } + {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, "variation" } }; var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); @@ -1040,15 +1041,15 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() { new Dictionary { - {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, - {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "chrome"} }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -1076,7 +1077,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() var userAttributes = new UserAttributes { - {EventBuilder.USER_AGENT_ATTRIBUTE, "chrome" } + {ReservedAttribute.USER_AGENT_ATTRIBUTE, "chrome" } }; var botFilteringEnabledConfig = Config; @@ -1133,8 +1134,8 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() { new Dictionary { - {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, - {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "chrome"} } @@ -1162,7 +1163,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() var userAttributes = new UserAttributes { - {EventBuilder.USER_AGENT_ATTRIBUTE, "chrome" } + {ReservedAttribute.USER_AGENT_ATTRIBUTE, "chrome" } }; var botFilteringDisabledConfig = Config; @@ -1220,15 +1221,15 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() { new Dictionary { - {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, - {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "safari"} }, new Dictionary { - {"entity_id", EventBuilder.BOT_FILTERING_ATTRIBUTE}, - {"key", EventBuilder.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -1256,7 +1257,7 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() var userAttributes = new UserAttributes { - {EventBuilder.USER_AGENT_ATTRIBUTE, "safari" } + {ReservedAttribute.USER_AGENT_ATTRIBUTE, "safari" } }; var experimentToVariationMap = new Dictionary { @@ -1317,8 +1318,8 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() { new Dictionary { - {"entity_id", EventBuilder.USER_AGENT_ATTRIBUTE }, - {"key", EventBuilder.USER_AGENT_ATTRIBUTE }, + {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "safari"} } @@ -1346,7 +1347,7 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() var userAttributes = new UserAttributes { - {EventBuilder.USER_AGENT_ATTRIBUTE, "safari" } + {ReservedAttribute.USER_AGENT_ATTRIBUTE, "safari" } }; var experimentToVariationMap = new Dictionary { diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 351072e0..40a82b45 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -28,6 +28,7 @@ using OptimizelySDK.Bucketing; using OptimizelySDK.Notifications; using OptimizelySDK.Tests.NotificationTests; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests { @@ -1110,7 +1111,7 @@ public void TestGetVariationBucketingIdAttribute() { "device_type", "iPhone" }, { "company", "Optimizely" }, { "location", "San Francisco" }, - { DecisionService.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation } + { ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation } }; // confirm that a valid variation is bucketed without the bucketing ID diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index aacc6517..7a5e731f 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -24,6 +24,7 @@ using OptimizelySDK.Entity; using Newtonsoft.Json; using OptimizelySDK.Event.Builder; +using OptimizelySDK.Utils; namespace OptimizelySDK.Tests { @@ -836,10 +837,10 @@ public void TestBotFilteringValues() public void TestGetAttributeIdWithReservedPrefix() { // Verify that attribute key is returned for reserved attribute key. - Assert.AreEqual(Config.GetAttributeId(EventBuilder.USER_AGENT_ATTRIBUTE), EventBuilder.USER_AGENT_ATTRIBUTE); + Assert.AreEqual(Config.GetAttributeId(ReservedAttribute.USER_AGENT_ATTRIBUTE), ReservedAttribute.USER_AGENT_ATTRIBUTE); // Verify that attribute Id is returned for attribute key with reserved prefix that does not exist in datafile. - Assert.AreEqual(Config.GetAttributeId(EventBuilder.USER_AGENT_ATTRIBUTE), EventBuilder.USER_AGENT_ATTRIBUTE); + Assert.AreEqual(Config.GetAttributeId("$opt_reserved_prefix_attribute"), "$opt_reserved_prefix_attribute"); // Create config file copy with additional resered prefix attribute. string reservedPrefixAttrKey = "$opt_user_defined_attribute"; diff --git a/OptimizelySDK.Tests/TestData.json b/OptimizelySDK.Tests/TestData.json index 648b5ec9..04c86ffd 100644 --- a/OptimizelySDK.Tests/TestData.json +++ b/OptimizelySDK.Tests/TestData.json @@ -1,10 +1,12 @@ { "tempproperty": "This is to test additional properties", - "experiments": [{ + "experiments": [ + { "status": "Running", "key": "test_experiment", "layerId": "7719770039", - "trafficAllocation": [{ + "trafficAllocation": [ + { "entityId": "7722370027", "endOfRange": 4000 }, @@ -13,8 +15,9 @@ "endOfRange": 8000 } ], - "audienceIds": ["7718080042"], - "variations": [{ + "audienceIds": [ "7718080042" ], + "variations": [ + { "id": "7722370027", "key": "control" }, @@ -32,7 +35,8 @@ "status": "Paused", "key": "paused_experiment", "layerId": "7719779139", - "trafficAllocation": [{ + "trafficAllocation": [ + { "entityId": "7722370427", "endOfRange": 5000 }, @@ -42,7 +46,8 @@ } ], "audienceIds": [], - "variations": [{ + "variations": [ + { "id": "7722370427", "key": "control" }, @@ -58,582 +63,740 @@ "key": "test_experiment_multivariate", "status": "Running", "layerId": "4", - "audienceIds": ["11154"], + "audienceIds": [ "11154" ], "id": "122230", "forcedVariations": {}, - "trafficAllocation": [{ - "entityId": "122231", - "endOfRange": 2500 - }, { - "entityId": "122232", - "endOfRange": 5000 - }, { - "entityId": "122233", - "endOfRange": 7500 - }, { - "entityId": "122234", - "endOfRange": 10000 - }], - "variations": [{ - "id": "122231", - "key": "Fred", - "variables": [{ - "id": "155560", - "value": "F" - }, - { - "id": "155561", - "value": "red" - } - ], - "featureEnabled": true - }, { - "id": "122232", - "key": "Feorge", - "variables": [{ - "id": "155560", - "value": "F" - }, - { - "id": "155561", - "value": "eorge" - } - ], - "featureEnabled": false - }, { - "id": "122233", - "key": "Gred", - "variables": [{ - "id": "155560", - "value": "G" - }, - { - "id": "155561", - "value": "red" - } - ] - }, { - "id": "122234", - "key": "George", - "variables": [{ - "id": "155560", - "value": "G" - }, - { - "id": "155561", - "value": "eorge" - } - ], - "featureEnabled": true - }] - }, { + "trafficAllocation": [ + { + "entityId": "122231", + "endOfRange": 2500 + }, + { + "entityId": "122232", + "endOfRange": 5000 + }, + { + "entityId": "122233", + "endOfRange": 7500 + }, + { + "entityId": "122234", + "endOfRange": 10000 + } + ], + "variations": [ + { + "id": "122231", + "key": "Fred", + "variables": [ + { + "id": "155560", + "value": "F" + }, + { + "id": "155561", + "value": "red" + } + ], + "featureEnabled": true + }, + { + "id": "122232", + "key": "Feorge", + "variables": [ + { + "id": "155560", + "value": "F" + }, + { + "id": "155561", + "value": "eorge" + } + ], + "featureEnabled": false + }, + { + "id": "122233", + "key": "Gred", + "variables": [ + { + "id": "155560", + "value": "G" + }, + { + "id": "155561", + "value": "red" + } + ] + }, + { + "id": "122234", + "key": "George", + "variables": [ + { + "id": "155560", + "value": "G" + }, + { + "id": "155561", + "value": "eorge" + } + ], + "featureEnabled": true + } + ] + }, + { "key": "test_experiment_with_feature_rollout", "status": "Running", "layerId": "5", "audienceIds": [], "id": "122235", "forcedVariations": {}, - "trafficAllocation": [{ - "entityId": "122236", - "endOfRange": 5000 - }, { - "entityId": "122237", - "endOfRange": 10000 - }], - "variations": [{ - "id": "122236", - "key": "control", - "variables": [{ - "id": "155558", - "value": "cta_1" - }], - "featureEnabled": true - }, { - "id": "122237", - "key": "variation", - "variables": [{ - "id": "155558", - "value": "cta_2" - }], - "featureEnabled": true - }] - }, { + "trafficAllocation": [ + { + "entityId": "122236", + "endOfRange": 5000 + }, + { + "entityId": "122237", + "endOfRange": 10000 + } + ], + "variations": [ + { + "id": "122236", + "key": "control", + "variables": [ + { + "id": "155558", + "value": "cta_1" + } + ], + "featureEnabled": true + }, + { + "id": "122237", + "key": "variation", + "variables": [ + { + "id": "155558", + "value": "cta_2" + } + ], + "featureEnabled": true + } + ] + }, + { "key": "test_experiment_double_feature", "status": "Running", "layerId": "5", "audienceIds": [], "id": "122238", "forcedVariations": {}, - "trafficAllocation": [{ - "entityId": "122239", - "endOfRange": 5000 - }, { - "entityId": "122240", - "endOfRange": 10000 - }], - "variations": [{ - "id": "122239", - "key": "control", - "variables": [{ - "id": "155551", - "value": "42.42" - }], - "featureEnabled": true - }, { - "id": "122240", - "key": "variation", - "variables": [{ - "id": "155551", - "value": "13.37" - }], - "featureEnabled": false - }] - }, { + "trafficAllocation": [ + { + "entityId": "122239", + "endOfRange": 5000 + }, + { + "entityId": "122240", + "endOfRange": 10000 + } + ], + "variations": [ + { + "id": "122239", + "key": "control", + "variables": [ + { + "id": "155551", + "value": "42.42" + } + ], + "featureEnabled": true + }, + { + "id": "122240", + "key": "variation", + "variables": [ + { + "id": "155551", + "value": "13.37" + } + ], + "featureEnabled": false + } + ] + }, + { "key": "test_experiment_integer_feature", "status": "Running", "layerId": "6", "audienceIds": [], "id": "122241", "forcedVariations": {}, - "trafficAllocation": [{ - "entityId": "122242", - "endOfRange": 5000 - }, { - "entityId": "122243", - "endOfRange": 10000 - }], - "variations": [{ - "id": "122242", - "key": "control", - "variables": [{ - "id": "155553", - "value": "42" - }], - "featureEnabled": false - }, { - "id": "122243", - "key": "variation", - "variables": [{ - "id": "155553", - "value": "13" - }], - "featureEnabled": true - }] - }, { - "id": "223", - "key": "etag1", - "status": "Running", - "layerId": "1", - "percentageIncluded": 9000, - "audienceIds": [], - "variations": [{ - "id": "276", - "key": "vtag1", - "variables": [] - }, { - "id": "277", - "key": "vtag2", - "variables": [] - }], - "forcedVariations": { - "testUser1": "vtag1", - "testUser2": "vtag2" - }, - "trafficAllocation": [{ - "entityId": "276", - "endOfRange": 3500 - }, { - "entityId": "277", - "endOfRange": 9000 - }] - }, { - "id": "118", - "key": "etag2", - "status": "Not started", - "layerId": "2", - "audienceIds": [], - "variations": [{ - "id": "278", - "key": "vtag3", - "variables": [] - }, { - "id": "279", - "key": "vtag4", - "variables": [] - }], - "forcedVariations": {}, - "trafficAllocation": [{ - "entityId": "278", - "endOfRange": 4500 - }, { - "entityId": "279", - "endOfRange": 9000 - }] - }, { - "id": "224", - "key": "etag3", - "status": "Running", - "layerId": "1", - "percentageIncluded": 9000, - "audienceIds": [ - "100" - ], - "variations": [{ - "id": "280", - "key": "vtag5", - "variables": [] - }, { - "id": "281", - "key": "vtag6", - "variables": [] - }], - "forcedVariations": { - "testUser1": "vtag5" - }, - "trafficAllocation": [{ - "entityId": "280", - "endOfRange": 3500 - }, { - "entityId": "281", - "endOfRange": 9000 - }] - }, { - "id": "119", - "key": "etag4", - "status": "Not started", - "layerId": "2", - "audienceIds": [ - "100" - ], - "variations": [{ - "id": "282", - "key": "vtag7", - "variables": [] - }, { - "id": "283", - "key": "vtag8", - "variables": [] - }], - "forcedVariations": { - "testUser3": "vtag7" - }, - "trafficAllocation": [{ - "entityId": "282", - "endOfRange": 4500 - }, { - "entityId": "283", - "endOfRange": 9000 - }] + "trafficAllocation": [ + { + "entityId": "122242", + "endOfRange": 5000 + }, + { + "entityId": "122243", + "endOfRange": 10000 + } + ], + "variations": [ + { + "id": "122242", + "key": "control", + "variables": [ + { + "id": "155553", + "value": "42" + } + ], + "featureEnabled": false + }, + { + "id": "122243", + "key": "variation", + "variables": [ + { + "id": "155553", + "value": "13" + } + ], + "featureEnabled": true + } + ] + }, + { + "id": "223", + "key": "etag1", + "status": "Running", + "layerId": "1", + "percentageIncluded": 9000, + "audienceIds": [], + "variations": [ + { + "id": "276", + "key": "vtag1", + "variables": [] + }, + { + "id": "277", + "key": "vtag2", + "variables": [] + } + ], + "forcedVariations": { + "testUser1": "vtag1", + "testUser2": "vtag2" + }, + "trafficAllocation": [ + { + "entityId": "276", + "endOfRange": 3500 + }, + { + "entityId": "277", + "endOfRange": 9000 + } + ] + }, + { + "id": "118", + "key": "etag2", + "status": "Not started", + "layerId": "2", + "audienceIds": [], + "variations": [ + { + "id": "278", + "key": "vtag3", + "variables": [] + }, + { + "id": "279", + "key": "vtag4", + "variables": [] + } + ], + "forcedVariations": {}, + "trafficAllocation": [ + { + "entityId": "278", + "endOfRange": 4500 + }, + { + "entityId": "279", + "endOfRange": 9000 + } + ] + }, + { + "id": "224", + "key": "etag3", + "status": "Running", + "layerId": "1", + "percentageIncluded": 9000, + "audienceIds": [ + "100" + ], + "variations": [ + { + "id": "280", + "key": "vtag5", + "variables": [] + }, + { + "id": "281", + "key": "vtag6", + "variables": [] + } + ], + "forcedVariations": { + "testUser1": "vtag5" + }, + "trafficAllocation": [ + { + "entityId": "280", + "endOfRange": 3500 + }, + { + "entityId": "281", + "endOfRange": 9000 + } + ] + }, + { + "id": "119", + "key": "etag4", + "status": "Not started", + "layerId": "2", + "audienceIds": [ + "100" + ], + "variations": [ + { + "id": "282", + "key": "vtag7", + "variables": [] + }, + { + "id": "283", + "key": "vtag8", + "variables": [] + } + ], + "forcedVariations": { + "testUser3": "vtag7" + }, + "trafficAllocation": [ + { + "entityId": "282", + "endOfRange": 4500 + }, + { + "entityId": "283", + "endOfRange": 9000 + } + ] } ], "version": "4", - "audiences": [{ - "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"device_type\", \"type\": \"custom_attribute\", \"value\": \"iPhone\"}]], [\"or\", [\"or\", {\"name\": \"location\", \"type\": \"custom_attribute\", \"value\": \"San Francisco\"}]]]", - "id": "7718080042", - "name": "iPhone users in San Francisco" - }, { - "name": "Chrome users", - "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\": \"chrome\"}]]]", - "id": "11154" - }, { - "id": "100", - "name": "not_firefox_users", - "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_dimension\", \"value\":\"firefox\"}]]]]" - }], - "groups": [{ - "policy": "random", - "trafficAllocation": [{ - "entityId": "7723330021", - "endOfRange": 2000 - }, - { - "entityId": "7718750065", - "endOfRange": 6000 - } - ], - "experiments": [{ - "status": "Running", - "key": "group_experiment_1", - "layerId": "7721010011", - "trafficAllocation": [{ - "entityId": "7722260071", - "endOfRange": 5000 - }, - { - "entityId": "7722360022", - "endOfRange": 10000 - } - ], - "audienceIds": [], - "variations": [{ - "id": "7722260071", - "key": "group_exp_1_var_1", - "variables": [{ - "id": "155563", - "value": "groupie_1_v1" - }], - "featureEnabled": true - }, - { - "id": "7722360022", - "key": "group_exp_1_var_2", - "variables": [{ - "id": "155563", - "value": "groupie_1_v2" - }], - "featureEnabled": true - } - ], - "forcedVariations": { - "user1": "group_exp_1_var_1" + "audiences": [ + { + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"device_type\", \"type\": \"custom_attribute\", \"value\": \"iPhone\"}]], [\"or\", [\"or\", {\"name\": \"location\", \"type\": \"custom_attribute\", \"value\": \"San Francisco\"}]]]", + "id": "7718080042", + "name": "iPhone users in San Francisco" + }, + { + "name": "Chrome users", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_attribute\", \"value\": \"chrome\"}]]]", + "id": "11154" + }, + { + "id": "100", + "name": "not_firefox_users", + "conditions": "[\"and\", [\"or\", [\"not\", [\"or\", {\"name\": \"browser_type\", \"type\": \"custom_dimension\", \"value\":\"firefox\"}]]]]" + } + ], + "groups": [ + { + "policy": "random", + "trafficAllocation": [ + { + "entityId": "7723330021", + "endOfRange": 2000 }, - "id": "7723330021" - }, - { - "status": "Running", - "key": "group_experiment_2", - "layerId": "7721020020", - "trafficAllocation": [{ - "entityId": "7713030086", - "endOfRange": 5000 - }, - { - "entityId": "7725250007", - "endOfRange": 10000 - } - ], - "audienceIds": [], - "variations": [{ - "id": "7713030086", - "key": "group_exp_2_var_1", - "variables": [{ - "id": "155563", - "value": "groupie_2_v1" - }], - "featureEnabled": false + { + "entityId": "7718750065", + "endOfRange": 6000 + } + ], + "experiments": [ + { + "status": "Running", + "key": "group_experiment_1", + "layerId": "7721010011", + "trafficAllocation": [ + { + "entityId": "7722260071", + "endOfRange": 5000 + }, + { + "entityId": "7722360022", + "endOfRange": 10000 + } + ], + "audienceIds": [], + "variations": [ + { + "id": "7722260071", + "key": "group_exp_1_var_1", + "variables": [ + { + "id": "155563", + "value": "groupie_1_v1" + } + ], + "featureEnabled": true + }, + { + "id": "7722360022", + "key": "group_exp_1_var_2", + "variables": [ + { + "id": "155563", + "value": "groupie_1_v2" + } + ], + "featureEnabled": true + } + ], + "forcedVariations": { + "user1": "group_exp_1_var_1" }, - { - "id": "7725250007", - "key": "group_exp_2_var_2", - "variables": [{ - "id": "155563", - "value": "groupie_2_v2" - }], - "featureEnabled": false - } - ], - "forcedVariations": {}, - "id": "7718750065" - } - ], - "id": "7722400015" - }], - "featureFlags": [{ - "id": "155549", - "key": "boolean_feature", - "rolloutId": "", - "experimentIds": ["7723330021", "7718750065"], - "variables": [] - }, { - "id": "155550", - "key": "double_single_variable_feature", - "rolloutId": "", - "experimentIds": ["122238"], - "variables": [{ - "id": "155551", - "key": "double_variable", - "type": "double", - "defaultValue": "14.99" - }] - }, { - "id": "155552", - "key": "integer_single_variable_feature", - "rolloutId": "", - "experimentIds": ["122241"], - "variables": [{ - "id": "155553", - "key": "integer_variable", - "type": "integer", - "defaultValue": "7" - }] - }, { - "id": "155554", - "key": "boolean_single_variable_feature", - "rolloutId": "166660", - "experimentIds": [], - "variables": [{ - "id": "155556", - "key": "boolean_variable", - "type": "boolean", - "defaultValue": "true" - }] - }, { - "id": "155557", - "key": "string_single_variable_feature", - "rolloutId": "166661", - "experimentIds": ["122235"], - "variables": [{ - "id": "155558", - "key": "string_variable", - "type": "string", - "defaultValue": "wingardium leviosa" - }] - }, { - "id": "155559", - "key": "multi_variate_feature", - "rolloutId": "", - "experimentIds": ["122230"], - "variables": [{ - "id": "155560", - "key": "first_letter", - "type": "string", - "defaultValue": "H" - }, - { - "id": "155561", - "key": "rest_of_name", - "type": "string", - "defaultValue": "arry" - } - ] - }, { - "id": "155562", - "key": "mutex_group_feature", - "rolloutId": "", - "experimentIds": ["7723330021", "7718750065"], - "variables": [{ - "id": "155563", - "key": "correlating_variation_name", - "type": "string", - "defaultValue": "null" - }] - }, { - "id": "155564", - "key": "empty_feature", - "rolloutId": "", - "experimentIds": [], - "variables": [] - }, { - "id": "155565", - "key": "no_rollout_experiment_feature", - "rolloutId": "166662", - "experimentIds": [], - "variables": [] - }], - "rollouts": [{ - "id": "166660", - "experiments": [{ - "id": "177770", - "key": "177770", - "status": "Running", - "layerId": "166660", - "audienceIds": ["11154"], - "variations": [{ - "id": "177771", - "key": "177771", - "variables": [{ - "id": "155556", - "value": "true" - }], - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "177771", - "endOfRange": 1000 - }] - }, { - "id": "177772", - "key": "177772", - "status": "Running", - "layerId": "166660", - "audienceIds": ["7718080042"], - "variations": [{ - "id": "177773", - "key": "177773", - "variables": [{ - "id": "155556", - "value": "false" - }], - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "177773", - "endOfRange": 10000 - }] - }, { - "id": "177776", - "key": "177776", - "status": "Running", - "layerId": "166660", - "audienceIds": [], - "variations": [{ - "id": "177778", - "key": "177778", - "variables": [{ + "id": "7723330021" + }, + { + "status": "Running", + "key": "group_experiment_2", + "layerId": "7721020020", + "trafficAllocation": [ + { + "entityId": "7713030086", + "endOfRange": 5000 + }, + { + "entityId": "7725250007", + "endOfRange": 10000 + } + ], + "audienceIds": [], + "variations": [ + { + "id": "7713030086", + "key": "group_exp_2_var_1", + "variables": [ + { + "id": "155563", + "value": "groupie_2_v1" + } + ], + "featureEnabled": false + }, + { + "id": "7725250007", + "key": "group_exp_2_var_2", + "variables": [ + { + "id": "155563", + "value": "groupie_2_v2" + } + ], + "featureEnabled": false + } + ], + "forcedVariations": {}, + "id": "7718750065" + } + ], + "id": "7722400015" + } + ], + "featureFlags": [ + { + "id": "155549", + "key": "boolean_feature", + "rolloutId": "", + "experimentIds": [ "7723330021", "7718750065" ], + "variables": [] + }, + { + "id": "155550", + "key": "double_single_variable_feature", + "rolloutId": "", + "experimentIds": [ "122238" ], + "variables": [ + { + "id": "155551", + "key": "double_variable", + "type": "double", + "defaultValue": "14.99" + } + ] + }, + { + "id": "155552", + "key": "integer_single_variable_feature", + "rolloutId": "", + "experimentIds": [ "122241" ], + "variables": [ + { + "id": "155553", + "key": "integer_variable", + "type": "integer", + "defaultValue": "7" + } + ] + }, + { + "id": "155554", + "key": "boolean_single_variable_feature", + "rolloutId": "166660", + "experimentIds": [], + "variables": [ + { "id": "155556", - "value": "false" - }], - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "177778", - "endOfRange": 9000 - }] - }] - }, { - "id": "166661", - "experiments": [{ - "id": "177774", - "key": "177774", - "status": "Running", - "layerId": "166661", - "audienceIds": ["11154"], - "variations": [{ - "id": "177775", - "key": "177775", - "variables": [], - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "177775", - "endOfRange": 1500 - }] - }, { - "id": "177779", - "key": "177779", - "status": "Running", - "layerId": "166661", - "audienceIds": [], - "variations": [{ - "id": "177780", - "key": "177780", - "variables": [], - "featureEnabled": true - }], - "trafficAllocation": [{ - "entityId": "177780", - "endOfRange": 1500 - }] - }] - }], - "attributes": [{ - "id": "7723280020", - "key": "device_type" - }, { - "id": "7723340004", - "key": "location" - }, { - "id": "134", - "key": "browser_type" - }], + "key": "boolean_variable", + "type": "boolean", + "defaultValue": "true" + } + ] + }, + { + "id": "155557", + "key": "string_single_variable_feature", + "rolloutId": "166661", + "experimentIds": [ "122235" ], + "variables": [ + { + "id": "155558", + "key": "string_variable", + "type": "string", + "defaultValue": "wingardium leviosa" + } + ] + }, + { + "id": "155559", + "key": "multi_variate_feature", + "rolloutId": "", + "experimentIds": [ "122230" ], + "variables": [ + { + "id": "155560", + "key": "first_letter", + "type": "string", + "defaultValue": "H" + }, + { + "id": "155561", + "key": "rest_of_name", + "type": "string", + "defaultValue": "arry" + } + ] + }, + { + "id": "155562", + "key": "mutex_group_feature", + "rolloutId": "", + "experimentIds": [ "7723330021", "7718750065" ], + "variables": [ + { + "id": "155563", + "key": "correlating_variation_name", + "type": "string", + "defaultValue": "null" + } + ] + }, + { + "id": "155564", + "key": "empty_feature", + "rolloutId": "", + "experimentIds": [], + "variables": [] + }, + { + "id": "155565", + "key": "no_rollout_experiment_feature", + "rolloutId": "166662", + "experimentIds": [], + "variables": [] + } + ], + "rollouts": [ + { + "id": "166660", + "experiments": [ + { + "id": "177770", + "key": "177770", + "status": "Running", + "layerId": "166660", + "audienceIds": [ "11154" ], + "variations": [ + { + "id": "177771", + "key": "177771", + "variables": [ + { + "id": "155556", + "value": "true" + } + ], + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "177771", + "endOfRange": 1000 + } + ] + }, + { + "id": "177772", + "key": "177772", + "status": "Running", + "layerId": "166660", + "audienceIds": [ "7718080042" ], + "variations": [ + { + "id": "177773", + "key": "177773", + "variables": [ + { + "id": "155556", + "value": "false" + } + ], + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "177773", + "endOfRange": 10000 + } + ] + }, + { + "id": "177776", + "key": "177776", + "status": "Running", + "layerId": "166660", + "audienceIds": [], + "variations": [ + { + "id": "177778", + "key": "177778", + "variables": [ + { + "id": "155556", + "value": "false" + } + ], + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "177778", + "endOfRange": 9000 + } + ] + } + ] + }, + { + "id": "166661", + "experiments": [ + { + "id": "177774", + "key": "177774", + "status": "Running", + "layerId": "166661", + "audienceIds": [ "11154" ], + "variations": [ + { + "id": "177775", + "key": "177775", + "variables": [], + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "177775", + "endOfRange": 1500 + } + ] + }, + { + "id": "177779", + "key": "177779", + "status": "Running", + "layerId": "166661", + "audienceIds": [], + "variations": [ + { + "id": "177780", + "key": "177780", + "variables": [], + "featureEnabled": true + } + ], + "trafficAllocation": [ + { + "entityId": "177780", + "endOfRange": 1500 + } + ] + } + ] + } + ], + "attributes": [ + { + "id": "7723280020", + "key": "device_type" + }, + { + "id": "7723340004", + "key": "location" + }, + { + "id": "134", + "key": "browser_type" + } + ], "projectId": "7720880029", "accountId": "1592310167", - "events": [{ - "experimentIds": ["7716830082", "7723330021", "7718750065", "7716830585"], - "id": "7718020063", - "key": "purchase" - }], + "events": [ + { + "experimentIds": [ "7716830082", "7723330021", "7718750065", "7716830585" ], + "id": "7718020063", + "key": "purchase" + } + ], "revision": "15", "anonymizeIP": false, "botFiltering": true -} \ No newline at end of file +} diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index a28ee9be..5e8860b8 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -35,8 +35,6 @@ namespace OptimizelySDK.Bucketing /// public class DecisionService { - public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; - private Bucketer Bucketer; private IErrorHandler ErrorHandler; private ProjectConfig ProjectConfig; @@ -401,9 +399,9 @@ private string GetBucketingId(string userId, UserAttributes filteredAttributes) string bucketingId = userId; // If the bucketing ID key is defined in attributes, then use that in place of the userID for the murmur hash key - if (filteredAttributes != null && filteredAttributes.ContainsKey(BUCKETING_ID_ATTRIBUTE)) + if (filteredAttributes != null && filteredAttributes.ContainsKey(ReservedAttribute.BUCKETING_ID_ATTRIBUTE)) { - bucketingId = filteredAttributes[BUCKETING_ID_ATTRIBUTE]; + bucketingId = filteredAttributes[ReservedAttribute.BUCKETING_ID_ATTRIBUTE]; Logger.Log(LogLevel.DEBUG, string.Format("Setting the bucketing ID to \"{0}\"", bucketingId)); } diff --git a/OptimizelySDK/Event/Builder/EventBuilder.cs b/OptimizelySDK/Event/Builder/EventBuilder.cs index 27ea0244..fc2cc5a5 100644 --- a/OptimizelySDK/Event/Builder/EventBuilder.cs +++ b/OptimizelySDK/Event/Builder/EventBuilder.cs @@ -35,10 +35,6 @@ public class EventBuilder private const string ACTIVATE_EVENT_KEY = "campaign_activated"; - public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; - - public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; - private static readonly Dictionary HTTP_HEADERS = new Dictionary { { "Content-Type", "application/json" }, @@ -122,8 +118,8 @@ private Dictionary GetCommonParams(ProjectConfig config, string { userFeatures.Add(new Dictionary { - { "entity_id", BOT_FILTERING_ATTRIBUTE }, - { "key", BOT_FILTERING_ATTRIBUTE }, + { "entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE }, + { "key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE }, { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, { "value", config.BotFiltering} }); diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index c423f63b..119fb10d 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -103,6 +103,7 @@ + @@ -124,4 +125,4 @@ --> - + \ No newline at end of file diff --git a/OptimizelySDK/Utils/ReservedAttribute.cs b/OptimizelySDK/Utils/ReservedAttribute.cs new file mode 100644 index 00000000..b2bb9e17 --- /dev/null +++ b/OptimizelySDK/Utils/ReservedAttribute.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2018, Optimizely + * + * 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. + */ + +namespace OptimizelySDK.Utils +{ + public class ReservedAttribute + { + public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; + public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; + public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; + } +} From 0540ccc4714308108955bcd5ec3555aea6f010bf Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Wed, 27 Jun 2018 11:47:47 -0700 Subject: [PATCH 5/6] Renamed from ReservedAttribute to ControlAttributes and alphabetized const strings. --- .../OptimizelySDK.Net35.csproj | 4 +- .../OptimizelySDK.Net40.csproj | 4 +- .../OptimizelySDK.NetStandard16.csproj | 2 +- OptimizelySDK.Tests/DecisionServiceTest.cs | 4 +- .../EventTests/EventBuilderTest.cs | 84 +++++++++---------- OptimizelySDK.Tests/OptimizelyTest.cs | 2 +- OptimizelySDK.Tests/ProjectConfigTest.cs | 2 +- OptimizelySDK/Bucketing/DecisionService.cs | 4 +- OptimizelySDK/Event/Builder/EventBuilder.cs | 4 +- OptimizelySDK/OptimizelySDK.csproj | 2 +- OptimizelySDK/Utils/ControlAttributes.cs | 25 ++++++ 11 files changed, 81 insertions(+), 56 deletions(-) create mode 100644 OptimizelySDK/Utils/ControlAttributes.cs diff --git a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj index 4b3bbdec..c89f5c86 100644 --- a/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj +++ b/OptimizelySDK.Net35/OptimizelySDK.Net35.csproj @@ -148,8 +148,8 @@ Utils\Validator.cs - - Utils\ReservedAttribute.cs + + Utils\ControlAttributes.cs diff --git a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj index 57ae7135..20daf229 100644 --- a/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj +++ b/OptimizelySDK.Net40/OptimizelySDK.Net40.csproj @@ -149,8 +149,8 @@ Utils\Validator.cs - - Utils\ReservedAttribute.cs + + Utils\ControlAttributes.cs diff --git a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj index feb19498..134e5ede 100644 --- a/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj +++ b/OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj @@ -48,7 +48,7 @@ - + diff --git a/OptimizelySDK.Tests/DecisionServiceTest.cs b/OptimizelySDK.Tests/DecisionServiceTest.cs index 4eeb5d24..bc9c67d7 100644 --- a/OptimizelySDK.Tests/DecisionServiceTest.cs +++ b/OptimizelySDK.Tests/DecisionServiceTest.cs @@ -389,13 +389,13 @@ public void TestGetVariationWithBucketingId() {"device_type", "iPhone"}, {"company", "Optimizely"}, {"location", "San Francisco"}, - {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} + {ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation} }; var invalidUserAttributesWithBucketingId = new UserAttributes { {"company", "Optimizely"}, - {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} + {ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdControl} }; var optlyObject = new Optimizely(TestData.Datafile, new ValidEventDispatcher(), LoggerMock.Object); diff --git a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs index 2ce58d1b..f80beef9 100644 --- a/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs +++ b/OptimizelySDK.Tests/EventTests/EventBuilderTest.cs @@ -87,8 +87,8 @@ public void TestCreateImpressionEventNoAttributes() { new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -173,8 +173,8 @@ public void TestCreateImpressionEventWithAttributes() }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -259,8 +259,8 @@ public void TestCreateConversionEventNoAttributesNoValue() { new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -349,8 +349,8 @@ public void TestCreateConversionEventWithAttributesNoValue() }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -443,8 +443,8 @@ public void TestCreateConversionEventNoAttributesWithValue() { new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -547,8 +547,8 @@ public void TestCreateConversionEventWithAttributesWithValue() }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -651,8 +651,8 @@ public void TestCreateConversionEventNoAttributesWithInvalidValue() { new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -748,8 +748,8 @@ public void TestConversionEventWithNumericTag() { new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -844,15 +844,15 @@ public void TestCreateConversionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, - {"key", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, + {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -883,7 +883,7 @@ public void TestCreateConversionEventWithBucketingIDAttribute() { { "device_type", "iPhone"}, {"company", "Optimizely" }, - {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, "variation" } + {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } }; var experimentToVariationMap = new Dictionary @@ -949,15 +949,15 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() }, new Dictionary { - {"entity_id", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, - {"key", ReservedAttribute.BUCKETING_ID_ATTRIBUTE }, + {"entity_id", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, + {"key", ControlAttributes.BUCKETING_ID_ATTRIBUTE }, {"type", "custom" }, {"value", "variation"} }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -987,7 +987,7 @@ public void TestCreateImpressionEventWithBucketingIDAttribute() { { "device_type", "iPhone" }, { "company", "Optimizely" }, - {ReservedAttribute.BUCKETING_ID_ATTRIBUTE, "variation" } + {ControlAttributes.BUCKETING_ID_ATTRIBUTE, "variation" } }; var logEvent = EventBuilder.CreateImpressionEvent(Config, Config.GetExperimentFromKey("test_experiment"), "7722370027", TestUserId, userAttributes); @@ -1041,15 +1041,15 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() { new Dictionary { - {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, - {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "chrome"} }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -1077,7 +1077,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsProvidedInDatafile() var userAttributes = new UserAttributes { - {ReservedAttribute.USER_AGENT_ATTRIBUTE, "chrome" } + {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } }; var botFilteringEnabledConfig = Config; @@ -1134,8 +1134,8 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() { new Dictionary { - {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, - {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "chrome"} } @@ -1163,7 +1163,7 @@ public void TestCreateImpressionEventWhenBotFilteringIsNotProvidedInDatafile() var userAttributes = new UserAttributes { - {ReservedAttribute.USER_AGENT_ATTRIBUTE, "chrome" } + {ControlAttributes.USER_AGENT_ATTRIBUTE, "chrome" } }; var botFilteringDisabledConfig = Config; @@ -1221,15 +1221,15 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() { new Dictionary { - {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, - {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "safari"} }, new Dictionary { - {"entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, - {"key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE}, + {"entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, + {"key", ControlAttributes.BOT_FILTERING_ATTRIBUTE}, {"type", "custom" }, {"value", true } } @@ -1257,7 +1257,7 @@ public void TestCreateConversionEventWhenBotFilteringIsProvidedInDatafile() var userAttributes = new UserAttributes { - {ReservedAttribute.USER_AGENT_ATTRIBUTE, "safari" } + {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } }; var experimentToVariationMap = new Dictionary { @@ -1318,8 +1318,8 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() { new Dictionary { - {"entity_id", ReservedAttribute.USER_AGENT_ATTRIBUTE }, - {"key", ReservedAttribute.USER_AGENT_ATTRIBUTE }, + {"entity_id", ControlAttributes.USER_AGENT_ATTRIBUTE }, + {"key", ControlAttributes.USER_AGENT_ATTRIBUTE }, {"type", "custom" }, {"value", "safari"} } @@ -1347,7 +1347,7 @@ public void TestCreateConversionEventWhenBotFilteringIsNotProvidedInDatafile() var userAttributes = new UserAttributes { - {ReservedAttribute.USER_AGENT_ATTRIBUTE, "safari" } + {ControlAttributes.USER_AGENT_ATTRIBUTE, "safari" } }; var experimentToVariationMap = new Dictionary { diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 2517b179..490ef7e7 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -1112,7 +1112,7 @@ public void TestGetVariationBucketingIdAttribute() { "device_type", "iPhone" }, { "company", "Optimizely" }, { "location", "San Francisco" }, - { ReservedAttribute.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation } + { ControlAttributes.BUCKETING_ID_ATTRIBUTE, testBucketingIdVariation } }; // confirm that a valid variation is bucketed without the bucketing ID diff --git a/OptimizelySDK.Tests/ProjectConfigTest.cs b/OptimizelySDK.Tests/ProjectConfigTest.cs index 7a5e731f..8dece0f8 100644 --- a/OptimizelySDK.Tests/ProjectConfigTest.cs +++ b/OptimizelySDK.Tests/ProjectConfigTest.cs @@ -837,7 +837,7 @@ public void TestBotFilteringValues() public void TestGetAttributeIdWithReservedPrefix() { // Verify that attribute key is returned for reserved attribute key. - Assert.AreEqual(Config.GetAttributeId(ReservedAttribute.USER_AGENT_ATTRIBUTE), ReservedAttribute.USER_AGENT_ATTRIBUTE); + Assert.AreEqual(Config.GetAttributeId(ControlAttributes.USER_AGENT_ATTRIBUTE), ControlAttributes.USER_AGENT_ATTRIBUTE); // Verify that attribute Id is returned for attribute key with reserved prefix that does not exist in datafile. Assert.AreEqual(Config.GetAttributeId("$opt_reserved_prefix_attribute"), "$opt_reserved_prefix_attribute"); diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs index 5e8860b8..29ee69e1 100644 --- a/OptimizelySDK/Bucketing/DecisionService.cs +++ b/OptimizelySDK/Bucketing/DecisionService.cs @@ -399,9 +399,9 @@ private string GetBucketingId(string userId, UserAttributes filteredAttributes) string bucketingId = userId; // If the bucketing ID key is defined in attributes, then use that in place of the userID for the murmur hash key - if (filteredAttributes != null && filteredAttributes.ContainsKey(ReservedAttribute.BUCKETING_ID_ATTRIBUTE)) + if (filteredAttributes != null && filteredAttributes.ContainsKey(ControlAttributes.BUCKETING_ID_ATTRIBUTE)) { - bucketingId = filteredAttributes[ReservedAttribute.BUCKETING_ID_ATTRIBUTE]; + bucketingId = filteredAttributes[ControlAttributes.BUCKETING_ID_ATTRIBUTE]; Logger.Log(LogLevel.DEBUG, string.Format("Setting the bucketing ID to \"{0}\"", bucketingId)); } diff --git a/OptimizelySDK/Event/Builder/EventBuilder.cs b/OptimizelySDK/Event/Builder/EventBuilder.cs index fc2cc5a5..193a38f0 100644 --- a/OptimizelySDK/Event/Builder/EventBuilder.cs +++ b/OptimizelySDK/Event/Builder/EventBuilder.cs @@ -118,8 +118,8 @@ private Dictionary GetCommonParams(ProjectConfig config, string { userFeatures.Add(new Dictionary { - { "entity_id", ReservedAttribute.BOT_FILTERING_ATTRIBUTE }, - { "key", ReservedAttribute.BOT_FILTERING_ATTRIBUTE }, + { "entity_id", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, + { "key", ControlAttributes.BOT_FILTERING_ATTRIBUTE }, { "type", CUSTOM_ATTRIBUTE_FEATURE_TYPE }, { "value", config.BotFiltering} }); diff --git a/OptimizelySDK/OptimizelySDK.csproj b/OptimizelySDK/OptimizelySDK.csproj index 119fb10d..b021922d 100644 --- a/OptimizelySDK/OptimizelySDK.csproj +++ b/OptimizelySDK/OptimizelySDK.csproj @@ -103,7 +103,7 @@ - + diff --git a/OptimizelySDK/Utils/ControlAttributes.cs b/OptimizelySDK/Utils/ControlAttributes.cs new file mode 100644 index 00000000..9fc7a5ba --- /dev/null +++ b/OptimizelySDK/Utils/ControlAttributes.cs @@ -0,0 +1,25 @@ +/* + * Copyright 2018, Optimizely + * + * 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. + */ + +namespace OptimizelySDK.Utils +{ + public class ControlAttributes + { + public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; + public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; + public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; + } +} From ace69dd79ea65509ab18d623c4959195d5e1deab Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Wed, 27 Jun 2018 11:48:06 -0700 Subject: [PATCH 6/6] deleted reserved attribute --- OptimizelySDK/Utils/ReservedAttribute.cs | 25 ------------------------ 1 file changed, 25 deletions(-) delete mode 100644 OptimizelySDK/Utils/ReservedAttribute.cs diff --git a/OptimizelySDK/Utils/ReservedAttribute.cs b/OptimizelySDK/Utils/ReservedAttribute.cs deleted file mode 100644 index b2bb9e17..00000000 --- a/OptimizelySDK/Utils/ReservedAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018, Optimizely - * - * 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. - */ - -namespace OptimizelySDK.Utils -{ - public class ReservedAttribute - { - public const string BOT_FILTERING_ATTRIBUTE = "$opt_bot_filtering"; - public const string USER_AGENT_ATTRIBUTE = "$opt_user_agent"; - public const string BUCKETING_ID_ATTRIBUTE = "$opt_bucketing_id"; - } -}