Skip to content

Commit 327c59b

Browse files
yasirfolio3Michael Ng
authored andcommitted
ci(nl): enable notification listener (#194)
1 parent 8e7a004 commit 327c59b

File tree

12 files changed

+170
-47
lines changed

12 files changed

+170
-47
lines changed

pkg/config/datafileprojectconfig/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ func NewDatafileProjectConfig(jsonDatafile []byte) (*DatafileProjectConfig, erro
177177
rolloutMap := mappers.MapRollouts(datafile.Rollouts)
178178
eventMap := mappers.MapEvents(datafile.Events)
179179
mergedAudiences := append(datafile.TypedAudiences, datafile.Audiences...)
180+
featureMap := mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentMap)
180181
config := &DatafileProjectConfig{
181182
accountID: datafile.AccountID,
182183
anonymizeIP: datafile.AnonymizeIP,
@@ -188,7 +189,7 @@ func NewDatafileProjectConfig(jsonDatafile []byte) (*DatafileProjectConfig, erro
188189
experimentMap: experimentMap,
189190
groupMap: groupMap,
190191
eventMap: eventMap,
191-
featureMap: mappers.MapFeatures(datafile.FeatureFlags, rolloutMap, experimentMap),
192+
featureMap: featureMap,
192193
projectID: datafile.ProjectID,
193194
revision: datafile.Revision,
194195
rolloutMap: rolloutMap,

pkg/config/datafileprojectconfig/mappers/experiment.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func mapExperiment(rawExperiment datafileEntities.Experiment) entities.Experimen
8888
TrafficAllocation: make([]entities.Range, len(rawExperiment.TrafficAllocation)),
8989
AudienceConditionTree: audienceConditionTree,
9090
Whitelist: rawExperiment.ForcedVariations,
91+
IsFeatureExperiment: false,
9192
}
9293

9394
for _, variation := range rawExperiment.Variations {

pkg/config/datafileprojectconfig/mappers/feature.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424

2525
// MapFeatures maps the raw datafile feature flag entities to SDK Feature entities
2626
func MapFeatures(featureFlags []datafileEntities.FeatureFlag, rolloutMap map[string]entities.Rollout, experimentMap map[string]entities.Experiment,
27-
) map[string]entities.Feature {
27+
) (featureMap map[string]entities.Feature) {
2828

29-
featureMap := make(map[string]entities.Feature)
29+
featureMap = make(map[string]entities.Feature)
3030
for _, featureFlag := range featureFlags {
3131
feature := entities.Feature{
3232
Key: featureFlag.Key,
@@ -38,7 +38,9 @@ func MapFeatures(featureFlags []datafileEntities.FeatureFlag, rolloutMap map[str
3838
featureExperiments := []entities.Experiment{}
3939
for _, experimentID := range featureFlag.ExperimentIDs {
4040
if experiment, ok := experimentMap[experimentID]; ok {
41+
experiment.IsFeatureExperiment = true
4142
featureExperiments = append(featureExperiments, experiment)
43+
experimentMap[experimentID] = experiment
4244
}
4345
}
4446

pkg/config/datafileprojectconfig/mappers/feature_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestMapFeatures(t *testing.T) {
3030
"id": "21111",
3131
"key": "test_feature_21111",
3232
"rolloutId": "41111",
33-
"experimentIds": ["31111", "31112"],
33+
"experimentIds": ["31111"],
3434
"variables": [{"defaultValue":"1","id":"1","key":"test","type":"integer"}]
3535
}`
3636

@@ -50,6 +50,9 @@ func TestMapFeatures(t *testing.T) {
5050
"31112": experiment31112,
5151
}
5252
featureMap := MapFeatures(rawFeatureFlags, rolloutMap, experimentMap)
53+
54+
// Test MapFeatures should only change IsFeatureExperiment to true for experiment31111 since it belongs to a featureflag
55+
experiment31111.IsFeatureExperiment = true
5356
variable := entities.Variable{
5457
ID: "1",
5558
DefaultValue: "1",
@@ -61,10 +64,15 @@ func TestMapFeatures(t *testing.T) {
6164
ID: "21111",
6265
Key: "test_feature_21111",
6366
Rollout: rollout,
64-
FeatureExperiments: []entities.Experiment{experiment31111, experiment31112},
67+
FeatureExperiments: []entities.Experiment{experiment31111},
6568
VariableMap: map[string]entities.Variable{variable.Key: variable},
6669
},
6770
}
71+
expectedExperimentMap := map[string]entities.Experiment{
72+
"31111": experiment31111,
73+
"31112": experiment31112,
74+
}
6875

6976
assert.Equal(t, expectedFeatureMap, featureMap)
77+
assert.Equal(t, expectedExperimentMap, experimentMap)
7078
}

pkg/decision/composite_service.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (s CompositeService) GetFeatureDecision(featureDecisionContext FeatureDecis
7373
sourceInfo := map[string]string{}
7474

7575
if featureDecision.Source == FeatureTest {
76-
sourceInfo["experimentKey"] = featureDecisionContext.Feature.Key
76+
sourceInfo["experimentKey"] = featureDecision.Experiment.Key
7777
sourceInfo["variationKey"] = featureDecision.Variation.Key
7878
}
7979

@@ -86,11 +86,14 @@ func (s CompositeService) GetFeatureDecision(featureDecisionContext FeatureDecis
8686
if featureDecision.Variation != nil {
8787
featureInfo["featureEnabled"] = featureDecision.Variation.FeatureEnabled
8888
}
89+
90+
notificationType := notification.Feature
8991
variable := featureDecisionContext.Variable
9092
if variable.ID != "" && variable.Key != "" {
9193
featureInfo["variableKey"] = variable.Key
9294
featureInfo["variableType"] = variable.Type
9395

96+
notificationType = notification.FeatureVariable
9497
variableValue := variable.DefaultValue
9598

9699
if featureDecision.Variation != nil {
@@ -125,7 +128,7 @@ func (s CompositeService) GetFeatureDecision(featureDecisionContext FeatureDecis
125128

126129
decisionNotification := notification.DecisionNotification{
127130
DecisionInfo: decisionInfo,
128-
Type: notification.Feature,
131+
Type: notificationType,
129132
UserContext: userContext,
130133
}
131134
if err = s.notificationCenter.Send(notification.Decision, decisionNotification); err != nil {
@@ -141,16 +144,22 @@ func (s CompositeService) GetExperimentDecision(experimentDecisionContext Experi
141144
return experimentDecision, err
142145
}
143146

144-
if s.notificationCenter != nil && experimentDecision.Variation != nil {
147+
if s.notificationCenter != nil {
145148
decisionInfo := map[string]interface{}{
146149
"experimentKey": experimentDecisionContext.Experiment.Key,
147-
"variationKey": experimentDecision.Variation.Key,
150+
}
151+
152+
if experimentDecision.Variation != nil {
153+
decisionInfo["variationKey"] = experimentDecision.Variation.Key
148154
}
149155

150156
decisionNotification := notification.DecisionNotification{
151157
DecisionInfo: decisionInfo,
152-
Type: notification.ABTest,
153158
UserContext: userContext,
159+
Type: notification.ABTest,
160+
}
161+
if experimentDecisionContext.Experiment.IsFeatureExperiment {
162+
decisionNotification.Type = notification.FeatureTest
154163
}
155164

156165
if err = s.notificationCenter.Send(notification.Decision, decisionNotification); err != nil {

pkg/decision/composite_service_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type CompositeServiceFeatureTestSuite struct {
3535

3636
func (s *CompositeServiceFeatureTestSuite) SetupTest() {
3737
mockConfig := new(mockProjectConfig)
38+
3839
s.decisionContext = FeatureDecisionContext{
3940
Feature: &testFeat3333,
4041
ProjectConfig: mockConfig,
@@ -122,7 +123,7 @@ func (s *CompositeServiceFeatureTestSuite) TestDecisionListenersNotificationWith
122123
s.Equal(numberOfCalls, 1)
123124

124125
expectedDecisionInfo := map[string]interface{}{"feature": map[string]interface{}{"featureEnabled": false, "featureKey": "my_test_feature_3333", "source": FeatureTest,
125-
"sourceInfo": map[string]string{"experimentKey": "my_test_feature_3333", "variationKey": "2222"},
126+
"sourceInfo": map[string]string{"experimentKey": "test_experiment_1111", "variationKey": "2222"},
126127
"variableKey": "Key", "variableType": entities.Double, "variableValue": 23.34}}
127128

128129
s.Equal(expectedDecisionInfo, note.DecisionInfo)
@@ -160,7 +161,7 @@ func (s *CompositeServiceFeatureTestSuite) TestDecisionListenersNotificationWith
160161
s.Equal(numberOfCalls, 1)
161162

162163
expectedDecisionInfo := map[string]interface{}{"feature": map[string]interface{}{"featureEnabled": false, "featureKey": "my_test_feature_3333", "source": FeatureTest,
163-
"sourceInfo": map[string]string{"experimentKey": "my_test_feature_3333", "variationKey": "2222"},
164+
"sourceInfo": map[string]string{"experimentKey": "test_experiment_1111", "variationKey": "2222"},
164165
"variableKey": "Key", "variableType": entities.Integer, "variableValue": 23}}
165166

166167
s.Equal(expectedDecisionInfo, note.DecisionInfo)
@@ -198,7 +199,7 @@ func (s *CompositeServiceFeatureTestSuite) TestDecisionListenersNotificationWith
198199
s.Equal(numberOfCalls, 1)
199200

200201
expectedDecisionInfo := map[string]interface{}{"feature": map[string]interface{}{"featureEnabled": false, "featureKey": "my_test_feature_3333", "source": FeatureTest,
201-
"sourceInfo": map[string]string{"experimentKey": "my_test_feature_3333", "variationKey": "2222"},
202+
"sourceInfo": map[string]string{"experimentKey": "test_experiment_1111", "variationKey": "2222"},
202203
"variableKey": "Key", "variableType": entities.Boolean, "variableValue": true}}
203204

204205
s.Equal(expectedDecisionInfo, note.DecisionInfo)
@@ -236,7 +237,7 @@ func (s *CompositeServiceFeatureTestSuite) TestDecisionListenersNotificationWith
236237
s.Equal(numberOfCalls, 1)
237238

238239
expectedDecisionInfo := map[string]interface{}{"feature": map[string]interface{}{"featureEnabled": false, "featureKey": "my_test_feature_3333", "source": FeatureTest,
239-
"sourceInfo": map[string]string{"experimentKey": "my_test_feature_3333", "variationKey": "2222"},
240+
"sourceInfo": map[string]string{"experimentKey": "test_experiment_1111", "variationKey": "2222"},
240241
"variableKey": "Key", "variableType": entities.Double, "variableValue": "string"}}
241242

242243
s.Equal(expectedDecisionInfo, note.DecisionInfo)
@@ -269,7 +270,7 @@ func (s *CompositeServiceFeatureTestSuite) TestDecisionListenersNotificationWith
269270
s.Equal(numberOfCalls, 1)
270271

271272
expectedDecisionInfo := map[string]interface{}{"feature": map[string]interface{}{"featureEnabled": false, "featureKey": "my_test_feature_3333", "source": FeatureTest,
272-
"sourceInfo": map[string]string{"experimentKey": "my_test_feature_3333", "variationKey": "2222"}}}
273+
"sourceInfo": map[string]string{"experimentKey": "test_experiment_1111", "variationKey": "2222"}}}
273274

274275
s.Equal(expectedDecisionInfo, note.DecisionInfo)
275276
}
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
117
// Package utils //
218
package utils
319

420
import "reflect"
521

622
// ToFloat attempts to convert the given value to a float
723
func ToFloat(value interface{}) (float64, bool) {
24+
25+
if value == nil {
26+
return 0, false
27+
}
828
var floatType = reflect.TypeOf(float64(0))
929
v := reflect.ValueOf(value)
1030
v = reflect.Indirect(v)
1131

1232
if v.Type().String() == "float64" || v.Type().ConvertibleTo(floatType) {
1333
floatValue := v.Convert(floatType).Float()
1434
return floatValue, true
15-
}
1635

36+
}
1737
return 0, false
1838
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/****************************************************************************
2+
* Copyright 2019, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package utils
18+
19+
import (
20+
"testing"
21+
22+
"math"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestToFloat(t *testing.T) {
28+
29+
// Invalid values
30+
zeroValue := float64(0)
31+
32+
result, success := ToFloat("abc")
33+
assert.Equal(t, zeroValue, result)
34+
assert.Equal(t, false, success)
35+
36+
result, success = ToFloat("1212")
37+
assert.Equal(t, zeroValue, result)
38+
assert.Equal(t, false, success)
39+
40+
result, success = ToFloat(math.Inf)
41+
assert.Equal(t, zeroValue, result)
42+
assert.Equal(t, false, success)
43+
44+
result, success = ToFloat(nil)
45+
assert.Equal(t, zeroValue, result)
46+
assert.Equal(t, false, success)
47+
48+
result, success = ToFloat(true)
49+
assert.Equal(t, zeroValue, result)
50+
assert.Equal(t, false, success)
51+
52+
result, success = ToFloat([]string{})
53+
assert.Equal(t, zeroValue, result)
54+
assert.Equal(t, false, success)
55+
56+
result, success = ToFloat(map[string]string{})
57+
assert.Equal(t, zeroValue, result)
58+
assert.Equal(t, false, success)
59+
60+
// Valid values
61+
result, success = ToFloat(121.0)
62+
assert.Equal(t, float64(121), result)
63+
assert.Equal(t, true, success)
64+
65+
result, success = ToFloat(5000)
66+
assert.Equal(t, float64(5000), result)
67+
assert.Equal(t, true, success)
68+
}

pkg/entities/experiment.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type Experiment struct {
3636
GroupID string
3737
AudienceConditionTree *TreeNode
3838
Whitelist map[string]string
39+
IsFeatureExperiment bool
3940
}
4041

4142
// Range represents bucketing range that the specify entityID falls into

pkg/notification/entities.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const (
3737
ABTest DecisionNotificationType = "ab-test"
3838
// Feature is used when the decision is returned as part of evaluating a feature
3939
Feature DecisionNotificationType = "feature"
40+
// FeatureTest is used when the decision is returned as part of evaluating a feature test
41+
FeatureTest DecisionNotificationType = "feature-test"
42+
// FeatureVariable is used when the decision is returned as part of evaluating a feature with a variable
43+
FeatureVariable DecisionNotificationType = "feature-variable"
4044
// LogEvent notification type
4145
LogEvent Type = "log_event_notification"
4246
)
@@ -56,6 +60,6 @@ type ProjectConfigUpdateNotification struct {
5660

5761
// LogEventNotification is the notification triggered before log event is dispatched.
5862
type LogEventNotification struct {
59-
Type Type
60-
LogEvent interface{}
63+
Type Type
64+
LogEvent interface{}
6165
}

0 commit comments

Comments
 (0)