Skip to content

Commit 82c4b0f

Browse files
authored
Merge pull request #264 from splitio/impression-properties
Release Impression properties
2 parents 7e284de + 45fec27 commit 82c4b0f

12 files changed

Lines changed: 137 additions & 47 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535

3636
- name: SonarQube Scan (Push)
3737
if: ${{ github.event_name == 'push' }}
38-
uses: SonarSource/sonarqube-scan-action@v5.0.0
38+
uses: SonarSource/sonarqube-scan-action@v6.0.0
3939
env:
4040
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
4141
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -46,7 +46,7 @@ jobs:
4646
4747
- name: SonarQube Scan (Pull Request)
4848
if: ${{ github.event_name == 'pull_request' }}
49-
uses: SonarSource/sonarqube-scan-action@v5.0.0
49+
uses: SonarSource/sonarqube-scan-action@v6.0.0
5050
env:
5151
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
5252
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

CHANGES

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
9.1.0 (Dec 17, 2025)
2+
- Added impression properties.
3+
14
9.0.0 (Nov 21, 2025)
25
- BREAKING CHANGE:
36
- Changed new evaluator.

dtos/impression.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type Impression struct {
1111
Time int64 `json:"m"`
1212
Pt int64 `json:"pt,omitempty"`
1313
Disabled bool `json:"-"`
14+
Properties string `json:"properties,omitempty"`
1415
}
1516

1617
// ImpressionQueueObject struct mapping impressions
@@ -28,6 +29,7 @@ type ImpressionDTO struct {
2829
Label string `json:"r"`
2930
BucketingKey string `json:"b,omitempty"`
3031
Pt int64 `json:"pt,omitempty"`
32+
Properties string `json:"properties,omitempty"`
3133
}
3234

3335
// ImpressionsDTO struct mapping impressions to post
@@ -47,3 +49,7 @@ type ImpressionsInTimeFrameDTO struct {
4749
type ImpressionsCountDTO struct {
4850
PerFeature []ImpressionsInTimeFrameDTO `json:"pf"`
4951
}
52+
53+
type EvaluationOptions struct {
54+
Properties map[string]interface{}
55+
}

engine/evaluator/mocks/mocks.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
package mocks
22

3-
import "github.com/splitio/go-split-commons/v9/engine/evaluator"
3+
import (
4+
"github.com/splitio/go-split-commons/v9/engine/evaluator"
5+
"github.com/stretchr/testify/mock"
6+
)
47

58
// MockEvaluator mock evaluator
69
type MockEvaluator struct {
7-
EvaluateFeatureCall func(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *evaluator.Result
8-
EvaluateFeaturesCall func(key string, bucketingKey *string, features []string, attributes map[string]interface{}) evaluator.Results
9-
EvaluateFeatureByFlagSetsCall func(key string, bucketingKey *string, flagSets []string, attributes map[string]interface{}) evaluator.Results
10+
mock.Mock
1011
}
1112

1213
// EvaluateFeature mock
1314
func (m MockEvaluator) EvaluateFeature(key string, bucketingKey *string, feature string, attributes map[string]interface{}) *evaluator.Result {
14-
return m.EvaluateFeatureCall(key, bucketingKey, feature, attributes)
15+
args := m.Called(key, bucketingKey, feature, attributes)
16+
return args.Get(0).(*evaluator.Result)
1517
}
1618

1719
// EvaluateFeatures mock
1820
func (m MockEvaluator) EvaluateFeatures(key string, bucketingKey *string, features []string, attributes map[string]interface{}) evaluator.Results {
19-
return m.EvaluateFeaturesCall(key, bucketingKey, features, attributes)
21+
args := m.Called(key, bucketingKey, features, attributes)
22+
return args.Get(0).(evaluator.Results)
2023
}
2124

2225
// EvaluateFeaturesByFlagSets mock
2326
func (m MockEvaluator) EvaluateFeatureByFlagSets(key string, bucketingKey *string, flagSets []string, attributes map[string]interface{}) evaluator.Results {
24-
return m.EvaluateFeatureByFlagSetsCall(key, bucketingKey, flagSets, attributes)
27+
args := m.Called(key, bucketingKey, flagSets, attributes)
28+
return args.Get(0).(evaluator.Results)
2529
}

provisional/strategy/debug.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ func NewDebugImpl(impressionObserver ImpressionObserver, listenerEnabled bool) P
1717
}
1818

1919
func (s *DebugImpl) apply(impression *dtos.Impression) bool {
20-
impression.Pt, _ = s.impressionObserver.TestAndSet(impression.FeatureName, impression)
20+
if len(impression.Properties) == 0 {
21+
impression.Pt, _ = s.impressionObserver.TestAndSet(impression.FeatureName, impression)
22+
}
2123

2224
return true
2325
}

provisional/strategy/debug_test.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/splitio/go-split-commons/v9/dtos"
7+
"github.com/stretchr/testify/assert"
78
)
89

