Skip to content

Commit 26a03c9

Browse files
authored
Merge pull request #62 from splitio/FME-12564
[FME-12564] Added impression properties in link package
2 parents 4594895 + 9ad70ba commit 26a03c9

File tree

17 files changed

+436
-167
lines changed

17 files changed

+436
-167
lines changed

infra/sidecar.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ----- Builder image
2-
ARG GOLANG_VERSION=1.23.6
2+
ARG GOLANG_VERSION=1.24.0
33
FROM golang:${GOLANG_VERSION}-bookworm AS builder
44

55
ARG FIPS_MODE

splitd.yaml.tpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ sdk:
88
apikey: <server-side-apitoken>
99
labelsEnabled: true
1010
streamingEnabled: true
11+
fallbackTreatment:
12+
global_fallback_treatment:
13+
treatment: other
14+
by_flag_fallback_treatment: {}
1115
urls:
1216
auth: https://auth.split.io
1317
sdk: https://sdk.split.io/api

splitio/commitsha.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package splitio
22

3-
const CommitSHA = "a651b23"
3+
const CommitSHA = "085f07b"

splitio/conf/splitcli.go

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ type CliArgs struct {
2525
WriteTimeoutMS int
2626

2727
// command
28-
Method string
29-
Key string
30-
BucketingKey string
31-
Feature string
32-
Features []string
33-
TrafficType string
34-
EventType string
35-
EventVal *float64
36-
Attributes map[string]interface{}
28+
Method string
29+
Key string
30+
BucketingKey string
31+
Feature string
32+
Features []string
33+
TrafficType string
34+
EventType string
35+
EventVal *float64
36+
Attributes map[string]interface{}
37+
ImpressionProperties map[string]interface{}
3738
}
3839

