Skip to content

Commit 7e284de

Browse files
authored
Merge pull request #258 from splitio/fallback-treatment
[FME-11063] Release Fallback treatment
2 parents 847b770 + b7582f0 commit 7e284de

File tree

210 files changed

+757
-581
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+757
-581
lines changed

.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/sonarcloud-github[email protected]
38+
uses: SonarSource/sonarqube-scan[email protected]
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/sonarcloud-github[email protected]
49+
uses: SonarSource/sonarqube-scan[email protected]
5050
env:
5151
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
5252
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
9.0.0 (Nov 21, 2025)
2+
- BREAKING CHANGE:
3+
- Changed new evaluator.
4+
- Added fallback treatment.
5+
16
8.0.0 (Nov 10, 2025)
27
- BREAKING CHANGE:
38
- Changed Rule Based Segment and Feature Flag storages.

conf/conf.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package conf
22

33
import (
44
"crypto/tls"
5+
6+
"github.com/splitio/go-split-commons/v9/dtos"
57
)
68

79
// RedisConfig struct is used to cofigure the redis parameters
@@ -89,6 +91,7 @@ type AdvancedConfig struct {
8991
FlagsSpecVersion string
9092
LargeSegment *LargeSegmentConfig
9193
RulesConfig *RulesConfig
94+
FallbackTreatment dtos.FallbackTreatmentConfig
9295
}
9396