910
func TestDebugMode(t *testing.T) {
@@ -22,15 +23,39 @@ func TestDebugMode(t *testing.T) {
2223

2324
toLog, toListener := debug.Apply([]dtos.Impression{imp})
2425

25-
if len(toLog) != 1 || len(toListener) != 1 {
26-
t.Error("Should have 1 to log")
27-
}
26+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
27+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
2828

2929
toLog, toListener = debug.Apply([]dtos.Impression{imp})
3030

31-
if len(toLog) != 1 || len(toListener) != 1 {
32-
t.Error("Should have 1 to log")
31+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
32+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
33+
}
34+
35+
func TestDebugModeWithProperties(t *testing.T) {
36+
observer, _ := NewImpressionObserver(5000)
37+
debug := NewDebugImpl(observer, true)
38+
39+
imp := dtos.Impression{
40+
BucketingKey: "someBuck",
41+
ChangeNumber: 123,
42+
KeyName: "someKey",
43+
Label: "someLabel",
44+
Time: 123456,
45+
Treatment: "on",
46+
FeatureName: "feature-test",
47+
Properties: "{'hello':'world'}",
3348
}
49+
50+
toLog, toListener := debug.Apply([]dtos.Impression{imp})
51+
52+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
53+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
54+
55+
toLog, toListener = debug.Apply([]dtos.Impression{imp})
56+
57+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
58+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
3459
}
3560

3661
func TestApplySingleDebug(t *testing.T) {
@@ -48,13 +73,9 @@ func TestApplySingleDebug(t *testing.T) {
4873

4974
toLog := debug.ApplySingle(&imp)
5075

51-
if !toLog {
52-
t.Error("Should be true")
53-
}
76+
assert.True(t, toLog, "Should be true")
5477

5578
toLog = debug.ApplySingle(&imp)
5679

57-
if !toLog {
58-
t.Error("Should be true")
59-
}
80+
assert.True(t, toLog, "Should be true")
6081
}

provisional/strategy/optimized.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ func NewOptimizedImpl(impressionObserver ImpressionObserver, impressionCounter *
2828
}
2929

3030
func (s *OptimizedImpl) apply(impression *dtos.Impression, now int64) bool {
31+
if len(impression.Properties) != 0 {
32+
return true
33+
}
3134
impression.Pt, _ = s.impressionObserver.TestAndSet(impression.FeatureName, impression)
3235
if impression.Pt != 0 {
3336
s.impressionsCounter.Inc(impression.FeatureName, now, 1)

provisional/strategy/optimized_test.go

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/splitio/go-split-commons/v9/dtos"
88
"github.com/splitio/go-split-commons/v9/storage/inmemory"
9+
"github.com/stretchr/testify/assert"
910
)
1011

1112
func TestOptimizedMode(t *testing.T) {
@@ -24,32 +25,55 @@ func TestOptimizedMode(t *testing.T) {
2425
}
2526

2627
toLog, toListener := optimized.Apply([]dtos.Impression{imp})
28+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
29+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
2730

28-
if len(toLog) != 1 || len(toListener) != 1 {
29-
t.Error("Should have 1 to log")
31+
assert.Equal(t, 0, len(counter.impressionsCounts), "Should not have counts")
32+
33+
toLog, toListener = optimized.Apply([]dtos.Impression{imp})
34+
35+
assert.Equal(t, 0, len(toLog), "Should have 0 to log")
36+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
37+
38+
rawCounts := counter.PopAll()
39+
assert.Equal(t, 1, len(rawCounts), "Should have counts")
40+
for key, counts := range counter.PopAll() {
41+
assert.Equal(t, "feature-test", key.FeatureName, "Feature should be feature-test")
42+
assert.Equal(t, 1, counts, "It should be tracked only once")
3043
}
44+
}
3145

32-
if len(counter.impressionsCounts) != 0 {
33-
t.Error("Should not have counts")
46+
func TestOptimizedModeWithProperties(t *testing.T) {
47+
observer, _ := NewImpressionObserver(5000)
48+
counter := NewImpressionsCounter()
49+
runtimeTelemetry, _ := inmemory.NewTelemetryStorage()
50+
optimized := NewOptimizedImpl(observer, counter, runtimeTelemetry, true)
51+
imp := dtos.Impression{
52+
BucketingKey: "someBuck",
53+
ChangeNumber: 123,
54+
KeyName: "someKey",
55+
Label: "someLabel",
56+
Time: time.Now().UTC().UnixNano(),
57+
Treatment: "on",
58+
FeatureName: "feature-test",
59+
Properties: "{'hello':'world'}",
3460
}
3561

62+
toLog, toListener := optimized.Apply([]dtos.Impression{imp})
63+
64+
assert.Equal(t, 1, len(toLog), "Should have 1 to log")
65+
assert.Equal(t, 1, len(toListener), "Should have 1 to listener")
66+
3667
toLog, toListener = optimized.Apply([]dtos.Impression{imp})
3768

38-
if len(toLog) != 0 || len(toListener) != 1 {
39-
t.Error("Should not have to log")
40-
}
69+
assert.Equal(t, 1, len(toLog), "toLog should be 1")
70+
assert.Equal(t, 1, len(toListener), "toListener should be 1")
4171

4272
rawCounts := counter.PopAll()
43-
if len(rawCounts) != 1 {
44-
t.Error("Should have counts")
45-
}
73+
assert.Equal(t, 0, len(rawCounts), "Should doesn't have counts")
4674
for key, counts := range counter.PopAll() {
47-
if key.FeatureName != "feature-test" {
48-
t.Error("Feature should be feature-test")
49-
}
50-
if counts != 1 {
51-
t.Error("It should be tracked only once")
52-
}
75+
assert.Equal(t, "feature-test", key.FeatureName, "Feature should be feature-test")
76+
assert.Equal(t, 1, counts, "It should be tracked empty")
5377
}
5478
}
5579

@@ -70,13 +94,8 @@ func TestApplySingleOptimized(t *testing.T) {
7094

7195
toLog := optimized.ApplySingle(&imp)
7296

73-
if !toLog {
74-
t.Error("Should be true")
75-
}
76-
97+
assert.True(t, toLog, "Should be true")
7798
toLog = optimized.ApplySingle(&imp)
7899

79-
if toLog {
80-
t.Error("Should be false")
81-
}
100+
assert.False(t, toLog, "Should be false")
82101
}

service/api/http_recorders_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ func TestImpressionRecord(t *testing.T) {
3434
}
3535
}
3636

37+
func TestImpressionRecordWithProperties(t *testing.T) {
38+
impressionTXT := `{"k":"some_key","t":"off","m":1234567890,"c":55555555,"r":"some label","b":"some_bucket_key","properties":"value"}`
39+
impressionRecord := &dtos.ImpressionDTO{
40+
KeyName: "some_key",
41+
Treatment: "off",
42+
Time: 1234567890,
43+
ChangeNumber: 55555555,
44+
Label: "some label",
45+
BucketingKey: "some_bucket_key",
46+
Properties: "value"}
47+
48+
marshalImpression, err := json.Marshal(impressionRecord)
49+
if err != nil {
50+
t.Error(err)
51+
}
52+
53+
if string(marshalImpression) != impressionTXT {
54+
t.Error("Error marshaling impression")
55+
}
56+
}
57+
3758
func TestImpressionRecordBulk(t *testing.T) {
3859
impressionTXT := `{"f":"some_feature","i":[{"k":"some_key","t":"off","m":1234567890,"c":55555555,"r":"some label","b":"some_bucket_key"}]}`
3960
impressionRecords := &dtos.ImpressionsDTO{

synchronizer/worker/impression/single.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func (i *RecorderSingle) SynchronizeImpressions(bulkSize int64) error {
7474
Label: impression.Label,
7575
BucketingKey: impression.BucketingKey,
7676
Pt: impression.Pt,
77+
Properties: impression.Properties,
7778
}
7879
v, ok := impressionsToPost[impression.FeatureName]
7980
if ok {

0 commit comments

Comments
 (0)