diff --git a/optimizely/client/client.go b/optimizely/client/client.go index 611295c42..dc0c03279 100644 --- a/optimizely/client/client.go +++ b/optimizely/client/client.go @@ -45,12 +45,6 @@ type OptimizelyClient struct { // IsFeatureEnabled returns true if the feature is enabled for the given user func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entities.UserContext) (result bool, err error) { - if !o.isValid { - errorMessage := "Optimizely instance is not valid. Failing IsFeatureEnabled." - err := errors.New(errorMessage) - logger.Error(errorMessage, nil) - return false, err - } defer func() { if r := recover(); r != nil { @@ -60,11 +54,11 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit } }() - projectConfig := o.configManager.GetConfig() - - if reflect.ValueOf(projectConfig).IsNil() { - return false, fmt.Errorf("project config is null") + if isValid, err := o.isValidClient("IsFeatureEnabled"); !isValid { + return false, err } + + projectConfig := o.configManager.GetConfig() feature, err := projectConfig.GetFeatureByKey(featureKey) if err != nil { logger.Error("Error retrieving feature", err) @@ -78,6 +72,7 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit userID := userContext.ID logger.Debug(fmt.Sprintf(`Evaluating feature "%s" for user "%s".`, featureKey, userID)) featureDecision, err := o.decisionService.GetFeatureDecision(featureDecisionContext, userContext) + if err != nil { logger.Error("Received an error while computing feature decision", err) return result, err @@ -100,12 +95,6 @@ func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entit // GetEnabledFeatures returns an array containing the keys of all features in the project that are enabled for the given user. func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext) (enabledFeatures []string, err error) { - if !o.isValid { - errorMessage := "Optimizely instance is not valid. Failing GetEnabledFeatures." - err := errors.New(errorMessage) - logger.Error(errorMessage, nil) - return enabledFeatures, err - } defer func() { if r := recover(); r != nil { @@ -115,12 +104,11 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext) } }() - projectConfig := o.configManager.GetConfig() - - if reflect.ValueOf(projectConfig).IsNil() { - return enabledFeatures, fmt.Errorf("project config is null") + if isValid, err := o.isValidClient("GetEnabledFeatures"); !isValid { + return enabledFeatures, err } + projectConfig := o.configManager.GetConfig() featureList := projectConfig.GetFeatureList() for _, feature := range featureList { isEnabled, _ := o.IsFeatureEnabled(feature.Key, userContext) @@ -164,6 +152,78 @@ func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserConte return nil } +// GetFeatureVariableString returns string feature variable value +func (o *OptimizelyClient) GetFeatureVariableString(featureKey string, variableKey string, userContext entities.UserContext) (value string, err error) { + value, valueType, err := o.getFeatureVariable(featureKey, variableKey, userContext) + if err != nil { + return "", err + } + if valueType != "string" { + return "", fmt.Errorf("Variable value for key %s is wrong type", variableKey) + } + return value, err +} + +func (o *OptimizelyClient) getFeatureVariable(featureKey string, variableKey string, userContext entities.UserContext) (value string, valueType string, err error) { + + defer func() { + if r := recover(); r != nil { + errorMessage := fmt.Sprintf(`Optimizely SDK is panicking with the error "%s"`, string(debug.Stack())) + err = errors.New(errorMessage) + logger.Error(errorMessage, err) + } + }() + + if isValid, err := o.isValidClient("getFeatureVariable"); !isValid { + return "", "", err + } + + projectConfig := o.configManager.GetConfig() + + feature, err := projectConfig.GetFeatureByKey(featureKey) + if err != nil { + logger.Error("Error retrieving feature", err) + return "", "", err + } + + variable, err := projectConfig.GetVariableByKey(featureKey, variableKey) + if err != nil { + logger.Error("Error retrieving variable", err) + return "", "", err + } + + var featureValue = variable.DefaultValue + + featureDecisionContext := decision.FeatureDecisionContext{ + Feature: &feature, + ProjectConfig: projectConfig, + } + + featureDecision, err := o.decisionService.GetFeatureDecision(featureDecisionContext, userContext) + if err == nil { + if v, ok := featureDecision.Variation.Variables[variable.ID]; ok && featureDecision.Variation.FeatureEnabled { + featureValue = v.Value + } + } + + // @TODO(yasir): send decision notification + return featureValue, variable.Type, nil +} + +func (o *OptimizelyClient) isValidClient(methodName string) (result bool, err error) { + if !o.isValid { + errorMessage := fmt.Sprintf("Optimizely instance is not valid. Failing %s.", methodName) + err := errors.New(errorMessage) + logger.Error(errorMessage, nil) + return false, err + } + + if reflect.ValueOf(o.configManager.GetConfig()).IsNil() { + return false, fmt.Errorf("project config is null") + } + return true, nil +} + // Close closes the Optimizely instance and stops any ongoing tasks from its children components func (o *OptimizelyClient) Close() { o.cancelFunc() diff --git a/optimizely/client/client_test.go b/optimizely/client/client_test.go index ea904112b..88a272401 100644 --- a/optimizely/client/client_test.go +++ b/optimizely/client/client_test.go @@ -18,13 +18,14 @@ package client import ( "errors" + "testing" + "github.com/optimizely/go-sdk/optimizely" "github.com/optimizely/go-sdk/optimizely/decision" "github.com/optimizely/go-sdk/optimizely/entities" "github.com/optimizely/go-sdk/optimizely/event" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "testing" ) type MockProjectConfig struct { @@ -42,6 +43,11 @@ func (c *MockProjectConfig) GetFeatureList() []entities.Feature { return args.Get(0).([]entities.Feature) } +func (c *MockProjectConfig) GetVariableByKey(featureKey string, variableKey string) (entities.Variable, error) { + args := c.Called(featureKey, variableKey) + return args.Get(0).(entities.Variable), args.Error(1) +} + func (c *MockProjectConfig) GetEventByKey(string) (entities.Event, error) { args := c.Called() return args.Get(0).(entities.Event), args.Error(1) @@ -126,11 +132,11 @@ func TestTrack(t *testing.T) { client := OptimizelyClient{ configManager: mockConfigManager, decisionService: mockDecisionService, - eventProcessor: mockProcessor, + eventProcessor: mockProcessor, isValid: true, } - err := client.Track("sample_conversion", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + err := client.Track("sample_conversion", entities.UserContext{ID: "1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) assert.Nil(t, err) assert.True(t, len(mockProcessor.Events) == 1) @@ -150,11 +156,11 @@ func TestTrackFail(t *testing.T) { client := OptimizelyClient{ configManager: mockConfigManager, decisionService: mockDecisionService, - eventProcessor: mockProcessor, + eventProcessor: mockProcessor, isValid: true, } - err := client.Track("bob", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + err := client.Track("bob", entities.UserContext{ID: "1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) assert.NotNil(t, err) assert.True(t, len(mockProcessor.Events) == 0) @@ -172,11 +178,11 @@ func TestTrackInvalid(t *testing.T) { client := OptimizelyClient{ configManager: mockConfigManager, decisionService: mockDecisionService, - eventProcessor: mockProcessor, + eventProcessor: mockProcessor, isValid: false, } - err := client.Track("sample_conversion", entities.UserContext{ID:"1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) + err := client.Track("sample_conversion", entities.UserContext{ID: "1212121", Attributes: map[string]interface{}{}}, map[string]interface{}{}) assert.NotNil(t, err) assert.True(t, len(mockProcessor.Events) == 0) @@ -418,3 +424,219 @@ func TestGetEnabledFeaturesPanic(t *testing.T) { assert.Empty(t, result) assert.True(t, assert.Error(t, err)) } + +func TestGetFeatureVariableStringWithValidValue(t *testing.T) { + testFeatureKey := "test_feature_key" + testVariableKey := "test_feature_flag_key" + testVariableValue := "teststring" + testUserContext := entities.UserContext{ID: "test_user_1"} + testVariationVariable := entities.VariationVariable{ + ID: "1", + Value: testVariableValue, + } + testVariable := entities.Variable{ + DefaultValue: "default", + ID: "1", + Key: "test_feature_flag_key", + Type: "string", + } + testVariation := getTestVariationWithFeatureVariable(true, testVariationVariable) + testExperiment := entities.Experiment{ + ID: "111111", + Variations: map[string]entities.Variation{"22222": testVariation}, + } + testFeature := getTestFeature(testFeatureKey, testExperiment) + mockConfig := getMockConfig(testFeatureKey, testVariableKey, testFeature, testVariable) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + + testDecisionContext := decision.FeatureDecisionContext{ + Feature: &testFeature, + ProjectConfig: mockConfig, + } + + expectedFeatureDecision := getTestFeatureDecision(testExperiment, testVariation, true) + mockDecisionService := new(MockDecisionService) + mockDecisionService.On("GetFeatureDecision", testDecisionContext, testUserContext).Return(expectedFeatureDecision, nil) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + isValid: true, + } + result, _ := client.GetFeatureVariableString(testFeatureKey, testVariableKey, testUserContext) + assert.Equal(t, testVariableValue, result) + mockConfig.AssertExpectations(t) + mockConfigManager.AssertExpectations(t) + mockDecisionService.AssertExpectations(t) +} + +func TestGetFeatureVariableStringWithInvalidValueType(t *testing.T) { + testFeatureKey := "test_feature_key" + testVariableKey := "test_feature_flag_key" + testVariableValue := "true" + testUserContext := entities.UserContext{ID: "test_user_1"} + testVariationVariable := entities.VariationVariable{ + ID: "1", + Value: testVariableValue, + } + testVariable := entities.Variable{ + DefaultValue: "default", + ID: "1", + Key: "test_feature_flag_key", + Type: "boolean", + } + testVariation := getTestVariationWithFeatureVariable(true, testVariationVariable) + testExperiment := entities.Experiment{ + ID: "111111", + Variations: map[string]entities.Variation{"22222": testVariation}, + } + testFeature := getTestFeature(testFeatureKey, testExperiment) + mockConfig := getMockConfig(testFeatureKey, testVariableKey, testFeature, testVariable) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + + testDecisionContext := decision.FeatureDecisionContext{ + Feature: &testFeature, + ProjectConfig: mockConfig, + } + + expectedFeatureDecision := getTestFeatureDecision(testExperiment, testVariation, true) + mockDecisionService := new(MockDecisionService) + mockDecisionService.On("GetFeatureDecision", testDecisionContext, testUserContext).Return(expectedFeatureDecision, nil) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + isValid: true, + } + result, err := client.GetFeatureVariableString(testFeatureKey, testVariableKey, testUserContext) + assert.Equal(t, "", result) + assert.NotNil(t, err) + mockConfig.AssertExpectations(t) + mockConfigManager.AssertExpectations(t) + mockDecisionService.AssertExpectations(t) +} + +func TestGetFeatureVariableStringReturnsDefaultValueIfFeatureNotEnabled(t *testing.T) { + testFeatureKey := "test_feature_key" + testVariableKey := "test_feature_flag_key" + testVariableValue := "teststring" + testUserContext := entities.UserContext{ID: "test_user_1"} + testVariationVariable := entities.VariationVariable{ + ID: "1", + Value: testVariableValue, + } + testVariable := entities.Variable{ + DefaultValue: "defaultString", + ID: "1", + Key: "test_feature_flag_key", + Type: "string", + } + testVariation := getTestVariationWithFeatureVariable(false, testVariationVariable) + testExperiment := entities.Experiment{ + ID: "111111", + Variations: map[string]entities.Variation{"22222": testVariation}, + } + testFeature := getTestFeature(testFeatureKey, testExperiment) + mockConfig := getMockConfig(testFeatureKey, testVariableKey, testFeature, testVariable) + mockConfigManager := new(MockProjectConfigManager) + mockConfigManager.On("GetConfig").Return(mockConfig) + + testDecisionContext := decision.FeatureDecisionContext{ + Feature: &testFeature, + ProjectConfig: mockConfig, + } + + expectedFeatureDecision := getTestFeatureDecision(testExperiment, testVariation, true) + mockDecisionService := new(MockDecisionService) + mockDecisionService.On("GetFeatureDecision", testDecisionContext, testUserContext).Return(expectedFeatureDecision, nil) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + isValid: true, + } + result, err := client.GetFeatureVariableString(testFeatureKey, testVariableKey, testUserContext) + assert.Equal(t, "defaultString", result) + assert.Nil(t, err) + mockConfig.AssertExpectations(t) + mockConfigManager.AssertExpectations(t) + mockDecisionService.AssertExpectations(t) +} + +func TestGetFeatureVariableErrorCases(t *testing.T) { + testUserContext := entities.UserContext{ID: "test_user_1"} + + mockConfigManager := new(MockProjectConfigManager) + mockDecisionService := new(MockDecisionService) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + isValid: false, + } + _, err1 := client.GetFeatureVariableString("test_feature_key", "test_variable_key", testUserContext) + assert.Error(t, err1) + mockConfigManager.AssertNotCalled(t, "GetFeatureByKey") + mockConfigManager.AssertNotCalled(t, "GetVariableByKey") + mockDecisionService.AssertNotCalled(t, "GetFeatureDecision") +} + +func TestGetFeatureVariableStringPanic(t *testing.T) { + testUserContext := entities.UserContext{ID: "test_user_1"} + testFeatureKey := "test_feature_key" + testVariableKey := "test_variable_key" + + mockConfigManager := new(MockProjectConfigManager) + mockDecisionService := new(MockDecisionService) + + client := OptimizelyClient{ + configManager: mockConfigManager, + decisionService: mockDecisionService, + isValid: true, + } + + // returning an error object will cause the Client to panic + mockConfigManager.On("GetFeatureByKey", testFeatureKey, testUserContext).Return(errors.New("failure")) + + // ensure that the client calms back down and recovers + result, err := client.GetFeatureVariableString(testFeatureKey, testVariableKey, testUserContext) + assert.Equal(t, "", result) + assert.True(t, assert.Error(t, err)) +} + +// Helper Methods +func getTestFeatureDecision(experiment entities.Experiment, variation entities.Variation, decisionMade bool) decision.FeatureDecision { + return decision.FeatureDecision{ + Experiment: experiment, + Variation: variation, + Decision: decision.Decision{ + DecisionMade: decisionMade, + }, + } +} + +func getTestVariationWithFeatureVariable(featureEnabled bool, variable entities.VariationVariable) entities.Variation { + return entities.Variation{ + ID: "22222", + Key: "22222", + FeatureEnabled: featureEnabled, + Variables: map[string]entities.VariationVariable{variable.ID: variable}, + } +} + +func getMockConfig(featureKey string, variableKey string, feature entities.Feature, variable entities.Variable) *MockProjectConfig { + mockConfig := new(MockProjectConfig) + mockConfig.On("GetFeatureByKey", featureKey).Return(feature, nil) + mockConfig.On("GetVariableByKey", featureKey, variableKey).Return(variable, nil) + return mockConfig +} + +func getTestFeature(featureKey string, experiment entities.Experiment) entities.Feature { + return entities.Feature{ + ID: "22222", + Key: featureKey, + FeatureExperiments: []entities.Experiment{experiment}, + } +} diff --git a/optimizely/config/datafileprojectconfig/config.go b/optimizely/config/datafileprojectconfig/config.go index 8a38e5165..af60a2935 100644 --- a/optimizely/config/datafileprojectconfig/config.go +++ b/optimizely/config/datafileprojectconfig/config.go @@ -94,7 +94,7 @@ func NewDatafileProjectConfig(jsonDatafile []byte) (*DatafileProjectConfig, erro experimentMap: experimentMap, experimentKeyToIDMap: experimentKeyMap, rolloutMap: rolloutMap, - featureMap: mappers.MapFeatureFlags(datafile.FeatureFlags, rolloutMap, experimentMap), + featureMap: mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentMap), } logger.Info("Datafile is valid.") @@ -121,6 +121,23 @@ func (c DatafileProjectConfig) GetFeatureByKey(featureKey string) (entities.Feat return entities.Feature{}, errors.New(errMessage) } +// GetVariableByKey returns the featureVariable with the given key +func (c DatafileProjectConfig) GetVariableByKey(featureKey string, variableKey string) (entities.Variable, error) { + + var variable entities.Variable + var err = fmt.Errorf("Variable with key %s not found", featureKey) + if feature, ok := c.featureMap[featureKey]; ok { + for _, v := range feature.Variables { + if v.Key == variableKey { + variable = v + err = nil + break + } + } + } + return variable, err +} + // GetAttributeByKey returns the attribute with the given key func (c DatafileProjectConfig) GetAttributeByKey(key string) (entities.Attribute, error) { if attributeID, ok := c.attributeKeyToIDMap[key]; ok { diff --git a/optimizely/config/datafileprojectconfig/entities/entities.go b/optimizely/config/datafileprojectconfig/entities/entities.go index 1a16c7986..304d6a2ee 100644 --- a/optimizely/config/datafileprojectconfig/entities/entities.go +++ b/optimizely/config/datafileprojectconfig/entities/entities.go @@ -67,10 +67,16 @@ type trafficAllocation struct { // Variation represents an experiment variation from the Optimizely datafile type Variation struct { - ID string `json:"id"` - // @TODO(mng): include variables - Key string `json:"key"` - FeatureEnabled bool `json:"featureEnabled"` + ID string `json:"id"` + Variables []VariationVariable `json:"variables"` + Key string `json:"key"` + FeatureEnabled bool `json:"featureEnabled"` +} + +// VariationVariable represents a Variable object from the Variation +type VariationVariable struct { + ID string `json:"id"` + Value string `json:"value"` } // Event represents an event from the Optimizely datafile diff --git a/optimizely/config/datafileprojectconfig/mappers/experiment.go b/optimizely/config/datafileprojectconfig/mappers/experiment.go index 5dd553a1e..fcfb63f55 100644 --- a/optimizely/config/datafileprojectconfig/mappers/experiment.go +++ b/optimizely/config/datafileprojectconfig/mappers/experiment.go @@ -44,6 +44,12 @@ func mapVariation(rawVariation datafileEntities.Variation) entities.Variation { Key: rawVariation.Key, FeatureEnabled: rawVariation.FeatureEnabled, } + + variation.Variables = make(map[string]entities.VariationVariable) + for _, variable := range rawVariation.Variables { + variation.Variables[variable.ID] = entities.VariationVariable{ID: variable.ID, Value: variable.Value} + } + return variation } diff --git a/optimizely/config/datafileprojectconfig/mappers/experiment_test.go b/optimizely/config/datafileprojectconfig/mappers/experiment_test.go index 1927948b8..6ed6ca42a 100644 --- a/optimizely/config/datafileprojectconfig/mappers/experiment_test.go +++ b/optimizely/config/datafileprojectconfig/mappers/experiment_test.go @@ -34,12 +34,14 @@ func TestMapExperiments(t *testing.T) { { "id": "21111", "key": "variation_1", - "featureEnabled": true + "featureEnabled": true, + "variables": [{"id":"1","value":"1"}] }, { "id": "21112", "key": "variation_2", - "featureEnabled": false + "featureEnabled": false, + "variables": [{"id":"2","value":"2"}] } ], "trafficAllocation": [ @@ -71,11 +73,13 @@ func TestMapExperiments(t *testing.T) { Variations: map[string]entities.Variation{ "21111": { ID: "21111", + Variables: map[string]entities.VariationVariable{"1": entities.VariationVariable{ID: "1", Value: "1"}}, Key: "variation_1", FeatureEnabled: true, }, "21112": { ID: "21112", + Variables: map[string]entities.VariationVariable{"2": entities.VariationVariable{ID: "2", Value: "2"}}, Key: "variation_2", FeatureEnabled: false, }, diff --git a/optimizely/config/datafileprojectconfig/mappers/feature.go b/optimizely/config/datafileprojectconfig/mappers/feature.go index 978b1c71c..1987522f1 100644 --- a/optimizely/config/datafileprojectconfig/mappers/feature.go +++ b/optimizely/config/datafileprojectconfig/mappers/feature.go @@ -21,11 +21,8 @@ import ( "github.com/optimizely/go-sdk/optimizely/entities" ) -// MapFeatureFlags maps the raw datafile feature flag entities to SDK Feature entities -func MapFeatureFlags( - featureFlags []datafileEntities.FeatureFlag, - rolloutMap map[string]entities.Rollout, - experimentMap map[string]entities.Experiment, +// MapFeatures maps the raw datafile feature flag entities to SDK Feature entities +func MapFeatures(featureFlags []datafileEntities.FeatureFlag, rolloutMap map[string]entities.Rollout, experimentMap map[string]entities.Experiment, ) map[string]entities.Feature { featureMap := make(map[string]entities.Feature) @@ -44,7 +41,17 @@ func MapFeatureFlags( } } + var variables = []entities.Variable{} + for _, variable := range featureFlag.Variables { + variables = append(variables, entities.Variable{ + DefaultValue: variable.DefaultValue, + ID: variable.ID, + Key: variable.Key, + Type: variable.Type}) + } + feature.FeatureExperiments = featureExperiments + feature.Variables = variables featureMap[featureFlag.Key] = feature } return featureMap diff --git a/optimizely/config/datafileprojectconfig/mappers/feature_test.go b/optimizely/config/datafileprojectconfig/mappers/feature_test.go index 831eac1e9..44f8c5b81 100644 --- a/optimizely/config/datafileprojectconfig/mappers/feature_test.go +++ b/optimizely/config/datafileprojectconfig/mappers/feature_test.go @@ -30,7 +30,8 @@ func TestMapFeatures(t *testing.T) { "id": "21111", "key": "test_feature_21111", "rolloutId": "41111", - "experimentIds": ["31111", "31112"] + "experimentIds": ["31111", "31112"], + "variables": [{"defaultValue":"1","id":"1","key":"test","type":"integer"}] }` var rawFeatureFlag datafileEntities.FeatureFlag @@ -47,13 +48,20 @@ func TestMapFeatures(t *testing.T) { "31111": experiment31111, "31112": experiment31112, } - featureMap := MapFeatureFlags(rawFeatureFlags, rolloutMap, experimentMap) + featureMap := MapFeatures(rawFeatureFlags, rolloutMap, experimentMap) + variable := entities.Variable{ + ID: "1", + DefaultValue: "1", + Key: "test", + Type: "integer", + } expectedFeatureMap := map[string]entities.Feature{ "test_feature_21111": entities.Feature{ ID: "21111", Key: "test_feature_21111", Rollout: rollout, FeatureExperiments: []entities.Experiment{experiment31111, experiment31112}, + Variables: []entities.Variable{variable}, }, } diff --git a/optimizely/entities/experiment.go b/optimizely/entities/experiment.go index c402fb62b..e5a10313a 100644 --- a/optimizely/entities/experiment.go +++ b/optimizely/entities/experiment.go @@ -19,6 +19,7 @@ package entities // Variation represents a variation in the experiment type Variation struct { ID string + Variables map[string]VariationVariable Key string FeatureEnabled bool } @@ -40,3 +41,9 @@ type Range struct { EntityID string EndOfRange int } + +// VariationVariable represents a Variable object from the Variation +type VariationVariable struct { + ID string + Value string +} diff --git a/optimizely/entities/feature.go b/optimizely/entities/feature.go index 8e4654956..131d88f16 100644 --- a/optimizely/entities/feature.go +++ b/optimizely/entities/feature.go @@ -22,6 +22,7 @@ type Feature struct { Key string FeatureExperiments []Experiment Rollout Rollout + Variables []Variable } // Rollout represents a feature rollout @@ -29,3 +30,11 @@ type Rollout struct { ID string Experiments []Experiment } + +// Variable represents a feature variable +type Variable struct { + DefaultValue string + ID string + Key string + Type string +} diff --git a/optimizely/event/processor_test.go b/optimizely/event/processor_test.go index f09fcf88d..bb35b28f1 100644 --- a/optimizely/event/processor_test.go +++ b/optimizely/event/processor_test.go @@ -19,7 +19,7 @@ func TestDefaultEventProcessor_ProcessImpression(t *testing.T) { assert.Equal(t, 1, processor.EventsCount()) - time.Sleep(200 * time.Millisecond) + time.Sleep(2000 * time.Millisecond) assert.NotNil(t, processor.Ticker) diff --git a/optimizely/interface.go b/optimizely/interface.go index 9dc985e9a..7bee0f66a 100644 --- a/optimizely/interface.go +++ b/optimizely/interface.go @@ -32,6 +32,7 @@ type ProjectConfig interface { GetEventByKey(string) (entities.Event, error) GetExperimentByKey(string) (entities.Experiment, error) GetFeatureByKey(string) (entities.Feature, error) + GetVariableByKey(featureKey string, variableKey string) (entities.Variable, error) GetFeatureList() []entities.Feature GetGroupByID(string) (entities.Group, error) GetProjectID() string