diff --git a/splitio/admin/admin.go b/splitio/admin/admin.go index 3d0da679..47c9f87c 100644 --- a/splitio/admin/admin.go +++ b/splitio/admin/admin.go @@ -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) } 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..7565aed7 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.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) diff --git a/splitio/admin/controllers/helpers_test.go b/splitio/admin/controllers/helpers_test.go new file mode 100644 index 00000000..7889dfcb --- /dev/null +++ b/splitio/admin/controllers/helpers_test.go @@ -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) +} diff --git a/splitio/admin/controllers/snapshot.go b/splitio/admin/controllers/snapshot.go index 2bc933af..657907b3 100644 --- a/splitio/admin/controllers/snapshot.go +++ b/splitio/admin/controllers/snapshot.go @@ -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 @@ -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"}) diff --git a/splitio/admin/controllers/snapshot_test.go b/splitio/admin/controllers/snapshot_test.go index 28d0e69c..77394a7e 100644 --- a/splitio/admin/controllers/snapshot_test.go +++ b/splitio/admin/controllers/snapshot_test.go @@ -2,7 +2,7 @@ package controllers import ( "bytes" - "io/ioutil" + "io" "net/http" "net/http/httptest" "testing" @@ -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) @@ -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)) } 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 + +