From 21b0eafabcaae2b894c3864fc5dfc8b640f85145 Mon Sep 17 00:00:00 2001 From: sroyal-statsig <76536058+sroyal-statsig@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:22:04 -0700 Subject: [PATCH] Layer Group Name (#163) --- client.go | 14 +++---- logger.go | 2 +- types.go | 100 ++++++++++++++++++++++++++++++++++++++++---------- types_test.go | 46 ++++++++++++++++++++++- 4 files changed, 132 insertions(+), 30 deletions(-) diff --git a/client.go b/client.go index 0145fdd..793c8ea 100644 --- a/client.go +++ b/client.go @@ -179,9 +179,9 @@ func (c *Client) ManuallyLogLayerParameterExposure(user User, layer string, para } user = normalizeUser(user, *c.options) res := c.evaluator.evalLayer(user, layer) - config := NewLayer(layer, res.JsonValue, res.RuleID, res.GroupName, nil).configBase + config := NewLayer(layer, res.JsonValue, res.RuleID, res.GroupName, nil, res.ConfigDelegate) context := &logContext{isManualExposure: true} - c.logger.logLayerExposure(user, config, parameter, *res, res.EvaluationDetails, context) + c.logger.logLayerExposure(user, *config, parameter, *res, res.EvaluationDetails, context) }) } @@ -367,7 +367,7 @@ func (c *Client) getConfigImpl(user User, name string, context getConfigImplCont func (c *Client) getLayerImpl(user User, name string, options getLayerOptions) Layer { return c.errorBoundary.captureGetLayer(func() Layer { if !c.verifyUser(user) { - return *NewLayer(name, nil, "", "", nil) + return *NewLayer(name, nil, "", "", nil, "") } user = normalizeUser(user, *c.options) @@ -377,18 +377,18 @@ func (c *Client) getLayerImpl(user User, name string, options getLayerOptions) L res = c.fetchConfigFromServer(user, name) } - logFunc := func(config configBase, parameterName string) { + logFunc := func(layer Layer, parameterName string) { var exposure *ExposureEvent = nil if !options.disableLogExposures { context := &logContext{isManualExposure: false} - exposure = c.logger.logLayerExposure(user, config, parameterName, *res, res.EvaluationDetails, context) + exposure = c.logger.logLayerExposure(user, layer, parameterName, *res, res.EvaluationDetails, context) } if c.options.EvaluationCallbacks.LayerEvaluationCallback != nil { - c.options.EvaluationCallbacks.LayerEvaluationCallback(name, parameterName, DynamicConfig{configBase: config}, exposure) + c.options.EvaluationCallbacks.LayerEvaluationCallback(name, parameterName, DynamicConfig{layer.configBase}, exposure) } } - return *NewLayer(name, res.JsonValue, res.RuleID, res.GroupName, &logFunc) + return *NewLayer(name, res.JsonValue, res.RuleID, res.GroupName, &logFunc, res.ConfigDelegate) }) } diff --git a/logger.go b/logger.go index 1b2c7f4..4839200 100644 --- a/logger.go +++ b/logger.go @@ -183,7 +183,7 @@ func (l *logger) logConfigExposure( func (l *logger) logLayerExposure( user User, - config configBase, + config Layer, parameterName string, evalResult evalResult, evalDetails *evaluationDetails, diff --git a/types.go b/types.go index 8219400..a00b6c4 100644 --- a/types.go +++ b/types.go @@ -29,20 +29,18 @@ type Event struct { } type configBase struct { - Name string `json:"name"` - Value map[string]interface{} `json:"value"` - RuleID string `json:"rule_id"` - GroupName string `json:"group_name"` - EvaluationDetails *evaluationDetails `json:"evaluation_details"` - LogExposure *func(configBase, string) `json:"log_exposure"` + Name string `json:"name"` + Value map[string]interface{} `json:"value"` + RuleID string `json:"rule_id"` + GroupName string `json:"group_name"` + EvaluationDetails *evaluationDetails `json:"evaluation_details"` } type FeatureGate struct { - Name string `json:"name"` - Value bool `json:"value"` - RuleID string `json:"rule_id"` - GroupName string `json:"group_name"` - LogExposure *func(configBase, string) + Name string `json:"name"` + Value bool `json:"value"` + RuleID string `json:"rule_id"` + GroupName string `json:"group_name"` } // A json blob configured in the Statsig Console @@ -52,6 +50,8 @@ type DynamicConfig struct { type Layer struct { configBase + LogExposure *func(Layer, string) `json:"log_exposure"` + AllocatedExperimentName string `json:"allocated_experiment_name"` } func NewGate(name string, value bool, ruleID string, groupName string) *FeatureGate { @@ -68,7 +68,7 @@ func NewConfig(name string, value map[string]interface{}, ruleID string, groupNa value = make(map[string]interface{}) } return &DynamicConfig{ - configBase{ + configBase: configBase{ Name: name, Value: value, RuleID: ruleID, @@ -78,24 +78,38 @@ func NewConfig(name string, value map[string]interface{}, ruleID string, groupNa } } -func NewLayer(name string, value map[string]interface{}, ruleID string, groupName string, logExposure *func(configBase, string)) *Layer { +func NewLayer(name string, value map[string]interface{}, ruleID string, groupName string, logExposure *func(Layer, string), allocatedExperimentName string) *Layer { if value == nil { value = make(map[string]interface{}) } return &Layer{ - configBase{ - Name: name, - Value: value, - RuleID: ruleID, - GroupName: groupName, - LogExposure: logExposure, + configBase: configBase{ + Name: name, + Value: value, + RuleID: ruleID, + GroupName: groupName, }, + AllocatedExperimentName: allocatedExperimentName, + LogExposure: logExposure, } } // Gets the string value at the given key in the DynamicConfig // Returns the fallback string if the item at the given key is not found or not of type string func (d *configBase) GetString(key string, fallback string) string { + if v, ok := d.Value[key]; ok { + switch val := v.(type) { + case string: + return val + } + } + + return fallback +} + +// Gets the string value at the given key in the DynamicConfig +// Returns the fallback string if the item at the given key is not found or not of type string +func (d *Layer) GetString(key string, fallback string) string { if v, ok := d.Value[key]; ok { switch val := v.(type) { case string: @@ -110,6 +124,18 @@ func (d *configBase) GetString(key string, fallback string) string { // Gets the float64 value at the given key in the DynamicConfig // Returns the fallback float64 if the item at the given key is not found or not of type float64 func (d *configBase) GetNumber(key string, fallback float64) float64 { + if v, ok := d.Value[key]; ok { + switch val := v.(type) { + case float64: + return val + } + } + return fallback +} + +// Gets the float64 value at the given key in the DynamicConfig +// Returns the fallback float64 if the item at the given key is not found or not of type float64 +func (d *Layer) GetNumber(key string, fallback float64) float64 { if v, ok := d.Value[key]; ok { switch val := v.(type) { case float64: @@ -123,6 +149,18 @@ func (d *configBase) GetNumber(key string, fallback float64) float64 { // Gets the boolean value at the given key in the DynamicConfig // Returns the fallback boolean if the item at the given key is not found or not of type boolean func (d *configBase) GetBool(key string, fallback bool) bool { + if v, ok := d.Value[key]; ok { + switch val := v.(type) { + case bool: + return val + } + } + return fallback +} + +// Gets the boolean value at the given key in the DynamicConfig +// Returns the fallback boolean if the item at the given key is not found or not of type boolean +func (d *Layer) GetBool(key string, fallback bool) bool { if v, ok := d.Value[key]; ok { switch val := v.(type) { case bool: @@ -136,6 +174,18 @@ func (d *configBase) GetBool(key string, fallback bool) bool { // Gets the slice value at the given key in the DynamicConfig // Returns the fallback slice if the item at the given key is not found or not of type slice func (d *configBase) GetSlice(key string, fallback []interface{}) []interface{} { + if v, ok := d.Value[key]; ok { + switch val := v.(type) { + case []interface{}: + return val + } + } + return fallback +} + +// Gets the slice value at the given key in the DynamicConfig +// Returns the fallback slice if the item at the given key is not found or not of type slice +func (d *Layer) GetSlice(key string, fallback []interface{}) []interface{} { if v, ok := d.Value[key]; ok { switch val := v.(type) { case []interface{}: @@ -147,6 +197,16 @@ func (d *configBase) GetSlice(key string, fallback []interface{}) []interface{} } func (d *configBase) GetMap(key string, fallback map[string]interface{}) map[string]interface{} { + if v, ok := d.Value[key]; ok { + switch val := v.(type) { + case map[string]interface{}: + return val + } + } + return fallback +} + +func (d *Layer) GetMap(key string, fallback map[string]interface{}) map[string]interface{} { if v, ok := d.Value[key]; ok { switch val := v.(type) { case map[string]interface{}: @@ -157,7 +217,7 @@ func (d *configBase) GetMap(key string, fallback map[string]interface{}) map[str return fallback } -func logExposure(c *configBase, parameterName string) { +func logExposure(c *Layer, parameterName string) { if c == nil || c.LogExposure == nil { return } diff --git a/types_test.go b/types_test.go index 8dbeb19..d4a1993 100644 --- a/types_test.go +++ b/types_test.go @@ -48,6 +48,48 @@ func doValidation(t *testing.T, c *configBase) { } } +func doValidationLayer(t *testing.T, c *Layer) { + if c.Name != "test" { + t.Errorf("Failed to set name") + } + if c.RuleID != "rule_id" { + t.Errorf("Failed to set rule_id") + } + if c.GroupName != "group_name" { + t.Errorf("Failed to set group_name") + } + + if c.GetString("String", "abc") != "str" { + t.Errorf("Failed to get string") + } + if c.GetString("Number", "abc") != "abc" { + t.Errorf("Failed to use fallback string") + } + if c.GetString("Object", "def") != "def" { + t.Errorf("Failed to use fallback string") + } + + if c.GetNumber("String", 0.07) != 0.07 { + t.Errorf("Failed to use fallback number") + } + if c.GetNumber("Number", 0.07) != 143.7 { + t.Errorf("Failed to get number") + } + if c.GetNumber("Object", 4) != 4 { + t.Errorf("Failed to use fallback number") + } + + if !c.GetBool("String", true) { + t.Errorf("Failed to use fallback boolean") + } + if !c.GetBool("Boolean", false) { + t.Errorf("Failed to get boolean") + } + if c.GetBool("Object", false) { + t.Errorf("Failed to use fallback boolean") + } +} + func TestBasic(t *testing.T) { jsonMap := make(map[string]interface{}) _ = json.Unmarshal( @@ -75,8 +117,8 @@ func TestBasic(t *testing.T) { ) doValidation(t, &c.configBase) - l := NewLayer("test", jsonMap, "rule_id", "group_name", nil) - doValidation(t, &l.configBase) + l := NewLayer("test", jsonMap, "rule_id", "group_name", nil, "allocated_experiment_name") + doValidationLayer(t, l) fallbackValues := make([]interface{}, 0) fallbackValues = append(fallbackValues, 4, 5, 6)