diff --git a/splitio/admin/views/dashboard/js.go b/splitio/admin/views/dashboard/js.go
index 434766dd..48344789 100644
--- a/splitio/admin/views/dashboard/js.go
+++ b/splitio/admin/views/dashboard/js.go
@@ -86,6 +86,23 @@ const mainScript = `
}
});
}
+
+ function resetFilterRuleBasedSegments(){
+ $("tr.ruleBasedItem").removeClass("filterDisplayNone");
+ $("#filterRuleBasedSegmentNameInput").val("");
+ }
+
+ function filterRuleBasedSegments(){
+ $("tr.ruleBasedItem").removeClass("filterDisplayNone");
+ var filter = $("#filterRuleBasedSegmentNameInput").val();
+ $("tr.ruleBasedItem").each(function() {
+ $this = $(this);
+ var ruleBasedName = $this.find("span.ruleBasedItemName").html();
+ if (ruleBasedName.indexOf(filter.trim()) == -1) {
+ $this.addClass("filterDisplayNone");
+ }
+ });
+ }
$(function () {
$('[data-toggle="tooltip"]').tooltip()
@@ -255,6 +272,42 @@ const mainScript = `
}
};
+ function formatRuleBasedSegment(ruleBasedSegment) {
+ var excludedSegments = Array.isArray(ruleBasedSegment.excludedSegments)
+ ? ruleBasedSegment.excludedSegments
+ : [];
+
+ var excludedSegmentsHtml = excludedSegments.length
+ ? excludedSegments.map(function(seg, i) {
+ var segName = seg && seg.name ? seg.name : 'Unnamed';
+ var segType = seg && seg.type ? seg.type : 'Unknown';
+ var separator = i < excludedSegments.length - 1 ? ', ' : '';
+ return '' + segName + ' (' + segType + ')' + separator;
+ }).join('')
+ : '—';
+
+ return (
+ '
' +
+ '| ' + ruleBasedSegment.name + ' | ' +
+ (ruleBasedSegment.active
+ ? 'ACTIVE | '
+ : 'ARCHIVED | ') +
+ '' + (ruleBasedSegment.excludedKeys || '') + ' | ' +
+ '' + excludedSegmentsHtml + ' | ' +
+ '' + (ruleBasedSegment.cn || '') + ' | ' +
+ '
\n'
+ );
+ }
+
+ function updateRuleBasedSegments(ruleBasedSegments) {
+ ruleBasedSegments.sort((a, b) => parseFloat(b.changeNumber) - parseFloat(a.changeNumber));
+ const formatted = ruleBasedSegments.map(formatRuleBasedSegment).join('\n');
+ if (document.getElementById('filterRuleBasedSegmentNameInput').value.length == 0) {
+ $('#rule_based_segment_rows tbody').empty();
+ $('#rule_based_segment_rows tbody').append(formatted);
+ }
+ };
+
function formatFlagSet(flagSet) {
return (
'' +
@@ -443,6 +496,7 @@ const mainScript = `
updateFeatureFlags(stats.featureFlags);
updateSegments(stats.segments);
updateLargeSegments(stats.largesegments);
+ updateRuleBasedSegments(stats.rulebasedsegments)
updateLogEntries(stats.loggedMessages);
updateFlagSets(stats.flagSets)
diff --git a/splitio/admin/views/dashboard/main.go b/splitio/admin/views/dashboard/main.go
index 6149a30c..757a14dd 100644
--- a/splitio/admin/views/dashboard/main.go
+++ b/splitio/admin/views/dashboard/main.go
@@ -89,26 +89,26 @@ type RootObject struct {
// GlobalStats runtime stats used to render the dashboard
type GlobalStats struct {
- BackendTotalRequests int64 `json:"backendTotalRequests"`
- RequestsOk int64 `json:"requestsOk"`
- RequestsErrored int64 `json:"requestsErrored"`
- BackendRequestsOk int64 `json:"backendRequestsOk"`
- BackendRequestsErrored int64 `json:"backendRequestsErrored"`
- SdksTotalRequests int64 `json:"sdksTotalRequests"`
- LoggedErrors int64 `json:"loggedErrors"`
- LoggedMessages []string `json:"loggedMessages"`
- FeatureFlags []SplitSummary `json:"featureFlags"`
- Segments []SegmentSummary `json:"segments"`
- LargeSegments []LargeSegmentSummary `json:"largesegments"`
- Latencies []ChartJSData `json:"latencies"`
- BackendLatencies []ChartJSData `json:"backendLatencies"`
- ImpressionsQueueSize int64 `json:"impressionsQueueSize"`
- ImpressionsLambda float64 `json:"impressionsLambda"`
- EventsQueueSize int64 `json:"eventsQueueSize"`
- EventsLambda float64 `json:"eventsLambda"`
- Uptime int64 `json:"uptime"`
- FlagSets []FlagSetsSummary `json:"flagSets"`
- RuleBasedSegments []RBSummary `json:"ruleBasedSegments"`
+ BackendTotalRequests int64 `json:"backendTotalRequests"`
+ RequestsOk int64 `json:"requestsOk"`
+ RequestsErrored int64 `json:"requestsErrored"`
+ BackendRequestsOk int64 `json:"backendRequestsOk"`
+ BackendRequestsErrored int64 `json:"backendRequestsErrored"`
+ SdksTotalRequests int64 `json:"sdksTotalRequests"`
+ LoggedErrors int64 `json:"loggedErrors"`
+ LoggedMessages []string `json:"loggedMessages"`
+ FeatureFlags []SplitSummary `json:"featureFlags"`
+ Segments []SegmentSummary `json:"segments"`
+ LargeSegments []LargeSegmentSummary `json:"largesegments"`
+ RuleBasedSegments []RuleBasedSegmentSummary `json:"rulebasedsegments"`
+ Latencies []ChartJSData `json:"latencies"`
+ BackendLatencies []ChartJSData `json:"backendLatencies"`
+ ImpressionsQueueSize int64 `json:"impressionsQueueSize"`
+ ImpressionsLambda float64 `json:"impressionsLambda"`
+ EventsQueueSize int64 `json:"eventsQueueSize"`
+ EventsLambda float64 `json:"eventsLambda"`
+ Uptime int64 `json:"uptime"`
+ FlagSets []FlagSetsSummary `json:"flagSets"`
}
// SplitSummary encapsulates a minimalistic view of feature flag properties to be presented in the dashboard
@@ -138,6 +138,20 @@ type LargeSegmentSummary struct {
LastModified string `json:"cn"`
}
+type RuleBasedSegmentSummary struct {
+ Name string `json:"name"`
+ Active bool `json:"active"`
+ ExcludedKeys []string `json:"excludedKeys"`
+ ExcludedSegments []ExcludedSegments `json:"excludedSegments"`
+ LastModified string `json:"cn"`
+ ChangeNumber int64 `json:"changeNumber"`
+}
+
+type ExcludedSegments struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+}
+
// SegmentKeySummary encapsulates basic information associated to the key in proxy mode
// (fields other than name are empty when running as producer
type SegmentKeySummary struct {
@@ -152,14 +166,6 @@ type FlagSetsSummary struct {
FeatureFlags string `json:"featureFlags"`
}
-type RBSummary struct {
- Name string `json:"name"`
- ChangeNumber int64 `json:"cn"`
- Active bool `json:"active"`
- ExcludedKeys []string `json:"excludedKeys"`
- ExcludedSegments []string `json:"excludedSegments"`
-}
-
// RGBA bundles input to CSS's rgba function
type RGBA struct {
Red int32
diff --git a/splitio/commitversion.go b/splitio/commitversion.go
index d1705cd1..ff47aa2b 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 = "35fdde3"
+const CommitVersion = "8fd26d7"
diff --git a/splitio/proxy/storage/rulebasedsegments.go b/splitio/proxy/storage/rulebasedsegments.go
index db62f3ee..4f71d6f1 100644
--- a/splitio/proxy/storage/rulebasedsegments.go
+++ b/splitio/proxy/storage/rulebasedsegments.go
@@ -141,12 +141,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
diff --git a/splitio/proxy/storage/rulebasedsegments_test.go b/splitio/proxy/storage/rulebasedsegments_test.go
new file mode 100644
index 00000000..8be5a17b
--- /dev/null
+++ b/splitio/proxy/storage/rulebasedsegments_test.go
@@ -0,0 +1,74 @@
+package storage
+
+import (
+ "testing"
+
+ "github.com/splitio/split-synchronizer/v5/splitio/proxy/storage/persistent"
+
+ "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)
+
+ dbw, err := persistent.NewBoltWrapper(persistent.BoltInMemoryMode, nil)
+ assert.Nil(t, err)
+ pss := NewProxyRuleBasedSegmentsStorage(dbw, logger, false)
+
+ // From -1
+ rbs := []dtos.RuleBasedSegmentDTO{
+ {Name: "rbs1", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"},
+ {Name: "rbs2", ChangeNumber: 10, Status: "ACTIVE", TrafficTypeName: "user"},
+ }
+ pss.Update(rbs, 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, rbs, changes.RuleBasedSegments)
+
+ changes, err = pss.ChangesSince(5)
+ assert.Equal(t, ErrSinceParamTooOld, err)
+ assert.Nil(t, changes)
+
+ // 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"},
+ {Name: "rbs3", ChangeNumber: 15, Status: "ACTIVE", TrafficTypeName: "user"},
+ }
+ assert.ElementsMatch(t, expectedRBSs, changes.RuleBasedSegments)
+
+ 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 {