9497
type LargeSegmentConfig struct {

dtos/fallbacktreatment.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package dtos
2+
3+
const (
4+
labelPrefix = "fallback - "
5+
)
6+
7+
type FallbackTreatment struct {
8+
Treatment *string
9+
Config *string
10+
label *string
11+
}
12+
13+
func (f *FallbackTreatment) Label() *string {
14+
return f.label
15+
}
16+
17+
type FallbackTreatmentConfig struct {
18+
GlobalFallbackTreatment *FallbackTreatment
19+
ByFlagFallbackTreatment map[string]FallbackTreatment
20+
}
21+
22+
type FallbackTreatmentCalculator interface {
23+
Resolve(flagName string, label *string) FallbackTreatment
24+
}
25+
26+
type FallbackTreatmentCalculatorImp struct {
27+
fallbackTreatmentConfig *FallbackTreatmentConfig
28+
}
29+
30+
func NewFallbackTreatmentCalculatorImp(fallbackTreatmentConfig *FallbackTreatmentConfig) FallbackTreatmentCalculator {
31+
return &FallbackTreatmentCalculatorImp{
32+
fallbackTreatmentConfig: fallbackTreatmentConfig,
33+
}
34+
}
35+
36+
func (f *FallbackTreatmentCalculatorImp) Resolve(flagName string, label *string) FallbackTreatment {
37+
if f.fallbackTreatmentConfig != nil {
38+
if byFlag := f.fallbackTreatmentConfig.ByFlagFallbackTreatment; byFlag != nil {
39+
if val, ok := byFlag[flagName]; ok {
40+
return FallbackTreatment{
41+
Treatment: val.Treatment,
42+
Config: val.Config,
43+
label: f.resolveLabel(label),
44+
}
45+
}
46+
}
47+
if f.fallbackTreatmentConfig.GlobalFallbackTreatment != nil {
48+
return FallbackTreatment{
49+
Treatment: f.fallbackTreatmentConfig.GlobalFallbackTreatment.Treatment,
50+
Config: f.fallbackTreatmentConfig.GlobalFallbackTreatment.Config,
51+
label: f.resolveLabel(label),
52+
}
53+
}
54+
}
55+
controlTreatment := "control"
56+
return FallbackTreatment{
57+
Treatment: &controlTreatment,
58+
label: label,
59+
}
60+
}
61+
62+
func (f *FallbackTreatmentCalculatorImp) resolveLabel(label *string) *string {
63+
if label == nil {
64+
return nil
65+
}
66+
result := labelPrefix + *label
67+
return &result
68+
}

dtos/fallbacktreatment_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package dtos
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestFallbackTreatmentCalculatorResolve(t *testing.T) {
10+
// Initial setup with both global and flag-specific treatments
11+
stringConfig := "flag1_config"
12+
globalTreatment := "global_treatment"
13+
flag1Treatment := "flag1_treatment"
14+
config := &FallbackTreatmentConfig{
15+
GlobalFallbackTreatment: &FallbackTreatment{
16+
Treatment: &globalTreatment,
17+
},
18+
ByFlagFallbackTreatment: map[string]FallbackTreatment{
19+
"flag1": {
20+
Treatment: &flag1Treatment,
21+
Config: &stringConfig,
22+
},
23+
},
24+
}
25+
calc := NewFallbackTreatmentCalculatorImp(config)
26+
27+
// Test flag-specific treatment with label
28+
label := "some_label"
29+
result := calc.Resolve("flag1", &label)
30+
assert.Equal(t, "flag1_treatment", *result.Treatment)
31+
assert.Equal(t, &stringConfig, result.Config)
32+
assert.Equal(t, "fallback - some_label", *result.label)
33+
34+
// Test fallback to global treatment when flag not found
35+
result = calc.Resolve("flag2", &label)
36+
assert.Equal(t, "global_treatment", *result.Treatment)
37+
assert.Nil(t, result.Config)
38+
assert.Equal(t, "fallback - some_label", *result.label)
39+
40+
// Test nil label handling
41+
result = calc.Resolve("flag1", nil)
42+
assert.Equal(t, "flag1_treatment", *result.Treatment)
43+
assert.Equal(t, &stringConfig, result.Config)
44+
assert.Nil(t, result.label)
45+
46+
// Test default control when no config
47+
calcNoConfig := NewFallbackTreatmentCalculatorImp(nil)
48+
result = calcNoConfig.Resolve("flag1", &label)
49+
assert.Equal(t, "control", *result.Treatment)
50+
assert.Nil(t, result.Config)
51+
assert.Equal(t, "some_label", *result.label)
52+
53+
// Test global treatment when no flag-specific treatments exist
54+
configGlobalOnly := &FallbackTreatmentConfig{
55+
GlobalFallbackTreatment: &FallbackTreatment{
56+
Treatment: &globalTreatment,
57+
Config: nil,
58+
},
59+
}
60+
calcGlobalOnly := NewFallbackTreatmentCalculatorImp(configGlobalOnly)
61+
result = calcGlobalOnly.Resolve("any_flag", &label)
62+
assert.Equal(t, "global_treatment", *result.Treatment)
63+
assert.Nil(t, result.Config)
64+
assert.Equal(t, "fallback - some_label", *result.label)
65+
}

engine/engine.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"fmt"
55
"math"
66

7-
"github.com/splitio/go-split-commons/v8/engine/evaluator/impressionlabels"
8-
"github.com/splitio/go-split-commons/v8/engine/grammar"
9-
"github.com/splitio/go-split-commons/v8/engine/grammar/constants"
10-
"github.com/splitio/go-split-commons/v8/engine/hash"
7+
"github.com/splitio/go-split-commons/v9/engine/evaluator/impressionlabels"
8+
"github.com/splitio/go-split-commons/v9/engine/grammar"
9+
"github.com/splitio/go-split-commons/v9/engine/grammar/constants"
10+
"github.com/splitio/go-split-commons/v9/engine/hash"
1111

1212
"github.com/splitio/go-toolkit/v5/hasher"
1313
"github.com/splitio/go-toolkit/v5/logging"

engine/engine_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
"os"
88
"testing"
99

10-
"github.com/splitio/go-split-commons/v8/dtos"
11-
"github.com/splitio/go-split-commons/v8/engine/grammar"
12-
"github.com/splitio/go-split-commons/v8/engine/grammar/constants"
13-
"github.com/splitio/go-split-commons/v8/engine/hash"
10+
"github.com/splitio/go-split-commons/v9/dtos"
11+
"github.com/splitio/go-split-commons/v9/engine/grammar"
12+
"github.com/splitio/go-split-commons/v9/engine/grammar/constants"
13+
"github.com/splitio/go-split-commons/v9/engine/hash"
1414

1515
"github.com/splitio/go-toolkit/v5/hasher"
1616
"github.com/splitio/go-toolkit/v5/logging"

engine/evaluator/evaluator.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"fmt"
55
"time"
66

7-
"github.com/splitio/go-split-commons/v8/dtos"
8-
"github.com/splitio/go-split-commons/v8/engine"
9-
"github.com/splitio/go-split-commons/v8/engine/evaluator/impressionlabels"
10-
"github.com/splitio/go-split-commons/v8/engine/grammar"
11-
"github.com/splitio/go-split-commons/v8/storage"
7+
"github.com/splitio/go-split-commons/v9/dtos"
8+
"github.com/splitio/go-split-commons/v9/engine"
9+
"github.com/splitio/go-split-commons/v9/engine/evaluator/impressionlabels"
10+
"github.com/splitio/go-split-commons/v9/engine/grammar"
11+
"github.com/splitio/go-split-commons/v9/storage"
1212

1313
"github.com/splitio/go-toolkit/v5/logging"
1414
)
@@ -37,10 +37,11 @@ type Results struct {
3737

3838
// Evaluator struct is the main evaluator
3939
type Evaluator struct {
40-
splitStorage storage.SplitStorageConsumer
41-
eng *engine.Engine
42-
logger logging.LoggerInterface
43-
ruleBuilder grammar.RuleBuilder
40+
splitStorage storage.SplitStorageConsumer
41+
eng *engine.Engine
42+
logger logging.LoggerInterface
43+
ruleBuilder grammar.RuleBuilder
44+
fallbackTratmentCalculator dtos.FallbackTreatmentCalculator
4445
}
4546

4647
// NewEvaluator instantiates an Evaluator struct and returns a reference to it
@@ -53,21 +54,25 @@ func NewEvaluator(
5354
logger logging.LoggerInterface,
5455
featureFlagRules []string,
5556
ruleBasedSegmentRules []string,
57+
fallbackTreatmentCalculator dtos.FallbackTreatmentCalculator,
5658
) *Evaluator {
5759
e := &Evaluator{
58-
splitStorage: splitStorage,
59-
eng: eng,
60-
logger: logger,
60+
splitStorage: splitStorage,
61+
eng: eng,
62+
logger: logger,
63+
fallbackTratmentCalculator: fallbackTreatmentCalculator,
6164
}
6265
e.ruleBuilder = grammar.NewRuleBuilder(segmentStorage, ruleBasedSegmentStorage, largeSegmentStorage, featureFlagRules, ruleBasedSegmentRules, logger, e)
6366
return e
6467
}
6568

6669
func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFlag string, splitDto *dtos.SplitDTO, attributes map[string]interface{}) *Result {
6770
var config *string
71+
label := impressionlabels.SplitNotFound
6872
if splitDto == nil {
69-
e.logger.Warning(fmt.Sprintf("Feature flag %s not found, returning control.", featureFlag))
70-
return &Result{Treatment: Control, Label: impressionlabels.SplitNotFound, Config: config}
73+
fallbackTratment := e.fallbackTratmentCalculator.Resolve(featureFlag, &label)
74+
e.logger.Warning(fmt.Sprintf("Feature flag %s not found, returning fallback treatment.", featureFlag))
75+
return &Result{Treatment: *fallbackTratment.Treatment, Label: *fallbackTratment.Label(), Config: fallbackTratment.Config}
7176
}
7277

7378
split := grammar.NewSplit(splitDto, e.logger, e.ruleBuilder)
@@ -113,6 +118,15 @@ func (e *Evaluator) evaluateTreatment(key string, bucketingKey string, featureFl
113118
label = impressionlabels.NoConditionMatched
114119
}
115120

121+
if *treatment == Control {
122+
fallbackTreatment := e.fallbackTratmentCalculator.Resolve(featureFlag, &label)
123+
return &Result{
124+
Treatment: *fallbackTreatment.Treatment,
125+
Label: *fallbackTreatment.Label(),
126+
Config: fallbackTreatment.Config,
127+
}
128+
}
129+
116130
if _, ok := split.Configurations()[*treatment]; ok {
117131
treatmentConfig := split.Configurations()[*treatment]
118132
config = &treatmentConfig

0 commit comments

Comments
 (0)