From dfea32366199eb3196e6d15ccd8a2f0d9792628d Mon Sep 17 00:00:00 2001 From: Nadia Mayor Date: Tue, 4 Nov 2025 18:08:29 -0300 Subject: [PATCH 1/3] Added tests cases --- splitio/commitversion.go | 2 +- .../proxy/storage/rulebasedsegments_test.go | 103 +++++++++++++++ splitio/proxy/storage/splits_test.go | 124 ++++++++++++++++++ 3 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 splitio/proxy/storage/rulebasedsegments_test.go diff --git a/splitio/commitversion.go b/splitio/commitversion.go index e0846185..2b9ff1ac 100644 --- a/splitio/commitversion.go +++ b/splitio/commitversion.go @@ -5,4 +5,4 @@ This file is created automatically, please do not edit */ // CommitVersion is the version of the last commit previous to release -const CommitVersion = "1fbc498" +const CommitVersion = "514a95c" diff --git a/splitio/proxy/storage/rulebasedsegments_test.go b/splitio/proxy/storage/rulebasedsegments_test.go new file mode 100644 index 00000000..9ce4e95d --- /dev/null +++ b/splitio/proxy/storage/rulebasedsegments_test.go @@ -0,0 +1,103 @@ +package storage + +import ( + "testing" + + "github.com/splitio/go-split-commons/v8/dtos" + "github.com/splitio/go-toolkit/v5/logging" + "github.com/stretchr/testify/assert" +) + +func TestRBSChangesSince(t *testing.T) { + logger := logging.NewLogger(nil) + + // Initialize storage with some test data + pss := NewProxyRuleBasedSegmentsStorage(logger) + + // Test case 1: since == -1 + { + initialRuleBaseds := []dtos.RuleBasedSegmentDTO{ + {Name: "rbs1", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"}, + {Name: "rbs2", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"}, + } + pss.Update(initialRuleBaseds, nil, 10) + + changes, err := pss.ChangesSince(-1) + assert.Nil(t, err) + assert.Equal(t, int64(-1), changes.Since) + assert.Equal(t, int64(10), changes.Till) + assert.ElementsMatch(t, initialRuleBaseds, changes.RuleBasedSegments) + } + + // Test case 2: Error when since is too old + { + // The storage was initialized with CN 10, so requesting CN 5 should fail + changes, err := pss.ChangesSince(5) + assert.Equal(t, ErrSinceParamTooOld, err) + assert.Nil(t, changes) + } + + // Test case 3: Active and archived rule-based segment + { + // Add a new rule-based segment and archive an existing one + toAdd := []dtos.RuleBasedSegmentDTO{{Name: "rbs3", ChangeNumber: 15, Status: "ACTIVE", TrafficTypeName: "user"}} + toRemove := []dtos.RuleBasedSegmentDTO{ + { + Name: "rbs2", + ChangeNumber: 15, + Status: "ARCHIVED", + TrafficTypeName: "user", + Conditions: []dtos.RuleBasedConditionDTO{}, + }, + } + + pss.Update(toAdd, toRemove, 15) + + changes, err := pss.ChangesSince(10) + assert.Nil(t, err) + assert.Equal(t, int64(10), changes.Since) + assert.Equal(t, int64(15), changes.Till) + + // Should include both the new active rule-based segment and the archived one + expectedRBSs := []dtos.RuleBasedSegmentDTO{ + { + Name: "rbs2", + ChangeNumber: 15, + Status: "ARCHIVED", + // Note: Archived segments have minimal fields set by archivedRBDTOForView + }, + { + Name: "rbs3", + ChangeNumber: 15, + Status: "ACTIVE", + TrafficTypeName: "user", + Excluded: dtos.ExcludedDTO{ + Keys: nil, + Segments: nil, + }, + Conditions: nil, + }, + } + assert.ElementsMatch(t, expectedRBSs, changes.RuleBasedSegments) + } + + // Test case 4: Proper till calculation with multiple changes + { + // Add changes with different change numbers + changes1 := []dtos.RuleBasedSegmentDTO{{Name: "rbs6", ChangeNumber: 25, Status: "ACTIVE", TrafficTypeName: "user"}} + changes2 := []dtos.RuleBasedSegmentDTO{{Name: "rbs7", ChangeNumber: 30, Status: "ACTIVE", TrafficTypeName: "user"}} + + pss.Update(changes1, nil, 25) + pss.Update(changes2, nil, 30) + + changes, err := pss.ChangesSince(20) + assert.Nil(t, err) + assert.Equal(t, int64(20), changes.Since) + assert.Equal(t, int64(30), changes.Till) + expectedChanges := []dtos.RuleBasedSegmentDTO{ + {Name: "rbs6", ChangeNumber: 25, Status: "ACTIVE", TrafficTypeName: "user"}, + {Name: "rbs7", ChangeNumber: 30, Status: "ACTIVE", TrafficTypeName: "user"}, + } + assert.ElementsMatch(t, expectedChanges, changes.RuleBasedSegments) + } +} diff --git a/splitio/proxy/storage/splits_test.go b/splitio/proxy/storage/splits_test.go index e45c3619..59356ab4 100644 --- a/splitio/proxy/storage/splits_test.go +++ b/splitio/proxy/storage/splits_test.go @@ -220,6 +220,130 @@ func TestGetNamesByFlagSets(t *testing.T) { } } +func TestChangesSince(t *testing.T) { + dbw, err := persistent.NewBoltWrapper(persistent.BoltInMemoryMode, nil) + assert.Nil(t, err) + logger := logging.NewLogger(nil) + + // Initialize storage with some test data + pss := NewProxySplitStorage(dbw, logger, flagsets.NewFlagSetFilter(nil), true) + + // Test case 1: since == -1 and no flagSets + { + initialSplits := []dtos.SplitDTO{ + {Name: "split1", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"}, + {Name: "split2", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"}, + } + pss.Update(initialSplits, nil, 10) + + changes, err := pss.ChangesSince(-1, nil) + assert.Nil(t, err) + assert.Equal(t, int64(-1), changes.Since) + assert.Equal(t, int64(10), changes.Till) + assert.ElementsMatch(t, initialSplits, changes.Splits) + } + + // Test case 2: Error when since is too old + { + // The storage was initialized with CN 10, so requesting CN 5 should fail + changes, err := pss.ChangesSince(5, nil) + assert.Equal(t, ErrSinceParamTooOld, err) + assert.Nil(t, changes) + } + + // Test case 3: Active and archived splits + { + // Add a new split and archive an existing one + toAdd := []dtos.SplitDTO{{Name: "split3", ChangeNumber: 15, Status: "ACTIVE", TrafficTypeName: "user"}} + toRemove := []dtos.SplitDTO{ + { + Name: "split2", + ChangeNumber: 15, + Status: "ARCHIVED", + TrafficTypeName: "user", + TrafficAllocation: 100, + Algo: 1, + DefaultTreatment: "off", + Conditions: []dtos.ConditionDTO{}, + Sets: []string{}, + }, + } + + pss.Update(toAdd, toRemove, 15) + + changes, err := pss.ChangesSince(10, nil) + assert.Nil(t, err) + assert.Equal(t, int64(10), changes.Since) + assert.Equal(t, int64(15), changes.Till) + + // Should include both the new active split and the archived one + expectedSplits := []dtos.SplitDTO{ + { + Name: "split3", + ChangeNumber: 15, + Status: "ACTIVE", + TrafficTypeName: "user", + TrafficAllocation: 0, + Algo: 0, + Conditions: nil, + Sets: nil, + }, + { + Name: "split2", + ChangeNumber: 15, + Status: "ARCHIVED", + TrafficTypeName: "user", + TrafficAllocation: 100, + Algo: 1, + DefaultTreatment: "off", + Conditions: []dtos.ConditionDTO{}, + Sets: []string{}, + }, + } + assert.ElementsMatch(t, expectedSplits, changes.Splits) + } + + // Test case 4: FlagSets filtering + { + // Add splits with flag sets + flagSetSplits := []dtos.SplitDTO{ + {Name: "split4", ChangeNumber: 20, Status: "ACTIVE", Sets: []string{"set1"}, TrafficTypeName: "user"}, + {Name: "split5", ChangeNumber: 20, Status: "ACTIVE", Sets: []string{"set2"}, TrafficTypeName: "user"}, + } + pss.Update(flagSetSplits, nil, 20) + + // Test filtering by set1 + changes, err := pss.ChangesSince(15, []string{"set1"}) + assert.Nil(t, err) + assert.Equal(t, int64(15), changes.Since) + assert.Equal(t, int64(20), changes.Till) + expectedSet1 := []dtos.SplitDTO{ + {Name: "split4", ChangeNumber: 20, Status: "ACTIVE", Sets: []string{"set1"}, TrafficTypeName: "user"}, + } + assert.ElementsMatch(t, expectedSet1, changes.Splits) + } + + // Test case 5: Proper till calculation with multiple changes + { + // Add changes with different change numbers + changes1 := []dtos.SplitDTO{{Name: "split6", ChangeNumber: 25, Status: "ACTIVE", TrafficTypeName: "user"}} + changes2 := []dtos.SplitDTO{{Name: "split7", ChangeNumber: 30, Status: "ACTIVE", TrafficTypeName: "user"}} + + pss.Update(changes1, nil, 25) + pss.Update(changes2, nil, 30) + + changes, err := pss.ChangesSince(20, nil) + assert.Nil(t, err) + assert.Equal(t, int64(20), changes.Since) + assert.Equal(t, int64(30), changes.Till) + expectedChanges := []dtos.SplitDTO{ + {Name: "split6", ChangeNumber: 25, Status: "ACTIVE", TrafficTypeName: "user"}, + {Name: "split7", ChangeNumber: 30, Status: "ACTIVE", TrafficTypeName: "user"}, + } + assert.ElementsMatch(t, expectedChanges, changes.Splits) + } +} + func TestGetAllFlagSetNames(t *testing.T) { dbw, err := persistent.NewBoltWrapper(persistent.BoltInMemoryMode, nil) if err != nil { From 8fd26d7495470da60d73f35503540af51f9e4118 Mon Sep 17 00:00:00 2001 From: Nadia Mayor Date: Wed, 5 Nov 2025 16:22:44 -0300 Subject: [PATCH 2/3] Remove comment lines --- splitio/proxy/storage/rulebasedsegments.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/splitio/proxy/storage/rulebasedsegments.go b/splitio/proxy/storage/rulebasedsegments.go index e28d3333..cc8ef03f 100644 --- a/splitio/proxy/storage/rulebasedsegments.go +++ b/splitio/proxy/storage/rulebasedsegments.go @@ -103,12 +103,6 @@ func (p *ProxyRuleBasedSegmentsStorageImpl) ChangesSince(since int64) (*dtos.Rul all = append(all, *rbSegments) } return &dtos.RuleBasedSegmentsDTO{Since: since, Till: till, RuleBasedSegments: all}, nil - - // if since > -1 { - // return &dtos.RuleBasedSegmentsDTO{Since: since, Till: since, RuleBasedSegments: []dtos.RuleBasedSegmentDTO{}}, nil - // } - // cn, _ := p.snapshot.ChangeNumber() - // return &dtos.RuleBasedSegmentsDTO{Since: since, Till: cn, RuleBasedSegments: p.snapshot.All()}, nil } // All call is forwarded to the snapshot From 88290d5bf4302bcc33d0d2b990e390fc7d3e7c22 Mon Sep 17 00:00:00 2001 From: Nadia Mayor Date: Thu, 6 Nov 2025 13:02:15 -0300 Subject: [PATCH 3/3] Added rule-based segments in the dashboard --- splitio/admin/controllers/dashboard.go | 1 + splitio/admin/controllers/helpers.go | 39 +++++++++++++ .../admin/views/dashboard/datainspector.go | 57 +++++++++++++++++++ splitio/admin/views/dashboard/js.go | 54 ++++++++++++++++++ splitio/admin/views/dashboard/main.go | 53 ++++++++++------- splitio/commitversion.go | 2 +- splitio/proxy/initialization.go | 9 +-- 7 files changed, 191 insertions(+), 24 deletions(-) diff --git a/splitio/admin/controllers/dashboard.go b/splitio/admin/controllers/dashboard.go index 49a40bbe..a51ee444 100644 --- a/splitio/admin/controllers/dashboard.go +++ b/splitio/admin/controllers/dashboard.go @@ -150,6 +150,7 @@ func (c *DashboardController) gatherStats() *dashboard.GlobalStats { FeatureFlags: bundleSplitInfo(c.storages.SplitStorage), Segments: bundleSegmentInfo(c.storages.SplitStorage, c.storages.SegmentStorage), LargeSegments: bundleLargeSegmentInfo(c.storages.SplitStorage, c.storages.LargeSegmentStorage), + RuleBasedSegments: bundleRuleBasedInfo(c.storages.SplitStorage, c.storages.RuleBasedSegmentsStorage), Latencies: bundleProxyLatencies(c.storages.LocalTelemetryStorage), BackendLatencies: bundleLocalSyncLatencies(c.storages.LocalTelemetryStorage), ImpressionsQueueSize: getImpressionSize(c.storages.ImpressionStorage), diff --git a/splitio/admin/controllers/helpers.go b/splitio/admin/controllers/helpers.go index 4aa784f3..b2288b28 100644 --- a/splitio/admin/controllers/helpers.go +++ b/splitio/admin/controllers/helpers.go @@ -108,6 +108,45 @@ func bundleSegmentInfo(splitStorage storage.SplitStorage, segmentStorage storage return summaries } +func bundleRuleBasedInfo(splitStorage storage.SplitStorage, ruleBasedSegmentStorage storage.RuleBasedSegmentStorageConsumer) []dashboard.RuleBasedSegmentSummary { + + names := splitStorage.RuleBasedSegmentNames() + summaries := make([]dashboard.RuleBasedSegmentSummary, 0, names.Size()) + + for _, name := range names.List() { + strName, ok := name.(string) + if !ok { + continue + } + ruleBased, err := ruleBasedSegmentStorage.GetRuleBasedSegmentByName(strName) + + if err != nil { + continue + } + excluededSegments := make([]dashboard.ExcluededSegments, 0, len(ruleBased.Excluded.Segments)) + for _, excludedSegment := range ruleBased.Excluded.Segments { + excluededSegments = append(excluededSegments, dashboard.ExcluededSegments{ + Name: excludedSegment.Name, + Type: excludedSegment.Type, + }) + } + + if ruleBased.Excluded.Keys == nil { + ruleBased.Excluded.Keys = make([]string, 0) + } + + summaries = append(summaries, dashboard.RuleBasedSegmentSummary{ + Name: ruleBased.Name, + Active: ruleBased.Status == "ACTIVE", + ExcludedKeys: ruleBased.Excluded.Keys, + ExcluededSegments: excluededSegments, + LastModified: time.Unix(0, ruleBased.ChangeNumber*int64(time.Millisecond)).UTC().Format(time.UnixDate), + ChangeNumber: ruleBased.ChangeNumber, + }) + } + return summaries +} + func bundleSegmentKeysInfo(name string, segmentStorage storage.SegmentStorageConsumer) []dashboard.SegmentKeySummary { keys := segmentStorage.Keys(name) diff --git a/splitio/admin/views/dashboard/datainspector.go b/splitio/admin/views/dashboard/datainspector.go index f4f63577..3af58c63 100644 --- a/splitio/admin/views/dashboard/datainspector.go +++ b/splitio/admin/views/dashboard/datainspector.go @@ -38,6 +38,19 @@ const dataInspector = ` {{end}} +
  • + + +  Rule-based Segments + +