Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions pkg/config/datafileprojectconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type DatafileProjectConfig struct {
experimentMap map[string]entities.Experiment
featureMap map[string]entities.Feature
groupMap map[string]entities.Group
rollouts []entities.Rollout
rolloutMap map[string]entities.Rollout
anonymizeIP bool
botFiltering bool
Expand Down Expand Up @@ -77,6 +78,14 @@ func (c DatafileProjectConfig) GetAnonymizeIP() bool {
return c.anonymizeIP
}

// GetAttributes returns attributes
func (c DatafileProjectConfig) GetAttributes() (attributeList []entities.Attribute) {
for _, attribute := range c.attributeMap {
attributeList = append(attributeList, attribute)
}
return attributeList
}

// GetAttributeID returns attributeID
func (c DatafileProjectConfig) GetAttributeID(key string) string {
return c.attributeKeyToIDMap[key]
Expand All @@ -97,6 +106,14 @@ func (c DatafileProjectConfig) GetEnvironmentKey() string {
return c.environmentKey
}

// GetEvents returns all events
func (c DatafileProjectConfig) GetEvents() (eventList []entities.Event) {
for _, event := range c.eventMap {
eventList = append(eventList, event)
}
return eventList
}

// GetEventByKey returns the event with the given key
func (c DatafileProjectConfig) GetEventByKey(eventKey string) (entities.Event, error) {
if event, ok := c.eventMap[eventKey]; ok {
Expand Down Expand Up @@ -157,6 +174,19 @@ func (c DatafileProjectConfig) GetExperimentList() (experimentList []entities.Ex
return experimentList
}

// GetRolloutList returns an array of all the rollouts
func (c DatafileProjectConfig) GetRolloutList() (rolloutList []entities.Rollout) {
return c.rollouts
}

// GetAudienceList returns an array of all the audiences
func (c DatafileProjectConfig) GetAudienceList() (audienceList []entities.Audience) {
for _, audience := range c.audienceMap {
audienceList = append(audienceList, audience)
}
return audienceList
}

// GetAudienceByID returns the audience with the given ID
func (c DatafileProjectConfig) GetAudienceByID(audienceID string) (entities.Audience, error) {
if audience, ok := c.audienceMap[audienceID]; ok {
Expand Down Expand Up @@ -215,16 +245,18 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
groupMap, experimentGroupMap := mappers.MapGroups(datafile.Groups)
experimentMap, experimentKeyMap := mappers.MapExperiments(allExperiments, experimentGroupMap)

rolloutMap := mappers.MapRollouts(datafile.Rollouts)
rollouts, rolloutMap := mappers.MapRollouts(datafile.Rollouts)
eventMap := mappers.MapEvents(datafile.Events)
mergedAudiences := append(datafile.TypedAudiences, datafile.Audiences...)
featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentMap)
audienceMap := mappers.MapAudiences(mergedAudiences)

config := &DatafileProjectConfig{
datafile: string(jsonDatafile),
accountID: datafile.AccountID,
anonymizeIP: datafile.AnonymizeIP,
attributeKeyToIDMap: attributeKeyToIDMap,
audienceMap: mappers.MapAudiences(mergedAudiences),
audienceMap: audienceMap,
attributeMap: attributeMap,
botFiltering: datafile.BotFiltering,
sdkKey: datafile.SDKKey,
Expand All @@ -236,6 +268,7 @@ func NewDatafileProjectConfig(jsonDatafile []byte, logger logging.OptimizelyLogP
featureMap: featureMap,
projectID: datafile.ProjectID,
revision: datafile.Revision,
rollouts: rollouts,
rolloutMap: rolloutMap,
sendFlagDecisions: datafile.SendFlagDecisions,
}
Expand Down
52 changes: 49 additions & 3 deletions pkg/config/datafileprojectconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func TestNewDatafileProjectConfigNil(t *testing.T) {
}

func TestNewDatafileProjectConfigNotNil(t *testing.T) {
dpc := DatafileProjectConfig{accountID: "123", revision: "1", projectID: "12345", sdkKey: "a", environmentKey: "production"}
jsonDatafileStr := `{"accountID": "123", "revision": "1", "projectId": "12345", "version": "4", "sdkKey": "a", "environmentKey": "production"}`
dpc := DatafileProjectConfig{accountID: "123", revision: "1", projectID: "12345", sdkKey: "a", environmentKey: "production", eventMap: map[string]entities.Event{"event_single_targeted_exp": {Key: "event_single_targeted_exp"}}, attributeMap: map[string]entities.Attribute{"10401066170": {ID: "10401066170"}}}
jsonDatafileStr := `{"accountID":"123","revision":"1","projectId":"12345","version":"4","sdkKey":"a","environmentKey":"production","events":[{"key":"event_single_targeted_exp"}],"attributes":[{"id":"10401066170"}]}`
jsonDatafile := []byte(jsonDatafileStr)
projectConfig, err := NewDatafileProjectConfig(jsonDatafile, logging.GetLogger("", "DatafileProjectConfig"))
assert.Nil(t, err)
Expand All @@ -52,6 +52,16 @@ func TestNewDatafileProjectConfigNotNil(t *testing.T) {
assert.Equal(t, dpc.sdkKey, projectConfig.sdkKey)
}

func TestGetDatafile(t *testing.T) {
jsonDatafileStr := `{"accountID": "123", "revision": "1", "projectId": "12345", "version": "4", "sdkKey": "a", "environmentKey": "production"}`
jsonDatafile := []byte(jsonDatafileStr)
config := &DatafileProjectConfig{
datafile: string(jsonDatafile),
}

assert.Equal(t, string(jsonDatafile), config.GetDatafile())
}

func TestGetProjectID(t *testing.T) {
projectID := "projectID"
config := &DatafileProjectConfig{
Expand Down Expand Up @@ -88,6 +98,14 @@ func TestGetAnonymizeIP(t *testing.T) {
assert.Equal(t, anonymizeIP, config.GetAnonymizeIP())
}

func TestGetAttributes(t *testing.T) {
config := &DatafileProjectConfig{
attributeMap: map[string]entities.Attribute{"id1": {ID: "id1", Key: "key"}, "id2": {ID: "id1", Key: "key"}},
}

assert.Equal(t, []entities.Attribute{config.attributeMap["id1"], config.attributeMap["id2"]}, config.GetAttributes())
}

func TestGetAttributeID(t *testing.T) {
id := "id"
key := "key"
Expand Down Expand Up @@ -115,6 +133,13 @@ func TestGetEnvironmentKey(t *testing.T) {
assert.Equal(t, "production", config.GetEnvironmentKey())
}

func TestGetEvents(t *testing.T) {
config := &DatafileProjectConfig{
eventMap: map[string]entities.Event{"key": {ID: "5", Key: "key"}},
}
assert.Equal(t, []entities.Event{config.eventMap["key"]}, config.GetEvents())
}

func TestGetBotFiltering(t *testing.T) {
botFiltering := true
config := &DatafileProjectConfig{
Expand Down Expand Up @@ -298,20 +323,34 @@ func TestGetExperimentList(t *testing.T) {
experiment := entities.Experiment{
Key: key,
}

experimentMap := make(map[string]entities.Experiment)
experimentMap[id] = experiment

config := &DatafileProjectConfig{
experimentKeyToIDMap: experimentKeyToIDMap,
experimentMap: experimentMap,
}

experiments := config.GetExperimentList()

assert.Equal(t, 1, len(experiments))
assert.Equal(t, experiment, experiments[0])
}

func TestGetRolloutList(t *testing.T) {
config := &DatafileProjectConfig{
rollouts: []entities.Rollout{{ID: "5"}},
}
assert.Equal(t, config.rollouts, config.GetRolloutList())
}

func TestGetAudienceList(t *testing.T) {
config := &DatafileProjectConfig{
audienceMap: map[string]entities.Audience{"5": {ID: "5", Name: "one"}, "6": {ID: "6", Name: "two"}},
}
assert.ElementsMatch(t, []entities.Audience{config.audienceMap["5"], config.audienceMap["6"]}, config.GetAudienceList())
}

func TestGetAudienceByID(t *testing.T) {
id := "id"
audience := entities.Audience{
Expand Down Expand Up @@ -409,6 +448,13 @@ func TestGetGroupByID(t *testing.T) {
assert.Equal(t, group, actual)
}

func TestSendFlagDecisions(t *testing.T) {
config := &DatafileProjectConfig{
sendFlagDecisions: true,
}
assert.Equal(t, config.sendFlagDecisions, config.SendFlagDecisions())
}

func TestGetGroupByIDMissingIDError(t *testing.T) {
config := &DatafileProjectConfig{}
_, err := config.GetGroupByID("id")
Expand Down
9 changes: 5 additions & 4 deletions pkg/config/datafileprojectconfig/mappers/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ func MapAttributes(attributes []datafileEntities.Attribute) (attributeMap map[st
attributeMap = make(map[string]entities.Attribute)
attributeKeyToIDMap = make(map[string]string)
for _, attribute := range attributes {
tmpAttribute := entities.Attribute{
ID: attribute.ID,
Key: attribute.Key,
}
_, ok := attributeMap[attribute.ID]
if !ok {
attributeMap[attribute.ID] = entities.Attribute{
ID: attribute.ID,
Key: attribute.Key,
}
attributeMap[attribute.ID] = tmpAttribute
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of this change?

attributeKeyToIDMap[attribute.Key] = attribute.ID
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/datafileprojectconfig/mappers/attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ func TestMapAttributes(t *testing.T) {

attributeMap, attributeKeyToIDMap := MapAttributes(attrList)

expectedAttributeMap := map[string]entities.Attribute{"1": {"1", "one"},
"2": {"2", "two"}, "3": {"3", "three"}, "5": {"5", "one"}}
expectedAttributeMap := map[string]entities.Attribute{"1": {ID: "1", Key: "one"},
"2": {ID: "2", Key: "two"}, "3": {ID: "3", Key: "three"}, "5": {ID: "5", Key: "one"}}
expectedAttributeKeyToIDMap := map[string]string{"one": "5", "three": "3", "two": "2"}

assert.Equal(t, attributeMap, expectedAttributeMap)
Expand Down
25 changes: 14 additions & 11 deletions pkg/config/datafileprojectconfig/mappers/audience.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019,2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand All @@ -23,22 +23,25 @@ import (
)

// MapAudiences maps the raw datafile audience entities to SDK Audience entities
func MapAudiences(audiences []datafileEntities.Audience) map[string]entities.Audience {
func MapAudiences(audiences []datafileEntities.Audience) (audienceMap map[string]entities.Audience) {

audienceMap := make(map[string]entities.Audience)
audienceMap = make(map[string]entities.Audience)
// Since typed audiences were added prior to audiences,
// they will be given priority in the audienceMap and list
for _, audience := range audiences {
_, ok := audienceMap[audience.ID]
if !ok {
conditionTree, err := buildConditionTree(audience.Conditions)
if err != nil {
// @TODO: handle error
func() {}() // cheat the linters
audience := entities.Audience{
ID: audience.ID,
Name: audience.Name,
Conditions: audience.Conditions,
}
audienceMap[audience.ID] = entities.Audience{
ID: audience.ID,
Name: audience.Name,
ConditionTree: conditionTree,
conditionTree, err := buildConditionTree(audience.Conditions)
if err == nil {
audience.ConditionTree = conditionTree
}

audienceMap[audience.ID] = audience
}
}
return audienceMap
Expand Down
42 changes: 32 additions & 10 deletions pkg/config/datafileprojectconfig/mappers/audience_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019, Optimizely, Inc. and contributors *
* Copyright 2019,2021 Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -29,21 +29,43 @@ import (
func TestMapAudiencesEmptyList(t *testing.T) {

audienceMap := MapAudiences(nil)

expectedAudienceMap := map[string]entities.Audience{}

assert.Equal(t, audienceMap, expectedAudienceMap)

assert.Equal(t, expectedAudienceMap, audienceMap)
}
func TestMapAudiences(t *testing.T) {

audienceList := []datafileEntities.Audience{{ID: "1", Name: "one"}, {ID: "2", Name: "two"},
{ID: "3", Name: "three"}, {ID: "2", Name: "four"}, {ID: "5", Name: "one"}}
func TestMapAudiences(t *testing.T) {

expectedConditions := "[\"and\", [\"or\", [\"or\", {\"name\": \"s_foo\", \"type\": \"custom_attribute\", \"value\": \"foo\"}]]]"
audienceList := []datafileEntities.Audience{{ID: "1", Name: "one", Conditions: expectedConditions}, {ID: "2", Name: "two"},
{ID: "3", Name: "three"}, {ID: "2", Name: "four"}, {ID: "1", Name: "one"}}
audienceMap := MapAudiences(audienceList)

expectedAudienceMap := map[string]entities.Audience{"1": {ID: "1", Name: "one"}, "2": {ID: "2", Name: "two"},
"3": {ID: "3", Name: "three"}, "5": {ID: "5", Name: "one"}}
expectedConditionTree := &entities.TreeNode{
Operator: "and",
Nodes: []*entities.TreeNode{
{
Operator: "or",
Nodes: []*entities.TreeNode{
{
Operator: "or",
Nodes: []*entities.TreeNode{
{
Item: entities.Condition{
Name: "s_foo",
Type: "custom_attribute",
Value: "foo",
StringRepresentation: `{"name":"s_foo","type":"custom_attribute","value":"foo"}`,
},
},
},
},
},
},
},
}
expectedAudienceMap := map[string]entities.Audience{"1": {ID: "1", Name: "one", ConditionTree: expectedConditionTree, Conditions: expectedConditions}, "2": {ID: "2", Name: "two"},
"3": {ID: "3", Name: "three"}}

assert.Equal(t, audienceMap, expectedAudienceMap)
assert.Equal(t, expectedAudienceMap, audienceMap)
}
25 changes: 17 additions & 8 deletions pkg/config/datafileprojectconfig/mappers/condition_trees.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/****************************************************************************
* Copyright 2019-2020, Optimizely, Inc. and contributors *
* Copyright 2019-2021, Optimizely, Inc. and contributors *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
Expand Down Expand Up @@ -28,6 +28,16 @@ import (
var errEmptyTree = errors.New("empty tree")
var json = jsoniter.ConfigCompatibleWithStandardLibrary

// OperatorType defines logical operator for conditions
type OperatorType string

// Default conditional operators
const (
And OperatorType = "and"
Or OperatorType = "or"
Not OperatorType = "not"
)

// Takes the conditions array from the audience in the datafile and turns it into a condition tree
func buildConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, retErr error) {

Expand Down Expand Up @@ -135,7 +145,6 @@ func createLeafCondition(typedV map[string]interface{}, node *entities.TreeNode)
// Takes the conditions array from the audience in the datafile and turns it into a condition tree
func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities.TreeNode, err error) {

var operators = []string{"or", "and", "not"} // any other operators?
value := reflect.ValueOf(conditions)
visited := make(map[interface{}]bool)

Expand All @@ -161,7 +170,7 @@ func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities
n := &entities.TreeNode{}
typedV := v.Index(i).Interface()
if value, ok := typedV.(string); ok {
if stringInSlice(value, operators) {
if isValidOperator(value) {
n.Operator = typedV.(string)
root.Operator = n.Operator
continue
Expand All @@ -188,11 +197,11 @@ func buildAudienceConditionTree(conditions interface{}) (conditionTree *entities
return conditionTree, err
}

func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
func isValidOperator(op string) bool {
operator := OperatorType(op)
switch operator {
case And, Or, Not:
return true
}
return false
}
6 changes: 3 additions & 3 deletions pkg/config/datafileprojectconfig/mappers/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import (
)

// MapEvents maps the raw datafile event entities to SDK Event entities
func MapEvents(events []datafileEntities.Event) map[string]entities.Event {
eventMap := make(map[string]entities.Event)
func MapEvents(events []datafileEntities.Event) (eventMap map[string]entities.Event) {
eventMap = make(map[string]entities.Event)
for _, event := range events {
entityEvent := entities.Event{ID: event.ID, Key: event.Key, ExperimentIds: event.ExperimentIds}
entityEvent := entities.Event(event)
eventMap[entityEvent.Key] = entityEvent
}

Expand Down
Loading