3940
func (a *CliArgs) LinkOpts() (*link.ConsumerOptions, error) {
@@ -85,6 +86,7 @@ func ParseCliArgs() (*CliArgs, error) {
8586
et := cliFlags.String("event-type", "", "event type")
8687
ev := cliFlags.String("value", "", "event associated value")
8788
at := cliFlags.String("attributes", "", "json representation of attributes")
89+
pr := cliFlags.String("impression-properties", "null", "json representation of")
8890
err := cliFlags.Parse(os.Args[1:])
8991
if err != nil {
9092
return nil, fmt.Errorf("error parsing arguments: %w", err)
@@ -107,22 +109,31 @@ func ParseCliArgs() (*CliArgs, error) {
107109
return nil, fmt.Errorf("error parsing attributes: %w", err)
108110
}
109111

112+
if *pr == "" {
113+
*pr = "null"
114+
}
115+
impressionPorperties := make(map[string]interface{})
116+
if err = json.Unmarshal([]byte(*pr), &impressionPorperties); err != nil {
117+
return nil, fmt.Errorf("error parsing impression properties: %w", err)
118+
}
119+
110120
return &CliArgs{
111-
ID: *id,
112-
Serialization: *s,
113-
Protocol: *p,
114-
LogLevel: *ll,
115-
ConnType: *ct,
116-
ConnAddr: *ca,
117-
BufSize: *bs,
118-
Method: *m,
119-
Key: *k,
120-
BucketingKey: *bk,
121-
Feature: *f,
122-
Features: strings.Split(*fs, ","),
123-
TrafficType: *tt,
124-
EventType: *et,
125-
EventVal: eventVal,
126-
Attributes: attrs,
121+
ID: *id,
122+
Serialization: *s,
123+
Protocol: *p,
124+
LogLevel: *ll,
125+
ConnType: *ct,
126+
ConnAddr: *ca,
127+
BufSize: *bs,
128+
Method: *m,
129+
Key: *k,
130+
BucketingKey: *bk,
131+
Feature: *f,
132+
Features: strings.Split(*fs, ","),
133+
TrafficType: *tt,
134+
EventType: *et,
135+
EventVal: eventVal,
136+
Attributes: attrs,
137+
ImpressionProperties: impressionPorperties,
127138
}, nil
128139
}

splitio/conf/splitcli_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func TestCliConfig(t *testing.T) {
2525
"-event-type=someEventType",
2626
"-value=0.123",
2727
`-attributes={"some": "attribute"}`,
28+
`-impression-properties={"userId": "123", "age": 30, "premium": true, "balance": 99.5}`,
2829
}
2930

3031
parsed, err := ParseCliArgs()
@@ -42,6 +43,7 @@ func TestCliConfig(t *testing.T) {
4243
assert.Equal(t, "someEventType", parsed.EventType)
4344
assert.Equal(t, lang.Ref(float64(0.123)), parsed.EventVal)
4445
assert.Equal(t, map[string]interface{}{"some": "attribute"}, parsed.Attributes)
46+
assert.Equal(t, map[string]interface{}{"userId": "123", "age": float64(30), "premium": true, "balance": 99.5}, parsed.ImpressionProperties)
4547

4648
// test bad buffer size
4749
os.Args = []string{os.Args[0], "-buffer-size=sarasa"}

splitio/conf/splitd.go

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/splitio/go-split-commons/v9/dtos"
1112
"github.com/splitio/go-toolkit/v5/logging"
1213
"github.com/splitio/splitd/splitio/common/lang"
1314
"github.com/splitio/splitd/splitio/link"
@@ -122,21 +123,23 @@ func (l *Link) ToListenerOpts() (*link.ListenerOptions, error) {
122123
}
123124

124125
type SDK struct {
125-
Apikey string `yaml:"apikey"`
126-
LabelsEnabled *bool `yaml:"labelsEnabled"`
127-
StreamingEnabled *bool `yaml:"streamingEnabled"`
128-
URLs URLs `yaml:"urls"`
129-
FeatureFlags FeatureFlags `yaml:"featureFlags"`
130-
Impressions Impressions `yaml:"impressions"`
131-
Events Events `yaml:"events"`
132-
FlagSetsFilter []string `yaml:"flagSetsFilter"`
126+
Apikey string `yaml:"apikey"`
127+
LabelsEnabled *bool `yaml:"labelsEnabled"`
128+
StreamingEnabled *bool `yaml:"streamingEnabled"`
129+
FallbackTreatment fallbackTreatmentInput `yaml:"fallbackTreatment"`
130+
URLs URLs `yaml:"urls"`
131+
FeatureFlags FeatureFlags `yaml:"featureFlags"`
132+
Impressions Impressions `yaml:"impressions"`
133+
Events Events `yaml:"events"`
134+
FlagSetsFilter []string `yaml:"flagSetsFilter"`
133135
}
134136

135137
func (s *SDK) PopulateWithDefaults() {
136138
cfg := sdkConf.DefaultConfig()
137139
s.Apikey = apikeyPlaceHolder
138140
s.LabelsEnabled = lang.Ref(cfg.LabelsEnabled)
139141
s.StreamingEnabled = lang.Ref(cfg.StreamingEnabled)
142+
s.FallbackTreatment = fallbackTreatmentFromConfig(cfg.FallbackTreatment)
140143
s.URLs.PopulateWithDefaults()
141144
s.FeatureFlags.PopulateWithDefaults()
142145
s.Impressions.PopulateWithDefaults()
@@ -216,6 +219,11 @@ func (s *SDK) ToSDKConf() *sdkConf.Config {
216219
if len(s.FlagSetsFilter) > 0 {
217220
cfg.FlagSetsFilter = s.FlagSetsFilter
218221
}
222+
if parsed, err := (&s.FallbackTreatment).toConfig(); err != nil {
223+
log.Printf("[splitd] fallbackTreatment: %v", err)
224+
} else if parsed != nil {
225+
cfg.FallbackTreatment = *parsed
226+
}
219227
return cfg
220228
}
221229

@@ -310,6 +318,113 @@ func (p *Profiling) PopulateWithDefaults() {
310318
p.Port = 8888
311319
}
312320

321+
// fallbackTreatmentFromConfig maps the SDK default config's FallbackTreatment into our input type.
322+
func fallbackTreatmentFromConfig(c dtos.FallbackTreatmentConfig) fallbackTreatmentInput {
323+
parsed := new(dtos.FallbackTreatmentConfig)
324+
*parsed = c
325+
return fallbackTreatmentInput{parsed: parsed}
326+
}
327+
328+
type fallbackTreatmentEntry struct {
329+
Treatment *string `json:"treatment" yaml:"treatment"`
330+
Config *string `json:"config,omitempty" yaml:"config,omitempty"`
331+
}
332+
333+
type fallbackTreatmentInput struct {
334+
parsed *dtos.FallbackTreatmentConfig
335+
raw string
336+
}
337+
338+
func (f *fallbackTreatmentInput) UnmarshalYAML(value *yaml.Node) error {
339+
if value == nil {
340+
return nil
341+
}
342+
switch value.Kind {
343+
case yaml.ScalarNode:
344+
var s string
345+
if err := value.Decode(&s); err != nil {
346+
return err
347+
}
348+
f.parsed = nil
349+
f.raw = strings.TrimSpace(s)
350+
return nil
351+
case yaml.MappingNode:
352+
var m struct {
353+
Global *fallbackTreatmentEntry `yaml:"global_fallback_treatment"`
354+
ByFlag map[string]fallbackTreatmentEntry `yaml:"by_flag_fallback_treatment"`
355+
}
356+
if err := value.Decode(&m); err != nil {
357+
return err
358+
}
359+
out := dtos.FallbackTreatmentConfig{}
360+
if m.Global != nil && m.Global.Treatment != nil {
361+
out.GlobalFallbackTreatment = &dtos.FallbackTreatment{
362+
Treatment: m.Global.Treatment,
363+
Config: m.Global.Config,
364+
}
365+
}
366+
if len(m.ByFlag) > 0 {
367+
out.ByFlagFallbackTreatment = make(map[string]dtos.FallbackTreatment)
368+
for name, v := range m.ByFlag {
369+
if v.Treatment != nil {
370+
out.ByFlagFallbackTreatment[name] = dtos.FallbackTreatment{
371+
Treatment: v.Treatment,
372+
Config: v.Config,
373+
}
374+
}
375+
}
376+
}
377+
f.parsed = &out
378+
return nil
379+
}
380+
return nil
381+
}
382+
383+
func (f *fallbackTreatmentInput) toConfig() (*dtos.FallbackTreatmentConfig, error) {
384+
if f == nil {
385+
return nil, nil
386+
}
387+
if f.raw != "" {
388+
return parseFallbackTreatmentJSON(f.raw)
389+
}
390+
if f.parsed != nil {
391+
return f.parsed, nil
392+
}
393+
return nil, nil
394+
}
395+
396+
func parseFallbackTreatmentJSON(raw string) (*dtos.FallbackTreatmentConfig, error) {
397+
var wrapper struct {
398+
FallbackTreatment struct {
399+
GlobalFallbackTreatment *fallbackTreatmentEntry `json:"global_fallback_treatment"`
400+
ByFlagFallbackTreatment map[string]fallbackTreatmentEntry `json:"by_flag_fallback_treatment"`
401+
} `json:"fallback_treatment"`
402+
}
403+
if err := json.Unmarshal([]byte(raw), &wrapper); err != nil {
404+
return nil, fmt.Errorf("invalid JSON: %w", err)
405+
}
406+
out := dtos.FallbackTreatmentConfig{}
407+
inner := &wrapper.FallbackTreatment
408+
if inner.GlobalFallbackTreatment != nil && inner.GlobalFallbackTreatment.Treatment != nil {
409+
out.GlobalFallbackTreatment = &dtos.FallbackTreatment{
410+
Treatment: inner.GlobalFallbackTreatment.Treatment,
411+
Config: inner.GlobalFallbackTreatment.Config,
412+
}
413+
}
414+
if len(inner.ByFlagFallbackTreatment) > 0 {
415+
out.ByFlagFallbackTreatment = make(map[string]dtos.FallbackTreatment)
416+
for name, v := range inner.ByFlagFallbackTreatment {
417+
if v.Treatment != nil {
418+
out.ByFlagFallbackTreatment[name] = dtos.FallbackTreatment{
419+
Treatment: v.Treatment,
420+
Config: v.Config,
421+
}
422+
}
423+
}
424+
}
425+
return &out, nil
426+
}
427+
313428
func ReadConfig() (*Config, error) {
314429
cfgFN := defaultConfigFN
315430
if fromEnv := os.Getenv("SPLITD_CONF_FILE"); fromEnv != "" {

splitio/conf/splitd_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
"github.com/splitio/splitd/splitio/link/transfer"
1616
"github.com/splitio/splitd/splitio/sdk/conf"
1717
"github.com/stretchr/testify/assert"
18+
"github.com/stretchr/testify/require"
19+
"gopkg.in/yaml.v3"
1820
)
1921

2022
func TestConfig(t *testing.T) {
@@ -30,6 +32,7 @@ func TestConfig(t *testing.T) {
3032
cfg = Config{}
3133

3234
assert.Nil(t, cfg.parse(dir+string(filepath.Separator)+"splitd.yaml.tpl"))
35+
expected.SDK.FallbackTreatment = cfg.SDK.FallbackTreatment
3336
assert.Equal(t, expected, cfg)
3437

3538
assert.Error(t, cfg.parse("someNonexistantFile"))
@@ -185,3 +188,44 @@ func TestDefaultConf(t *testing.T) {
185188
assert.Equal(t, defaultLogLevel, *c.Logger.Level)
186189
assert.Equal(t, defaultLogOutput, *c.Logger.Output)
187190
}
191+
192+
func TestFallbackTreatmentToSDKConf(t *testing.T) {
193+
// JSON string form
194+
var cfg Config
195+
cfg.PopulateWithDefaults()
196+
err := yaml.Unmarshal([]byte(`
197+
sdk:
198+
apikey: test
199+
fallbackTreatment: '{"fallback_treatment":{"global_fallback_treatment":{"treatment":"control"},"by_flag_fallback_treatment":{"my_flag":{"treatment":"off"}}}}'
200+
`), &cfg)
201+
assert.Nil(t, err)
202+
sdkConf := cfg.SDK.ToSDKConf()
203+
require.NotNil(t, sdkConf)
204+
require.NotNil(t, sdkConf.FallbackTreatment.GlobalFallbackTreatment)
205+
require.NotEmpty(t, sdkConf.FallbackTreatment.ByFlagFallbackTreatment)
206+
assert.Equal(t, "control", *sdkConf.FallbackTreatment.GlobalFallbackTreatment.Treatment)
207+
assert.Equal(t, "off", *sdkConf.FallbackTreatment.ByFlagFallbackTreatment["my_flag"].Treatment)
208+
209+
// Native YAML object form
210+
var cfg2 Config
211+
cfg2.PopulateWithDefaults()
212+
err = yaml.Unmarshal([]byte(`
213+
sdk:
214+
apikey: test
215+
fallbackTreatment:
216+
global_fallback_treatment:
217+
treatment: global_val
218+
by_flag_fallback_treatment:
219+
some_flag:
220+
treatment: on
221+
config: "{}"
222+
`), &cfg2)
223+
assert.Nil(t, err)
224+
sdkConf2 := cfg2.SDK.ToSDKConf()
225+
require.NotNil(t, sdkConf2)
226+
require.NotNil(t, sdkConf2.FallbackTreatment.GlobalFallbackTreatment)
227+
require.Contains(t, sdkConf2.FallbackTreatment.ByFlagFallbackTreatment, "some_flag")
228+
assert.Equal(t, "global_val", *sdkConf2.FallbackTreatment.GlobalFallbackTreatment.Treatment)
229+
assert.Equal(t, "on", *sdkConf2.FallbackTreatment.ByFlagFallbackTreatment["some_flag"].Treatment)
230+
assert.Equal(t, "{}", *sdkConf2.FallbackTreatment.ByFlagFallbackTreatment["some_flag"].Config)
231+
}

splitio/link/client/types/interfaces.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
)
77

88
type ClientInterface interface {
9-
Treatment(key string, bucketingKey string, feature string, attrs map[string]interface{}) (*Result, error)
10-
Treatments(key string, bucketingKey string, features []string, attrs map[string]interface{}) (Results, error)
11-
TreatmentWithConfig(key string, bucketingKey string, feature string, attrs map[string]interface{}) (*Result, error)
12-
TreatmentsWithConfig(key string, bucketingKey string, features []string, attrs map[string]interface{}) (Results, error)
9+
Treatment(key string, bucketingKey string, feature string, attrs map[string]interface{}, optFns ...OptFn) (*Result, error)
10+
Treatments(key string, bucketingKey string, features []string, attrs map[string]interface{}, optFns ...OptFn) (Results, error)
11+
TreatmentWithConfig(key string, bucketingKey string, feature string, attrs map[string]interface{}, optFns ...OptFn) (*Result, error)
12+
TreatmentsWithConfig(key string, bucketingKey string, features []string, attrs map[string]interface{}, optFns ...OptFn) (Results, error)
1313
Track(key string, trafficType string, eventType string, value *float64, properties map[string]interface{}) error
1414
SplitNames() ([]string, error)
1515
Split(name string) (*sdk.SplitView, error)
@@ -24,3 +24,9 @@ type Result struct {
2424
}
2525

2626
type Results = map[string]Result
27+
28+
type Options struct {
29+
EvaluationOptions *dtos.EvaluationOptions
30+
}
31+
32+
type OptFn = func(o *Options)

0 commit comments

Comments
 (0)