Skip to content

Commit 428af14

Browse files
authored
Merge pull request #225 from splitio/SDKS-9120-impressoion-per-toggle
[SDKS-9120] Impression per toggle implementation
2 parents 1178dcf + 3037a39 commit 428af14

File tree

14 files changed

+543
-126
lines changed

14 files changed

+543
-126
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
6.7.0 (Jan 17, 2025)
2+
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs.
3+
14
6.6.0 (May 14, 2024)
25
- Updated go-split-commons to v6
36
- Added support for targeting rules based on semantic versions (https://semver.org/).

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright © 2024 Split Software, Inc.
1+
Copyright © 2025 Split Software, Inc.
22

33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ module github.com/splitio/go-client/v6
33
go 1.18
44

55
require (
6-
github.com/splitio/go-split-commons/v6 v6.0.0
6+
github.com/splitio/go-split-commons/v6 v6.1.0
77
github.com/splitio/go-toolkit/v5 v5.4.0
88
)
99

1010
require (
1111
github.com/bits-and-blooms/bitset v1.3.1 // indirect
1212
github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect
1313
github.com/cespare/xxhash/v2 v2.2.0 // indirect
14+
github.com/davecgh/go-spew v1.1.1 // indirect
1415
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
1517
github.com/redis/go-redis/v9 v9.0.4 // indirect
18+
github.com/stretchr/objx v0.5.2 // indirect
19+
github.com/stretchr/testify v1.9.0 // indirect
1620
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
1721
golang.org/x/sync v0.3.0 // indirect
1822
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
66
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
77
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
88
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
9+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
911
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1012
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1115
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
1216
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
13-
github.com/splitio/go-split-commons/v6 v6.0.0 h1:qenr5qbXafjvM832C64CVpjtlShuQiWCwtR5I2h4ogM=
14-
github.com/splitio/go-split-commons/v6 v6.0.0/go.mod h1:TsvIh3XP7yjc7ly4vpj06AkoBND36SodPs5qfhb8rHc=
17+
github.com/splitio/go-split-commons/v6 v6.1.0 h1:k3mwr12DF6gbEaV8XXU/tSAQlPkIEuzIgTEneYhGg2I=
18+
github.com/splitio/go-split-commons/v6 v6.1.0/go.mod h1:D/XIY/9Hmfk9ivWsRsJVp439kEdmHbzUi3PKzQQDOXY=
1519
github.com/splitio/go-toolkit/v5 v5.4.0 h1:g5WFpRhQomnXCmvfsNOWV4s5AuUrWIZ+amM68G8NBKM=
1620
github.com/splitio/go-toolkit/v5 v5.4.0/go.mod h1:xYhUvV1gga9/1029Wbp5pjnR6Cy8nvBpjw99wAbsMko=
21+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
22+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
23+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
24+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1725
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
1826
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
1927
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=

splitio/client/client.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ func (c *SplitClient) getEvaluationResult(matchingKey string, bucketingKey *stri
6262
c.logger.Warning(fmt.Sprintf("%s: the SDK is not ready, results may be incorrect for feature flag %s. Make sure to wait for SDK readiness before using this method", operation, featureFlag))
6363
c.initTelemetry.RecordNonReadyUsage()
6464
return &evaluator.Result{
65-
Treatment: evaluator.Control,
66-
Label: impressionlabels.ClientNotReady,
67-
Config: nil,
65+
Treatment: evaluator.Control,
66+
Label: impressionlabels.ClientNotReady,
67+
Config: nil,
68+
ImpressionsDisabled: false,
6869
}
6970
}
7071

@@ -82,16 +83,17 @@ func (c *SplitClient) getEvaluationsResult(matchingKey string, bucketingKey *str
8283
}
8384
for _, featureFlag := range featureFlags {
8485
result.Evaluations[featureFlag] = evaluator.Result{
85-
Treatment: evaluator.Control,
86-
Label: impressionlabels.ClientNotReady,
87-
Config: nil,
86+
Treatment: evaluator.Control,
87+
Label: impressionlabels.ClientNotReady,
88+
Config: nil,
89+
ImpressionsDisabled: false,
8890
}
8991
}
9092
return result
9193
}
9294

9395
// createImpression creates impression to be stored and used by listener
94-
func (c *SplitClient) createImpression(featureFlag string, bucketingKey *string, evaluationLabel string, matchingKey string, treatment string, changeNumber int64) dtos.Impression {
96+
func (c *SplitClient) createImpression(featureFlag string, bucketingKey *string, evaluationLabel string, matchingKey string, treatment string, changeNumber int64, disabled bool) dtos.Impression {
9597
var label string
9698
if c.factory.cfg.LabelsEnabled {
9799
label = evaluationLabel
@@ -110,18 +112,21 @@ func (c *SplitClient) createImpression(featureFlag string, bucketingKey *string,
110112
Label: label,
111113
Treatment: treatment,
112114
Time: time.Now().UTC().UnixNano() / int64(time.Millisecond), // Convert standard timestamp to java's ms timestamps
115+
Disabled: disabled,
113116
}
114117
}
115118

116119
// storeData stores impression, runs listener and stores metrics
117120
func (c *SplitClient) storeData(impressions []dtos.Impression, attributes map[string]interface{}, metricsLabel string, evaluationTime time.Duration) {
118121
// Store impression
119122
if c.impressions != nil {
120-
forLog, forListener := c.impressionManager.ProcessImpressions(impressions)
123+
listenerEnabled := c.impressionListener != nil
124+
125+
forLog, forListener := c.impressionManager.Process(impressions, listenerEnabled)
121126
c.impressions.LogImpressions(forLog)
122127

123128
// Custom Impression Listener
124-
if c.impressionListener != nil {
129+
if listenerEnabled {
125130
c.impressionListener.SendDataToClient(forListener, attributes)
126131
}
127132
} else {
@@ -177,7 +182,7 @@ func (c *SplitClient) doTreatmentCall(key interface{}, featureFlag string, attri
177182
}
178183

179184
c.storeData(
180-
[]dtos.Impression{c.createImpression(featureFlag, bucketingKey, evaluationResult.Label, matchingKey, evaluationResult.Treatment, evaluationResult.SplitChangeNumber)},
185+
[]dtos.Impression{c.createImpression(featureFlag, bucketingKey, evaluationResult.Label, matchingKey, evaluationResult.Treatment, evaluationResult.SplitChangeNumber, evaluationResult.ImpressionsDisabled)},
181186
attributes,
182187
metricsLabel,
183188
evaluationResult.EvaluationTime,
@@ -227,7 +232,7 @@ func (c *SplitClient) processResult(result evaluator.Results, operation string,
227232
Config: nil,
228233
}
229234
} else {
230-
bulkImpressions = append(bulkImpressions, c.createImpression(feature, bucketingKey, evaluation.Label, matchingKey, evaluation.Treatment, evaluation.SplitChangeNumber))
235+
bulkImpressions = append(bulkImpressions, c.createImpression(feature, bucketingKey, evaluation.Label, matchingKey, evaluation.Treatment, evaluation.SplitChangeNumber, evaluation.ImpressionsDisabled))
231236

232237
treatments[feature] = TreatmentResult{
233238
Treatment: evaluation.Treatment,

splitio/client/client_test.go

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func getFactory() SplitFactory {
140140
impressionObserver, _ := strategy.NewImpressionObserver(500)
141141
impressionsCounter := strategy.NewImpressionsCounter()
142142
impressionsStrategy := strategy.NewOptimizedImpl(impressionObserver, impressionsCounter, telemetryStorage, false)
143-
impressionManager := provisional.NewImpressionManager(impressionsStrategy)
143+
impressionManager := provisional.NewImpressionManager(impressionsStrategy).(*provisional.ImpressionManagerImpl)
144144

145145
return SplitFactory{
146146
cfg: cfg,
@@ -166,7 +166,7 @@ func getFactoryByFlagSets() SplitFactory {
166166
impressionObserver, _ := strategy.NewImpressionObserver(500)
167167
impressionsCounter := strategy.NewImpressionsCounter()
168168
impressionsStrategy := strategy.NewOptimizedImpl(impressionObserver, impressionsCounter, telemetryStorage, false)
169-
impressionManager := provisional.NewImpressionManager(impressionsStrategy)
169+
impressionManager := provisional.NewImpressionManager(impressionsStrategy).(*provisional.ImpressionManagerImpl)
170170

171171
return SplitFactory{
172172
cfg: cfg,
@@ -473,7 +473,7 @@ func TestClientPanicking(t *testing.T) {
473473
impressionObserver, _ := strategy.NewImpressionObserver(500)
474474
impressionsCounter := strategy.NewImpressionsCounter()
475475
impressionsStrategy := strategy.NewOptimizedImpl(impressionObserver, impressionsCounter, telemetryMockedStorage, false)
476-
impressionManager := provisional.NewImpressionManager(impressionsStrategy)
476+
impressionManager := provisional.NewImpressionManager(impressionsStrategy).(*provisional.ImpressionManagerImpl)
477477

478478
factory := SplitFactory{
479479
cfg: cfg,
@@ -650,7 +650,7 @@ func getClientForListener() SplitClient {
650650
impressionObserver, _ := strategy.NewImpressionObserver(500)
651651
impressionsCounter := strategy.NewImpressionsCounter()
652652
impressionsStrategy := strategy.NewOptimizedImpl(impressionObserver, impressionsCounter, telemetryMockedStorage, true)
653-
impressionManager := provisional.NewImpressionManager(impressionsStrategy)
653+
impressionManager := provisional.NewImpressionManager(impressionsStrategy).(*provisional.ImpressionManagerImpl)
654654

655655
factory := &SplitFactory{
656656
cfg: cfg,
@@ -1372,7 +1372,7 @@ func TestClient(t *testing.T) {
13721372

13731373
impressionObserver, _ := strategy.NewImpressionObserver(500)
13741374
impressionsStrategy := strategy.NewDebugImpl(impressionObserver, true)
1375-
impressionManager := provisional.NewImpressionManager(impressionsStrategy)
1375+
impressionManager := provisional.NewImpressionManager(impressionsStrategy).(*provisional.ImpressionManagerImpl)
13761376

13771377
factory := &SplitFactory{cfg: cfg, impressionManager: impressionManager}
13781378
client := SplitClient{
@@ -2114,6 +2114,105 @@ func TestClientDebug(t *testing.T) {
21142114
}
21152115
}
21162116

2117+
func TestClientDebugWithImpressionsDisabledTrue(t *testing.T) {
2118+
var isDestroyCalled = false
2119+
var splitsMock, _ = ioutil.ReadFile("../../testdata/splits_mock_2.json")
2120+
2121+
postChannel := make(chan string, 1)
2122+
var count int64
2123+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2124+
switch r.URL.Path {
2125+
case "/splitChanges":
2126+
fmt.Fprintln(w, string(splitsMock))
2127+
return
2128+
case "/testImpressions/bulk":
2129+
if r.Header.Get("SplitSDKImpressionsMode") != commonsCfg.ImpressionsModeDebug {
2130+
t.Error("Wrong header")
2131+
}
2132+
2133+
if isDestroyCalled {
2134+
rBody, _ := ioutil.ReadAll(r.Body)
2135+
var dataInPost []map[string]interface{}
2136+
err := json.Unmarshal(rBody, &dataInPost)
2137+
if err != nil {
2138+
t.Error(err)
2139+
return
2140+
}
2141+
if len(dataInPost) != 1 {
2142+
t.Error("It should send two impressions in optimized mode. Actual: ", len(dataInPost))
2143+
}
2144+
if len(dataInPost[0]["i"].([]interface{})) != 3 {
2145+
t.Error("It should send only one impression per featureName")
2146+
}
2147+
}
2148+
2149+
fmt.Fprintln(w, "ok")
2150+
postChannel <- "finished"
2151+
case "/testImpressions/count":
2152+
atomic.AddInt64(&count, 1)
2153+
case "/events/bulk":
2154+
fmt.Fprintln(w, "ok")
2155+
case "/segmentChanges":
2156+
fallthrough
2157+
default:
2158+
fmt.Fprintln(w, "ok")
2159+
}
2160+
}))
2161+
defer ts.Close()
2162+
2163+
impTest := &ImpressionListenerTest{}
2164+
cfg := conf.Default()
2165+
cfg.LabelsEnabled = true
2166+
cfg.Advanced.EventsURL = ts.URL
2167+
cfg.Advanced.SdkURL = ts.URL
2168+
cfg.Advanced.TelemetryServiceURL = ts.URL
2169+
cfg.Advanced.AuthServiceURL = ts.URL
2170+
cfg.Advanced.ImpressionListener = impTest
2171+
cfg.ImpressionsMode = "Debug"
2172+
2173+
factory, _ := NewSplitFactory("test", cfg)
2174+
client := factory.Client()
2175+
client.BlockUntilReady(2)
2176+
2177+
// Calls treatments to generate one valid impression
2178+
time.Sleep(300 * time.Millisecond) // Let's wait until first call of recorders have finished
2179+
client.Treatment("user1", "DEMO_MURMUR2", nil)
2180+
impL1, _ := ilResult["DEMO_MURMUR2"].(map[string]interface{})
2181+
if impL1["Pt"].(int64) != 0 {
2182+
t.Error("Pt should be 0")
2183+
}
2184+
client.Treatment("user1", "DEMO_MURMUR2", nil)
2185+
impL2, _ := ilResult["DEMO_MURMUR2"].(map[string]interface{})
2186+
if impL2["Pt"] != impL1["Time"] {
2187+
t.Error("Pt should be equal to previos imp1")
2188+
}
2189+
client.Treatments("user1", []string{"DEMO_MURMUR2"}, nil)
2190+
impL3, _ := ilResult["DEMO_MURMUR2"].(map[string]interface{})
2191+
if impL3["Pt"] != impL2["Time"] {
2192+
t.Error("Pt should be equal to previos imp2")
2193+
}
2194+
2195+
client.Treatment("user1", "IMPRESSION_TOGGLE_FLAG", nil)
2196+
impL4, _ := ilResult["IMPRESSION_TOGGLE_FLAG"].(map[string]interface{})
2197+
if impL4["Pt"].(int64) != 0 {
2198+
t.Error("Pt should be 0")
2199+
}
2200+
2201+
isDestroyCalled = true
2202+
client.Destroy()
2203+
2204+
select {
2205+
case <-postChannel:
2206+
if atomic.LoadInt64(&count) != 1 {
2207+
t.Error("Impression Count should be 1. Actual: ", atomic.LoadInt64(&count))
2208+
}
2209+
return
2210+
case <-time.After(4 * time.Second):
2211+
t.Error("The test couldn't send impressions to check headers")
2212+
return
2213+
}
2214+
}
2215+
21172216
func TestUnsupportedMatcherAndSemver(t *testing.T) {
21182217
var isDestroyCalled = false
21192218
var splitsMock, _ = ioutil.ReadFile("../../testdata/splits_mock_3.json")
@@ -2357,7 +2456,7 @@ func TestTelemetryMemory(t *testing.T) {
23572456
t.Error("It should queue one impression")
23582457
}
23592458
if dataInPost.ImpressionsDeduped != 1 {
2360-
t.Error("It should dedupe one impression")
2459+
t.Error("It should dedupe one impression. ", dataInPost.ImpressionsDeduped)
23612460
}
23622461
if dataInPost.EventsQueued != 1 {
23632462
t.Error("It should queue one event")

0 commit comments

Comments
 (0)