Skip to content

Commit

Permalink
Merge pull request #8 from absmartly/feature/add-custom-fields-support
Browse files Browse the repository at this point in the history
Adding Custom Fields Support
  • Loading branch information
hermeswaldemarin authored Dec 4, 2023
2 parents 1ff349e + cb22d6a commit 6f0020e
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 53 deletions.
178 changes: 143 additions & 35 deletions sdk/Context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,64 @@ import (
"github.com/absmartly/go-sdk/sdk/internal"
"github.com/absmartly/go-sdk/sdk/jsonmodels"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)

type Context struct {
PublishDelay_ int64
RefreshInterval_ int64
EventHandler_ ContextEventHandler
EventLogger_ ContextEventLogger
DataProvider_ ContextDataProvider
VariableParser_ VariableParser
AudienceMatcher_ AudienceMatcher
Units_ map[string]string
Failed_ *atomic.Value
Ready_ *atomic.Value
DataLock *sync.RWMutex
Data_ jsonmodels.ContextData
Index_ map[string]ExperimentVariables
IndexVariables_ map[interface{}]interface{}
ContextLock_ *sync.RWMutex
HashedUnits_ map[interface{}]interface{}
Assigners_ map[interface{}]interface{}
AssignmentCache map[string]Assignment
EventLock_ *sync.Mutex
Exposures_ []jsonmodels.Exposure
Achievements_ []jsonmodels.GoalAchievement
Attributes_ []interface{}
Overrides_ map[interface{}]interface{}
Cassignments_ map[interface{}]interface{}
PendingCount_ *atomic.Value
Closing_ *atomic.Value
Closed_ *atomic.Value
Refreshing_ *atomic.Value
ReadyFuture_ *future.Future
ClosingFuture_ *future.Future
RefreshFuture_ *future.Future
TimeoutLock_ *sync.Mutex
Timeout_ *time.Timer
RefreshTimer_ *time.Timer
Clock_ internal.Clock
PublishDelay_ int64
RefreshInterval_ int64
EventHandler_ ContextEventHandler
EventLogger_ ContextEventLogger
DataProvider_ ContextDataProvider
VariableParser_ VariableParser
AudienceMatcher_ AudienceMatcher
Units_ map[string]string
Failed_ *atomic.Value
Ready_ *atomic.Value
DataLock *sync.RWMutex
Data_ jsonmodels.ContextData
Index_ map[string]ExperimentVariables
ContextCustomFields_ map[string]map[string]ContextCustomFieldValue
IndexVariables_ map[interface{}]interface{}
ContextLock_ *sync.RWMutex
HashedUnits_ map[interface{}]interface{}
Assigners_ map[interface{}]interface{}
AssignmentCache map[string]Assignment
EventLock_ *sync.Mutex
Exposures_ []jsonmodels.Exposure
Achievements_ []jsonmodels.GoalAchievement
Attributes_ []interface{}
Overrides_ map[interface{}]interface{}
Cassignments_ map[interface{}]interface{}
PendingCount_ *atomic.Value
Closing_ *atomic.Value
Closed_ *atomic.Value
Refreshing_ *atomic.Value
ReadyFuture_ *future.Future
ClosingFuture_ *future.Future
RefreshFuture_ *future.Future
TimeoutLock_ *sync.Mutex
Timeout_ *time.Timer
RefreshTimer_ *time.Timer
Clock_ internal.Clock
}

type ExperimentVariables struct {
Data jsonmodels.Experiment
Variables []map[string]interface{}
}

type ContextCustomFieldValue struct {
Name string
Type string
Value interface{}
}

