Skip to content

Commit

Permalink
feat(gherkin-onTrack): Implemented onTrack support for gherkin. (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
yasirfolio3 authored and Michael Ng committed Dec 12, 2019
1 parent 5477d16 commit 6f69a3a
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 95 deletions.
2 changes: 1 addition & 1 deletion tests/integration/models/api_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ import (
type APIResponse struct {
Result interface{}
Type entities.VariableType
ListenerCalled []DecisionListener
ListenerCalled []interface{}
}
6 changes: 6 additions & 0 deletions tests/integration/models/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,9 @@ const (

// KeyListenerCalled - Key for listener called
const KeyListenerCalled = "listener_called"

// KeyDecision - Key for Decision listener
const KeyDecision = "Decision"

// KeyTrack - Key for Track listener
const KeyTrack = "Track"
25 changes: 25 additions & 0 deletions tests/integration/models/track_listener_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/****************************************************************************
* Copyright 2019, 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. *
* You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
***************************************************************************/

package models

// TrackListener represents a track notification
type TrackListener struct {
EventKey string `yaml:"event_key"`
Attributes map[string]interface{} `yaml:"attributes"`
UserID string `yaml:"user_id"`
EventTags map[string]interface{} `yaml:"event_tags"`
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,41 @@
package optlyplugins

import (
"github.com/optimizely/go-sdk/pkg/client"
"github.com/optimizely/go-sdk/pkg/decision"
"github.com/optimizely/go-sdk/pkg/entities"
"github.com/optimizely/go-sdk/pkg/event"
"github.com/optimizely/go-sdk/pkg/notification"
"github.com/optimizely/go-sdk/tests/integration/models"
)

// TestCompositeService represents a CompositeService with custom implementations
type TestCompositeService struct {
decision.CompositeService
listenersCalled []models.DecisionListener
// NotificationManager manager class for notification listeners
type NotificationManager struct {
listenersCalled []interface{}
}

// AddListeners - Adds Notification Listeners
func (c *TestCompositeService) AddListeners(listeners map[string]int) {
// SubscribeNotifications subscribes to the provided notification listeners
func (n *NotificationManager) SubscribeNotifications(listeners map[string]int, client *client.OptimizelyClient) {

if len(listeners) < 1 {
return
}
for listenerType, count := range listeners {
for i := 1; i <= count; i++ {
switch listenerType {
case "Decision":
c.OnDecision(c.decisionNotificationCallback)
break
default:
break
}
addNotificationCallback := func(notificationType string) {
switch notificationType {
case models.KeyDecision:
client.DecisionService.OnDecision(n.decisionCallback)
break
case models.KeyTrack:
client.OnTrack(n.trackCallback)
break
}
}
}

// GetListenersCalled - Returns listeners called
func (c *TestCompositeService) GetListenersCalled() []models.DecisionListener {
listenerCalled := c.listenersCalled
// Since for every scenario, a new sdk instance is created, emptying listenersCalled is required for scenario's
// where multiple requests are executed but no session is to be maintained among them.
// @TODO: Make it optional once event-batching(sessioned) tests are implemented.
c.listenersCalled = nil
return listenerCalled
for key, count := range listeners {
for i := 0; i < count; i++ {
addNotificationCallback(key)
}
}
}

func (c *TestCompositeService) decisionNotificationCallback(notification notification.DecisionNotification) {
func (n *NotificationManager) decisionCallback(notification notification.DecisionNotification) {

model := models.DecisionListener{}
model.Type = notification.Type
Expand All @@ -71,7 +64,17 @@ func (c *TestCompositeService) decisionNotificationCallback(notification notific

decisionInfoDict := getDecisionInfoForNotification(notification)
model.DecisionInfo = decisionInfoDict
c.listenersCalled = append(c.listenersCalled, model)
n.listenersCalled = append(n.listenersCalled, model)
}

func (n *NotificationManager) trackCallback(eventKey string, userContext entities.UserContext, eventTags map[string]interface{}, conversionEvent event.ConversionEvent) {
listener := models.TrackListener{
EventKey: eventKey,
UserID: userContext.ID,
Attributes: userContext.Attributes,
EventTags: eventTags,
}
n.listenersCalled = append(n.listenersCalled, listener)
}

func getDecisionInfoForNotification(decisionNotification notification.DecisionNotification) map[string]interface{} {
Expand Down Expand Up @@ -135,3 +138,13 @@ func getDecisionInfoForNotification(decisionNotification notification.DecisionNo
}
return decisionInfoDict
}

// GetListenersCalled - Returns listeners called
func (n *NotificationManager) GetListenersCalled() []interface{} {
listenerCalled := n.listenersCalled
// Since for every scenario, a new sdk instance is created, emptying listenersCalled is required for scenario's
// where multiple requests are executed but no session is to be maintained among them.
// @TODO: Make it optional once event-batching(sessioned) tests are implemented.
n.listenersCalled = nil
return listenerCalled
}
61 changes: 29 additions & 32 deletions tests/integration/support/client_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ var sdkKey int

// ClientWrapper - wrapper around the optimizely client that keeps track of various custom components used with the client
type ClientWrapper struct {
Client *client.OptimizelyClient
DecisionService decision.Service
EventDispatcher event.Dispatcher
UserProfileService decision.UserProfileService
OverrideStore decision.ExperimentOverrideStore
client *client.OptimizelyClient
eventDispatcher event.Dispatcher
userProfileService decision.UserProfileService
overrideStore decision.ExperimentOverrideStore
notificationManager *optlyplugins.NotificationManager
}

// DeleteInstance deletes cached instance of optly wrapper
Expand Down Expand Up @@ -98,28 +98,25 @@ func GetInstance(apiOptions models.APIOptions) *ClientWrapper {
)

// @TODO: Add sdkKey dynamically once event-batching support is implemented

compositeService := *decision.NewCompositeService(strconv.Itoa(sdkKey), decision.WithCompositeExperimentService(compositeExperimentService))
decisionService := &optlyplugins.TestCompositeService{CompositeService: compositeService}

compositeService := decision.NewCompositeService(strconv.Itoa(sdkKey), decision.WithCompositeExperimentService(compositeExperimentService))
client, err := optimizelyFactory.Client(
client.WithConfigManager(configManager),
client.WithDecisionService(decisionService),
client.WithDecisionService(compositeService),
client.WithEventProcessor(eventProcessor),
)
if err != nil {
log.Fatal(err)
}

notificationManager := optlyplugins.NotificationManager{}
notificationManager.SubscribeNotifications(apiOptions.Listeners, client)
clientInstance = &ClientWrapper{
Client: client,
DecisionService: decisionService,
EventDispatcher: eventProcessor.EventDispatcher,
UserProfileService: userProfileService,
OverrideStore: overrideStore,
client: client,
eventDispatcher: eventProcessor.EventDispatcher,
userProfileService: userProfileService,
overrideStore: overrideStore,
notificationManager: &notificationManager,
}
clientInstance.DecisionService.(*optlyplugins.TestCompositeService).AddListeners(apiOptions.Listeners)

return clientInstance
}

Expand Down Expand Up @@ -170,8 +167,8 @@ func (c *ClientWrapper) InvokeAPI(request models.APIOptions) (models.APIResponse
break
}
// TODO: For event batching, it should be conditional.
c.Client.Close()
response.ListenerCalled = c.DecisionService.(*optlyplugins.TestCompositeService).GetListenersCalled()
c.client.Close()
response.ListenerCalled = c.notificationManager.GetListenersCalled()
return response, err
}

Expand All @@ -185,7 +182,7 @@ func (c *ClientWrapper) isFeatureEnabled(request models.APIOptions) (models.APIR
Attributes: params.Attributes,
}

isEnabled, err := c.Client.IsFeatureEnabled(params.FeatureKey, user)
isEnabled, err := c.client.IsFeatureEnabled(params.FeatureKey, user)
result := "false"
if err == nil && isEnabled {
result = "true"
Expand All @@ -204,7 +201,7 @@ func (c *ClientWrapper) getFeatureVariable(request models.APIOptions) (models.AP
ID: params.UserID,
Attributes: params.Attributes,
}
value, valueType, err := c.Client.GetFeatureVariable(params.FeatureKey, params.VariableKey, user)
value, valueType, err := c.client.GetFeatureVariable(params.FeatureKey, params.VariableKey, user)
if err == nil {
response.Result = value
response.Type = valueType
Expand All @@ -222,7 +219,7 @@ func (c *ClientWrapper) getFeatureVariableInteger(request models.APIOptions) (mo
ID: params.UserID,
Attributes: params.Attributes,
}
value, err := c.Client.GetFeatureVariableInteger(params.FeatureKey, params.VariableKey, user)
value, err := c.client.GetFeatureVariableInteger(params.FeatureKey, params.VariableKey, user)
if err == nil {
response.Result = value
}
Expand All @@ -239,7 +236,7 @@ func (c *ClientWrapper) getFeatureVariableDouble(request models.APIOptions) (mod
ID: params.UserID,
Attributes: params.Attributes,
}
value, err := c.Client.GetFeatureVariableDouble(params.FeatureKey, params.VariableKey, user)
value, err := c.client.GetFeatureVariableDouble(params.FeatureKey, params.VariableKey, user)
if err == nil {
response.Result = value
}
Expand All @@ -256,7 +253,7 @@ func (c *ClientWrapper) getFeatureVariableBoolean(request models.APIOptions) (mo
ID: params.UserID,
Attributes: params.Attributes,
}
value, err := c.Client.GetFeatureVariableBoolean(params.FeatureKey, params.VariableKey, user)
value, err := c.client.GetFeatureVariableBoolean(params.FeatureKey, params.VariableKey, user)
if err == nil {
response.Result = value
}
Expand All @@ -273,7 +270,7 @@ func (c *ClientWrapper) getFeatureVariableString(request models.APIOptions) (mod
ID: params.UserID,
Attributes: params.Attributes,
}
value, err := c.Client.GetFeatureVariableString(params.FeatureKey, params.VariableKey, user)
value, err := c.client.GetFeatureVariableString(params.FeatureKey, params.VariableKey, user)
if err == nil {
response.Result = value
}
Expand All @@ -291,7 +288,7 @@ func (c *ClientWrapper) getEnabledFeatures(request models.APIOptions) (models.AP
ID: params.UserID,
Attributes: params.Attributes,
}
if values, err := c.Client.GetEnabledFeatures(user); err == nil {
if values, err := c.client.GetEnabledFeatures(user); err == nil {
enabledFeatures = values
}
response.Result = enabledFeatures
Expand All @@ -308,7 +305,7 @@ func (c *ClientWrapper) getVariation(request models.APIOptions) (models.APIRespo
ID: params.UserID,
Attributes: params.Attributes,
}
response.Result, _ = c.Client.GetVariation(params.ExperimentKey, user)
response.Result, _ = c.client.GetVariation(params.ExperimentKey, user)
if response.Result == "" {
response.Result = "NULL"
}
Expand All @@ -325,7 +322,7 @@ func (c *ClientWrapper) activate(request models.APIOptions) (models.APIResponse,
ID: params.UserID,
Attributes: params.Attributes,
}
response.Result, _ = c.Client.Activate(params.ExperimentKey, user)
response.Result, _ = c.client.Activate(params.ExperimentKey, user)
if response.Result == "" {
response.Result = "NULL"
}
Expand All @@ -342,7 +339,7 @@ func (c *ClientWrapper) track(request models.APIOptions) (models.APIResponse, er
ID: params.UserID,
Attributes: params.Attributes,
}
err = c.Client.Track(params.EventKey, user, params.EventTags)
err = c.client.Track(params.EventKey, user, params.EventTags)
}
response.Result = "NULL"
return response, err
Expand All @@ -356,9 +353,9 @@ func (c *ClientWrapper) setForcedVariation(request models.APIOptions) (models.AP
if err == nil {
// For removeForcedVariation cases
if params.VariationKey == "" {
c.OverrideStore.(*decision.MapExperimentOverridesStore).RemoveVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID})
c.overrideStore.(*decision.MapExperimentOverridesStore).RemoveVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID})
} else {
c.OverrideStore.(*decision.MapExperimentOverridesStore).SetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID}, params.VariationKey)
c.overrideStore.(*decision.MapExperimentOverridesStore).SetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID}, params.VariationKey)
response.Result = params.VariationKey
}
}
Expand All @@ -371,7 +368,7 @@ func (c *ClientWrapper) getForcedVariation(request models.APIOptions) (models.AP
err := yaml.Unmarshal([]byte(request.Arguments), &params)
response.Result = "NULL"
if err == nil {
variation, success := c.OverrideStore.(*decision.MapExperimentOverridesStore).GetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID})
variation, success := c.overrideStore.(*decision.MapExperimentOverridesStore).GetVariation(decision.ExperimentOverrideKey{ExperimentKey: params.ExperimentKey, UserID: params.UserID})
if success {
response.Result = variation
}
Expand Down
Loading

0 comments on commit 6f69a3a

Please sign in to comment.