Skip to content

Commit

Permalink
Merge pull request #26 from statsig-io/1-9-0
Browse files Browse the repository at this point in the history
v1.9.0 - Layer overrides
  • Loading branch information
kenny-statsig authored May 2, 2023
2 parents 0ec3d0c + 547bc74 commit 33b7121
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 18 deletions.
5 changes: 5 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ func (c *Client) OverrideConfig(config string, val map[string]interface{}) {
c.evaluator.OverrideConfig(config, val)
}

// Override the Layer value for the given user
func (c *Client) OverrideLayer(layer string, val map[string]interface{}) {
c.evaluator.OverrideLayer(layer, val)
}

func (c *Client) LogImmediate(events []Event) (*http.Response, error) {
if len(events) > 500 {
err := errors.New(EventBatchSizeError)
Expand Down
55 changes: 40 additions & 15 deletions evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import (
)

type evaluator struct {
store *store
gateOverrides map[string]bool
gateOverridesLock sync.RWMutex
configOverrides map[string]map[string]interface{}
configOverridesLock sync.RWMutex
countryLookup *countrylookup.CountryLookup
uaParser *uaparser.Parser
store *store
gateOverrides map[string]bool
configOverrides map[string]map[string]interface{}
layerOverrides map[string]map[string]interface{}
countryLookup *countrylookup.CountryLookup
uaParser *uaparser.Parser
mu sync.RWMutex
}

type evalResult struct {
Expand Down Expand Up @@ -62,6 +62,7 @@ func newEvaluator(
uaParser: parser,
gateOverrides: make(map[string]bool),
configOverrides: make(map[string]map[string]interface{}),
layerOverrides: make(map[string]map[string]interface{}),
}
}

Expand Down Expand Up @@ -118,6 +119,16 @@ func (e *evaluator) getConfig(user User, configName string) *evalResult {
}

func (e *evaluator) getLayer(user User, name string) *evalResult {
if layerOverride, hasOverride := e.getLayerOverride(name); hasOverride {
evalDetails := e.createEvaluationDetails(reasonLocalOverride)
return &evalResult{
Pass: true,
ConfigValue: *NewConfig(name, layerOverride, "override"),
Id: "override",
EvaluationDetails: evalDetails,
SecondaryExposures: make([]map[string]string, 0),
}
}
if config, hasConfig := e.store.getLayerConfig(name); hasConfig {
return e.eval(user, config)
}
Expand All @@ -128,33 +139,47 @@ func (e *evaluator) getLayer(user User, name string) *evalResult {
}

func (e *evaluator) getGateOverride(name string) (bool, bool) {
e.gateOverridesLock.RLock()
defer e.gateOverridesLock.RUnlock()
e.mu.RLock()
defer e.mu.RUnlock()
gate, ok := e.gateOverrides[name]
return gate, ok
}

func (e *evaluator) getConfigOverride(name string) (map[string]interface{}, bool) {
e.configOverridesLock.RLock()
defer e.configOverridesLock.RUnlock()
e.mu.RLock()
defer e.mu.RUnlock()
config, ok := e.configOverrides[name]
return config, ok
}

func (e *evaluator) getLayerOverride(name string) (map[string]interface{}, bool) {
e.mu.RLock()
defer e.mu.RUnlock()
layer, ok := e.layerOverrides[name]
return layer, ok
}

// Override the value of a Feature Gate for the given user
func (e *evaluator) OverrideGate(gate string, val bool) {
e.gateOverridesLock.Lock()
defer e.gateOverridesLock.Unlock()
e.mu.Lock()
defer e.mu.Unlock()
e.gateOverrides[gate] = val
}

// Override the DynamicConfig value for the given user
func (e *evaluator) OverrideConfig(config string, val map[string]interface{}) {
e.configOverridesLock.Lock()
defer e.configOverridesLock.Unlock()
e.mu.Lock()
defer e.mu.Unlock()
e.configOverrides[config] = val
}

// Override the Layer value for the given user
func (e *evaluator) OverrideLayer(layer string, val map[string]interface{}) {
e.mu.Lock()
defer e.mu.Unlock()
e.layerOverrides[layer] = val
}

// Gets all evaluated values for the given user.
// These values can then be given to a Statsig Client SDK via bootstrapping.
func (e *evaluator) getClientInitializeResponse(user User) ClientInitializeResponse {
Expand Down
19 changes: 19 additions & 0 deletions overrides_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,23 @@ func TestOverrides(t *testing.T) {
if !reflect.DeepEqual(newConfigOverride.Value, newConfig) {
t.Errorf("Failed to get override value for a config when in LocalMode")
}

layer := make(map[string]interface{})
layer["test"] = 123

c.OverrideLayer("any_layer", layer)
layerOverride := c.GetLayer(user, "any_layer")
if !reflect.DeepEqual(layerOverride.Value, layer) {
t.Errorf("Failed to get override value for a layer when in LocalMode")
}

newLayer := make(map[string]interface{})
newLayer["test"] = 456
newLayer["test2"] = "hello"

c.OverrideLayer("any_layer", newLayer)
newLayerOverride := c.GetLayer(user, "any_layer")
if !reflect.DeepEqual(newLayerOverride.Value, newLayer) {
t.Errorf("Failed to get override value for a layer when in LocalMode")
}
}
8 changes: 8 additions & 0 deletions statsig.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ func OverrideConfig(config string, val map[string]interface{}) {
instance.OverrideConfig(config, val)
}

// Override the Layer value for the given user
func OverrideLayer(layer string, val map[string]interface{}) {
if !IsInitialized() {
panic(fmt.Errorf("must Initialize() statsig before calling OverrideLayer"))
}
instance.OverrideLayer(layer, val)
}

// Gets the DynamicConfig value of an Experiment for the given user
func GetExperiment(user User, experiment string) DynamicConfig {
if !IsInitialized() {
Expand Down
2 changes: 1 addition & 1 deletion statsig_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type statsigMetadata struct {
func getStatsigMetadata() statsigMetadata {
return statsigMetadata{
SDKType: "go-sdk",
SDKVersion: "1.8.1",
SDKVersion: "1.9.0",
LanguageVersion: runtime.Version()[2:],
}
}
4 changes: 2 additions & 2 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ func (s *store) getDynamicConfig(name string) (configSpec, bool) {
func (s *store) getLayerConfig(name string) (configSpec, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
config, ok := s.layerConfigs[name]
return config, ok
layer, ok := s.layerConfigs[name]
return layer, ok
}

func (s *store) getExperimentLayer(experimentName string) (string, bool) {
Expand Down

0 comments on commit 33b7121

Please sign in to comment.