Skip to content
Merged
2 changes: 1 addition & 1 deletion splitio/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func NewServer(options *Options) (*AdminServer, error) {
observabilityController.Register(admin)

if options.Snapshotter != nil {
snapshotController := controllers.NewSnapshotController(options.Logger, options.Snapshotter)
snapshotController := controllers.NewSnapshotController(options.Logger, options.Snapshotter, options.FlagSpecVersion)
snapshotController.Register(admin)
}

Expand Down
1 change: 1 addition & 0 deletions splitio/admin/controllers/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
39 changes: 39 additions & 0 deletions splitio/admin/controllers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.ExcludedSegments, 0, len(ruleBased.Excluded.Segments))
for _, excludedSegment := range ruleBased.Excluded.Segments {
excluededSegments = append(excluededSegments, dashboard.ExcludedSegments{
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,
ExcludedSegments: 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)
Expand Down
29 changes: 29 additions & 0 deletions splitio/admin/controllers/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package controllers

import (
"testing"

"github.com/splitio/split-synchronizer/v5/splitio/admin/views/dashboard"

"github.com/splitio/go-split-commons/v8/dtos"
"github.com/splitio/go-split-commons/v8/storage/mocks"
"github.com/splitio/go-toolkit/v5/datastructures/set"

"github.com/stretchr/testify/assert"
)

func TestBundleRBInfo(t *testing.T) {
split := &mocks.SplitStorageMock{}
split.On("RuleBasedSegmentNames").Return(set.NewSet("rb1", "rb2"), nil).Once()
rb := &mocks.MockRuleBasedSegmentStorage{}
rb.On("GetRuleBasedSegmentByName", "rb1").Return(&dtos.RuleBasedSegmentDTO{Name: "rb1", ChangeNumber: 1, Status: "ACTIVE", Excluded: dtos.ExcludedDTO{Keys: []string{"one"}}}, nil).Once()
rb.On("GetRuleBasedSegmentByName", "rb2").Return(&dtos.RuleBasedSegmentDTO{Name: "rb2", ChangeNumber: 2, Status: "ARCHIVED"}, nil).Once()
result := bundleRuleBasedInfo(split, rb)
assert.Len(t, result, 2)
assert.ElementsMatch(t, result, []dashboard.RuleBasedSegmentSummary{
{Name: "rb1", ChangeNumber: 1, Active: true, ExcludedKeys: []string{"one"}, ExcludedSegments: []dashboard.ExcludedSegments{}, LastModified: "Thu Jan 1 00:00:00 UTC 1970"},
{Name: "rb2", ChangeNumber: 2, Active: false, ExcludedKeys: []string{}, ExcludedSegments: []dashboard.ExcludedSegments{}, LastModified: "Thu Jan 1 00:00:00 UTC 1970"},
})
split.AssertExpectations(t)
rb.AssertExpectations(t)
}
11 changes: 6 additions & 5 deletions splitio/admin/controllers/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (

// SnapshotController bundles endpoints associated to snapshot management
type SnapshotController struct {
logger logging.LoggerInterface
db storage.Snapshotter
logger logging.LoggerInterface
db storage.Snapshotter
version string
}

// NewSnapshotController constructs a new snapshot controller
func NewSnapshotController(logger logging.LoggerInterface, db storage.Snapshotter) *SnapshotController {
return &SnapshotController{logger: logger, db: db}
func NewSnapshotController(logger logging.LoggerInterface, db storage.Snapshotter, version string) *SnapshotController {
return &SnapshotController{logger: logger, db: db, version: version}
}

// Register mounts the endpoints int he provided router
Expand All @@ -38,7 +39,7 @@ func (c *SnapshotController) downloadSnapshot(ctx *gin.Context) {
return
}

s, err := snapshot.New(snapshot.Metadata{Version: 1, Storage: snapshot.StorageBoltDB}, b)
s, err := snapshot.New(snapshot.Metadata{Version: 1, Storage: snapshot.StorageBoltDB, SpecVersion: c.version}, b)
if err != nil {
c.logger.Error("error building snapshot: ", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "error building snapshot"})
Expand Down
54 changes: 15 additions & 39 deletions splitio/admin/controllers/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package controllers

import (
"bytes"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -13,31 +13,23 @@ import (
"github.com/splitio/go-toolkit/v5/logging"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)

func TestDownloadProxySnapshot(t *testing.T) {
// Read DB snapshot for test
path := "../../../test/snapshot/proxy.snapshot"
snap, err := snapshot.DecodeFromFile(path)
if err != nil {
t.Error(err)
return
}
assert.Nil(t, err)

tmpDataFile, err := snap.WriteDataToTmpFile()
if err != nil {
t.Error(err)
return
}
assert.Nil(t, err)

// loading snapshot from disk
dbInstance, err := persistent.NewBoltWrapper(tmpDataFile, nil)
if err != nil {
t.Error(err)
return
}
assert.Nil(t, err)

ctrl := NewSnapshotController(logging.NewLogger(nil), dbInstance)
ctrl := NewSnapshotController(logging.NewLogger(nil), dbInstance, "1.3")

resp := httptest.NewRecorder()
ctx, router := gin.CreateTestContext(resp)
Expand All @@ -46,35 +38,19 @@ func TestDownloadProxySnapshot(t *testing.T) {
ctx.Request, _ = http.NewRequest(http.MethodGet, "/snapshot", nil)
router.ServeHTTP(resp, ctx.Request)

responseBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Error(err)
return
}
responseBody, err := io.ReadAll(resp.Body)
assert.Nil(t, err)

snapRes, err := snapshot.Decode(responseBody)
if err != nil {
t.Error(err)
return
}
assert.Nil(t, err)

if snapRes.Meta().Version != 1 {
t.Error("Invalid Metadata version")
}

if snapRes.Meta().Storage != 1 {
t.Error("Invalid Metadata storage")
}
assert.Equal(t, uint64(1), snapRes.Meta().Version)
assert.Equal(t, uint64(1), snapRes.Meta().Storage)
assert.Equal(t, "1.3", snapRes.Meta().SpecVersion)

dat, err := snap.Data()
if err != nil {
t.Error(err)
}
assert.Nil(t, err)
resData, err := snapRes.Data()
if err != nil {
t.Error(err)
}
if bytes.Compare(dat, resData) != 0 {
t.Error("loaded snapshot is different to downloaded")
}
assert.Nil(t, err)
assert.Equal(t, 0, bytes.Compare(dat, resData))
}
57 changes: 57 additions & 0 deletions splitio/admin/views/dashboard/datainspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ const dataInspector = `
</a>
</li>
{{end}}
<li role="presentation" class="">
<a href="#rule-based-segments-data" aria-controls="rule-based" role="tab" data-toggle="tab">
<span class="glyphicon" style="vertical-align:bottom" aria-hidden="true">
<svg fill="none" height="24" width="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 4C5.44772 4 5 4.44772 5 5V9C5 9.55228 5.44772 10 6 10H10C10.5523 10 11 9.55228 11 9V8H13V11H10V13H13V16H11V15C11 14.4477 10.5523 14 10 14H6C5.44772 14 5 14.4477 5 15V19C5 19.5523 5.44772 20 6 20H10C10.5523 20 11 19.5523 11 19V18H13V20H18C18.5523 20 19 19.5523 19 19V15C19 14.4477 18.5523 14 18 14H15V10H18C18.5523 10 19 9.55228 19 9V5C19 4.44772 18.5523 4 18 4H13V6H11V5C11 4.44772 10.5523 4 10 4H6Z"
fill="currentColor"
/>
</svg>
</span>
&nbsp;Rule-based Segments
</a>
</li>
<li role="presentation" class="">
<a href="#flag-sets-data" aria-controls="profile" role="tab" data-toggle="tab">
<span class="glyphicon" style="vertical-align:bottom" aria-hidden="true">
Expand Down Expand Up @@ -154,6 +167,50 @@ const dataInspector = `
</div>
{{end}}

<!-- RULE-BASED SEGMENT DATA -->
<div role="tabpanel" class="tab-pane" id="rule-based-segments-data">
<div class="row">
<div class="col-md-12">
<div class="bg-primary metricBox">
<!-- <h4>Rule-based Segments in proxy</h4> -->
<div class="row">
<div class="col-md-4 col-md-offset-8">
<div class="input-group">
<input type="text" id="filterRuleBasedSegmentNameInput" class="form-control" placeholder="Filter by Rule-based Segment name">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="javascript:filterRuleBasedSegments();">
<span class="glyphicon glyphicon-filter" aria-hidden="true"></span>
</button>
<button class="btn btn-default" type="button" onclick="javascript:resetFilterRuleBasedSegments();">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="rule_based_segment_rows" class="table table-condensed table-hover">
<thead>
<tr>
<th>Rule-based segment</th>
<th>Status</th>
<th>Excluded Keys</th>
<th>Excluded segments</th>
<th>Last Modified</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>


<!-- FLAG SETS DATA -->
<div role="tabpanel" class="tab-pane" id="flag-sets-data">
<div class="row">
Expand Down
54 changes: 54 additions & 0 deletions splitio/admin/views/dashboard/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 '<span>' + segName + ' (' + segType + ')</span>' + separator;
}).join('')
: '—';

return (
'<tr class="ruleBasedItem">' +
'<td><span class="ruleBasedItemName">' + ruleBasedSegment.name + '</span></td>' +
(ruleBasedSegment.active
? '<td class="">ACTIVE</td>'
: '<td class="danger">ARCHIVED</td>') +
'<td>' + (ruleBasedSegment.excludedKeys || '') + '</td>' +
'<td>' + excludedSegmentsHtml + '</td>' +
'<td>' + (ruleBasedSegment.cn || '') + '</td>' +
'</tr>\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 (
'<tr class="flagSetItem">' +
Expand Down Expand Up @@ -443,6 +496,7 @@ const mainScript = `
updateFeatureFlags(stats.featureFlags);
updateSegments(stats.segments);
updateLargeSegments(stats.largesegments);
updateRuleBasedSegments(stats.rulebasedsegments)
updateLogEntries(stats.loggedMessages);
updateFlagSets(stats.flagSets)

Expand Down
Loading
Loading