type Assignment struct {
Id int
Iteration int
Expand Down Expand Up @@ -422,6 +431,82 @@ func (c *Context) GetVariableKeys() (map[string]string, error) {
return variableKeys, nil
}

func removeDuplicateValues(intSlice []string) []string {
keys := make(map[string]bool)
var list []string

for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}

func (c *Context) GetCustomFieldValueKeys() ([]string, error) {
var err = c.CheckReady(true)
if err != nil {
return nil, err
}

var keys []string

c.DataLock.Lock()

for _, experiment := range c.Data_.Experiments {
var customFieldValues = experiment.CustomFieldValues
if customFieldValues != nil {
for _, customFieldValue := range customFieldValues {
keys = append(keys, customFieldValue.Name)
}
}
}
c.DataLock.Unlock()

sort.Strings(keys)

return removeDuplicateValues(keys), nil
}

func (c *Context) GetCustomFieldValue(experimentName string, key string) interface{} {
var err = c.CheckReady(true)
if err != nil {
return nil
}
c.DataLock.Lock()
var customFieldValues = c.ContextCustomFields_[experimentName]

var value interface{} = nil
if customFieldValues != nil {
field, ok := customFieldValues[key]
if ok {
value = field.Value
}
}
c.DataLock.Unlock()

return value
}

func (c *Context) GetCustomFieldValueType(experimentName string, key string) string {
var customFieldValues = c.ContextCustomFields_[experimentName]

c.DataLock.Lock()

var fieldType string
if customFieldValues != nil {
field, ok := customFieldValues[key]
if ok {
fieldType = field.Type
}
}

c.DataLock.Unlock()

return fieldType
}

func (c *Context) GetVariableValue(key string, defaultValue interface{}) (interface{}, error) {
var err = c.CheckReady(true)
if err != nil {
Expand Down Expand Up @@ -752,9 +837,11 @@ func (c *Context) CheckReady(expectNotClosed bool) error {
func (c *Context) SetData(data jsonmodels.ContextData) {
var index = map[string]ExperimentVariables{}
var indexVariables = map[interface{}]interface{}{}
var contextCustomFields = map[string]map[string]ContextCustomFieldValue{}

for _, experiment := range data.Experiments {
var experiemntVariables = ExperimentVariables{}
var experimentCustomFields = map[string]ContextCustomFieldValue{}
experiemntVariables.Data = experiment
experiemntVariables.Variables = make([]map[string]interface{}, 0)

Expand All @@ -770,12 +857,33 @@ func (c *Context) SetData(data jsonmodels.ContextData) {
}
}

for _, customFieldValue := range experiment.CustomFieldValues {
var value = ContextCustomFieldValue{}
value.Type = customFieldValue.Type
value.Name = customFieldValue.Name
if len(customFieldValue.Value) > 0 {
var customValue = customFieldValue.Value
if strings.HasPrefix(customFieldValue.Type, "json") {
value.Value = c.VariableParser_.Parse(*c, experiment.Name, customFieldValue.Name, customValue)
} else if strings.HasPrefix(customFieldValue.Type, "boolean") {
value.Value, _ = strconv.ParseBool(customValue)
} else if strings.HasPrefix(customFieldValue.Type, "number") {
value.Value, _ = strconv.ParseInt(customValue, 10, 64)
} else {
value.Value = customValue
}

experimentCustomFields[value.Name] = value
}
}
contextCustomFields[experiment.Name] = experimentCustomFields
index[experiment.Name] = experiemntVariables
}

c.DataLock.Lock()

c.Index_ = index
c.ContextCustomFields_ = contextCustomFields
c.IndexVariables_ = indexVariables
c.Data_ = data
c.Ready_.Store(true)
Expand Down
53 changes: 53 additions & 0 deletions sdk/Context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sdk

import (
context2 "context"
"encoding/json"
"errors"
"github.com/absmartly/go-sdk/sdk/future"
"github.com/absmartly/go-sdk/sdk/internal"
Expand Down Expand Up @@ -772,6 +773,58 @@ func TestGetVariableKeys(t *testing.T) {
assertAny(int32(0), context.GetPendingCount(), t)
}

func TestGetCustomFieldValueKeys(t *testing.T) {
setUp()
var config = CreateDefaultContextConfig()
config.Units_ = units
var context = CreateTestContext(config, dataFutureReady)
assertAny(true, context.IsReady(), t)
assertAny(false, context.IsFailed(), t)

var res, _ = context.GetCustomFieldValueKeys()
assertAny([]string{"country", "languages", "overrides"}, res, t)
}

func TestGetCustomFieldValue(t *testing.T) {
setUp()
var config = CreateDefaultContextConfig()
config.Units_ = units
var context = CreateTestContext(config, dataFutureReady)
assertAny(true, context.IsReady(), t)
assertAny(false, context.IsFailed(), t)

assertAny(nil, context.GetCustomFieldValue("not_found", "not_found"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_ab", "not_found"), t)

assertAny("US,PT,ES,DE,FR", context.GetCustomFieldValue("exp_test_ab", "country"), t)
assertAny("string", context.GetCustomFieldValueType("exp_test_ab", "country"), t)

var js = "{\"123\":1,\"456\":0}"
overrides, _ := json.Marshal(context.GetCustomFieldValue("exp_test_ab", "overrides"))
var str = string(overrides)
assertAny(js, str, t)
assertAny("json", context.GetCustomFieldValueType("exp_test_ab", "overrides"), t)

assertAny(nil, context.GetCustomFieldValue("exp_test_ab", "languages"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_ab", "languages"), t)

assertAny(nil, context.GetCustomFieldValue("exp_test_abc", "overrides"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_abc", "overrides"), t)

assertAny("en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX", context.GetCustomFieldValue("exp_test_abc", "languages"), t)
assertAny("string", context.GetCustomFieldValueType("exp_test_abc", "languages"), t)

assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "country"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "country"), t)

assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "overrides"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "overrides"), t)

assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "languages"), t)
assertAny(nil, context.GetCustomFieldValue("exp_test_no_custom_fields", "languages"), t)

}

func TestPeekTreatmentOverrideVariant(t *testing.T) {
setUp()
var config = CreateDefaultContextConfig()
Expand Down
31 changes: 16 additions & 15 deletions sdk/jsonmodels/Experiment.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package jsonmodels

type Experiment struct {
Id int `json:"id"`
Name string `json:"name"`
UnitType string `json:"unitType"`
Iteration int `json:"iteration"`
SeedHi int `json:"seedHi"`
SeedLo int `json:"seedLo"`
Split []float64 `json:"split"`
TrafficSeedHi int `json:"trafficSeedHi"`
TrafficSeedLo int `json:"trafficSeedLo"`
TrafficSplit []float64 `json:"trafficSplit"`
FullOnVariant int `json:"fullOnVariant"`
Applications []ExperimentApplication `json:"applications"`
Variants []ExperimentVariant `json:"variants"`
AudienceStrict bool `json:"audienceStrict"`
Audience string `json:"audience"`
Id int `json:"id"`
Name string `json:"name"`
UnitType string `json:"unitType"`
Iteration int `json:"iteration"`
SeedHi int `json:"seedHi"`
SeedLo int `json:"seedLo"`
Split []float64 `json:"split"`
TrafficSeedHi int `json:"trafficSeedHi"`
TrafficSeedLo int `json:"trafficSeedLo"`
TrafficSplit []float64 `json:"trafficSplit"`
FullOnVariant int `json:"fullOnVariant"`
Applications []ExperimentApplication `json:"applications"`
Variants []ExperimentVariant `json:"variants"`
CustomFieldValues []CustomFieldValue `json:"customFieldValues"`
AudienceStrict bool `json:"audienceStrict"`
Audience string `json:"audience"`
}
31 changes: 28 additions & 3 deletions sdk/testAssets/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,19 @@
"config":"{\"banner.border\":1,\"banner.size\":\"large\"}"
}
],
"audience": null
"audience": null,
"customFieldValues": [
{
"name": "country",
"value": "US,PT,ES,DE,FR",
"type": "string"
},
{
"name": "overrides",
"value": "{\"123\":1,\"456\":0}",
"type": "json"
}
]
},
{
"id":2,
Expand Down Expand Up @@ -73,7 +85,19 @@
"config":"{\"button.color\":\"red\"}"
}
],
"audience": ""
"audience": "",
"customFieldValues": [
{
"name": "country",
"value": "US,PT,ES,DE,FR",
"type": "string"
},
{
"name": "languages",
"value": "en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX",
"type": "string"
}
]
},
{
"id":3,
Expand Down Expand Up @@ -113,7 +137,8 @@
"config":"{\"card.width\":\"75%\"}"
}
],
"audience": "{}"
"audience": "{}",
"customFieldValues": null
},
{
"id":4,
Expand Down

0 comments on commit 6f0020e

Please sign in to comment.