From b9c41d1b4e0f2401d32f3428ffe91d00dc49c1d3 Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Sat, 28 Feb 2026 17:52:54 +0200 Subject: [PATCH 1/6] refactor(meshmodel reg): removed the cycle dependency Signed-off-by: Amr-Shams --- models/meshmodel/entity/types.go | 9 +++++++++ models/meshmodel/registry/error.go | 11 ++--------- models/meshmodel/registry/registry.go | 15 +++++++++++++++ .../registry/v1alpha3/relationship_filter.go | 4 ++-- .../meshmodel/registry/v1beta1/category_filter.go | 4 ++-- .../registry/v1beta1/component_filter.go | 4 ++-- models/meshmodel/registry/v1beta1/model_filter.go | 4 ++-- .../meshmodel/registry/v1beta1/policy_filter.go | 4 ++-- 8 files changed, 36 insertions(+), 19 deletions(-) diff --git a/models/meshmodel/entity/types.go b/models/meshmodel/entity/types.go index aee0c112..ca502fe6 100644 --- a/models/meshmodel/entity/types.go +++ b/models/meshmodel/entity/types.go @@ -31,3 +31,12 @@ type Entity interface { GetID() uuid.UUID Create(db *database.Handler, hostID uuid.UUID) (entityID uuid.UUID, err error) } +type Summary interface { + KeyValue() string + CountValue() int +} + +// SummaryFilter is the interface for entities that expose aggregated summary data. +type SummaryFilter[T Summary] interface { + GetSummary(db *database.Handler) (*T, error) +} diff --git a/models/meshmodel/registry/error.go b/models/meshmodel/registry/error.go index 7bb40e69..9d47d477 100644 --- a/models/meshmodel/registry/error.go +++ b/models/meshmodel/registry/error.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/meshery/meshkit/errors" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" ) var ( @@ -18,15 +19,7 @@ var ( ) func ErrGetById(err error, id string) error { - return errors.New( - ErrUnknownHostCode, - errors.Alert, - []string{"Failed to get the entity with the given ID: " + id}, - []string{err.Error()}, - []string{"Entity with the given ID may not be present in the registry", "Registry might be inaccessible at the moment"}, - []string{"Check if your ID is correct", "If the registry is inaccesible, please try again after some time"}, - ) - + return registryerrors.ErrGetById(err, id) } func ErrUnknownHost(err error) error { diff --git a/models/meshmodel/registry/registry.go b/models/meshmodel/registry/registry.go index 2d37a910..72385938 100644 --- a/models/meshmodel/registry/registry.go +++ b/models/meshmodel/registry/registry.go @@ -10,6 +10,8 @@ import ( "github.com/meshery/meshkit/database" models "github.com/meshery/meshkit/models/meshmodel/core/v1beta1" "github.com/meshery/meshkit/models/meshmodel/entity" + regv1alpha3 "github.com/meshery/meshkit/models/meshmodel/registry/v1alpha3" + regv1beta1 "github.com/meshery/meshkit/models/meshmodel/registry/v1beta1" "github.com/meshery/schemas/models/v1alpha3/relationship" "github.com/meshery/schemas/models/v1beta1/category" "github.com/meshery/schemas/models/v1beta1/component" @@ -106,6 +108,19 @@ func NewRegistryManager(db *database.Handler) (*RegistryManager, error) { } return &rm, nil } + +func (rm *RegistryManager) GetComponentSummary( + f *regv1beta1.ComponentSummaryFilter, +) (*regv1beta1.ComponentSummary, error) { + return f.GetSummary(rm.db) +} + +func (rm *RegistryManager) GetRelationshipSummary( + f *regv1alpha3.RelationshipSummaryFilter, +) (*regv1alpha3.RelationshipSummary, error) { + return f.GetSummary(rm.db) +} + func (rm *RegistryManager) Cleanup() { _ = rm.db.Migrator().DropTable( &Registry{}, diff --git a/models/meshmodel/registry/v1alpha3/relationship_filter.go b/models/meshmodel/registry/v1alpha3/relationship_filter.go index f74be193..ef8882c4 100644 --- a/models/meshmodel/registry/v1alpha3/relationship_filter.go +++ b/models/meshmodel/registry/v1alpha3/relationship_filter.go @@ -3,7 +3,7 @@ package v1alpha3 import ( "github.com/meshery/meshkit/database" "github.com/meshery/meshkit/models/meshmodel/entity" - "github.com/meshery/meshkit/models/meshmodel/registry" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" "github.com/meshery/schemas/models/v1alpha3/relationship" "gorm.io/gorm/clause" @@ -39,7 +39,7 @@ func (rf *RelationshipFilter) GetById(db *database.Handler) (entity.Entity, erro r := &relationship.RelationshipDefinition{} err := db.First(r, "id = ?", rf.Id).Error if err != nil { - return nil, registry.ErrGetById(err, rf.Id) + return nil, registryerrors.ErrGetById(err, rf.Id) } return r, err } diff --git a/models/meshmodel/registry/v1beta1/category_filter.go b/models/meshmodel/registry/v1beta1/category_filter.go index e0964605..2a5f504a 100644 --- a/models/meshmodel/registry/v1beta1/category_filter.go +++ b/models/meshmodel/registry/v1beta1/category_filter.go @@ -3,7 +3,7 @@ package v1beta1 import ( "github.com/meshery/meshkit/database" "github.com/meshery/meshkit/models/meshmodel/entity" - "github.com/meshery/meshkit/models/meshmodel/registry" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" "github.com/meshery/schemas/models/v1beta1/category" "gorm.io/gorm/clause" ) @@ -30,7 +30,7 @@ func (cf *CategoryFilter) GetById(db *database.Handler) (entity.Entity, error) { c := &category.CategoryDefinition{} err := db.First(c, "id = ?", cf.Id).Error if err != nil { - return nil, registry.ErrGetById(err, cf.Id) + return nil, registryerrors.ErrGetById(err, cf.Id) } return c, err } diff --git a/models/meshmodel/registry/v1beta1/component_filter.go b/models/meshmodel/registry/v1beta1/component_filter.go index f2c39ac0..55a0b19f 100644 --- a/models/meshmodel/registry/v1beta1/component_filter.go +++ b/models/meshmodel/registry/v1beta1/component_filter.go @@ -3,7 +3,7 @@ package v1beta1 import ( "github.com/meshery/meshkit/database" "github.com/meshery/meshkit/models/meshmodel/entity" - "github.com/meshery/meshkit/models/meshmodel/registry" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" "github.com/meshery/schemas/models/v1beta1/category" "github.com/meshery/schemas/models/v1beta1/component" "github.com/meshery/schemas/models/v1beta1/connection" @@ -40,7 +40,7 @@ func (cf *ComponentFilter) GetById(db *database.Handler) (entity.Entity, error) c := &component.ComponentDefinition{} err := db.First(c, "id = ?", cf.Id).Error if err != nil { - return nil, registry.ErrGetById(err, cf.Id) + return nil, registryerrors.ErrGetById(err, cf.Id) } return c, err } diff --git a/models/meshmodel/registry/v1beta1/model_filter.go b/models/meshmodel/registry/v1beta1/model_filter.go index eda466f4..02b9a0e5 100644 --- a/models/meshmodel/registry/v1beta1/model_filter.go +++ b/models/meshmodel/registry/v1beta1/model_filter.go @@ -3,7 +3,7 @@ package v1beta1 import ( "github.com/meshery/meshkit/database" "github.com/meshery/meshkit/models/meshmodel/entity" - "github.com/meshery/meshkit/models/meshmodel/registry" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" "github.com/meshery/schemas/models/v1alpha3/relationship" "github.com/meshery/schemas/models/v1beta1/component" "github.com/meshery/schemas/models/v1beta1/model" @@ -58,7 +58,7 @@ func (mf *ModelFilter) GetById(db *database.Handler) (entity.Entity, error) { // Retrieve the model by ID err := db.First(m, "id = ?", mf.Id).Error if err != nil { - return nil, registry.ErrGetById(err, mf.Id) + return nil, registryerrors.ErrGetById(err, mf.Id) } // Include components if requested diff --git a/models/meshmodel/registry/v1beta1/policy_filter.go b/models/meshmodel/registry/v1beta1/policy_filter.go index b83edb6d..ad6c5d42 100644 --- a/models/meshmodel/registry/v1beta1/policy_filter.go +++ b/models/meshmodel/registry/v1beta1/policy_filter.go @@ -4,7 +4,7 @@ import ( "github.com/meshery/meshkit/database" "github.com/meshery/meshkit/models/meshmodel/core/v1beta1" "github.com/meshery/meshkit/models/meshmodel/entity" - "github.com/meshery/meshkit/models/meshmodel/registry" + registryerrors "github.com/meshery/meshkit/models/meshmodel/registry/errors" ) type PolicyFilter struct { @@ -29,7 +29,7 @@ func (pf *PolicyFilter) GetById(db *database.Handler) (entity.Entity, error) { p := &v1beta1.PolicyDefinition{} err := db.First(p, "id = ?", pf.Id).Error if err != nil { - return nil, registry.ErrGetById(err, pf.Id) + return nil, registryerrors.ErrGetById(err, pf.Id) } return p, err } From 39ae9c4acb488fbc6fb1796d8f3993301d7e9971 Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Sat, 28 Feb 2026 17:53:41 +0200 Subject: [PATCH 2/6] feat(filters): added two new filters for summary Signed-off-by: Amr-Shams --- models/meshmodel/registry/errors/error.go | 20 +++ .../registry/v1alpha3/relationship_summary.go | 161 ++++++++++++++++++ .../v1alpha3/relationship_summary_test.go | 127 ++++++++++++++ .../registry/v1beta1/component_filter.go | 10 +- .../registry/v1beta1/component_summary.go | 138 +++++++++++++++ .../v1beta1/component_summary_test.go | 149 ++++++++++++++++ 6 files changed, 600 insertions(+), 5 deletions(-) create mode 100644 models/meshmodel/registry/errors/error.go create mode 100644 models/meshmodel/registry/v1alpha3/relationship_summary.go create mode 100644 models/meshmodel/registry/v1alpha3/relationship_summary_test.go create mode 100644 models/meshmodel/registry/v1beta1/component_summary.go create mode 100644 models/meshmodel/registry/v1beta1/component_summary_test.go diff --git a/models/meshmodel/registry/errors/error.go b/models/meshmodel/registry/errors/error.go new file mode 100644 index 00000000..3d2b3fdc --- /dev/null +++ b/models/meshmodel/registry/errors/error.go @@ -0,0 +1,20 @@ +package errors + +import ( + meshkiterrors "github.com/meshery/meshkit/errors" +) + +const ( + ErrGetByIdCode = "meshkit-11262" +) + +func ErrGetById(err error, id string) error { + return meshkiterrors.New( + ErrGetByIdCode, + meshkiterrors.Alert, + []string{"Failed to get the entity with the given ID: " + id}, + []string{err.Error()}, + []string{"Entity with the given ID may not be present in the registry", "Registry might be inaccessible at the moment"}, + []string{"Check if your ID is correct", "If the registry is inaccesible, please try again after some time"}, + ) +} diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary.go b/models/meshmodel/registry/v1alpha3/relationship_summary.go new file mode 100644 index 00000000..7b288de3 --- /dev/null +++ b/models/meshmodel/registry/v1alpha3/relationship_summary.go @@ -0,0 +1,161 @@ +package v1alpha3 + +import ( + "fmt" + "slices" + + "github.com/meshery/meshkit/database" + "github.com/meshery/schemas/models/v1alpha3/relationship" + + "gorm.io/gorm" +) + +type RelationshipSummaryFilter struct { + Kind string + Greedy bool + SubType string + RelationshipType string + Version string + ModelName string + Status string + Include []RelationshipSummaryDimension +} + +type RelationshipSummaryDimension string + +const ( + RelationshipSummaryByModel RelationshipSummaryDimension = "by_model" + RelationshipSummaryByKind RelationshipSummaryDimension = "by_kind" + RelationshipSummaryByType RelationshipSummaryDimension = "by_type" + RelationshipSummaryBySubType RelationshipSummaryDimension = "by_subtype" +) + +type RelationshipGroupEntry struct { + Key string + Count int +} + +func (r RelationshipGroupEntry) KeyValue() string { + return r.Key +} +func (r RelationshipGroupEntry) CountValue() int { + return r.Count +} + +type RelationshipSummary struct { + Total int64 + ByModel []RelationshipGroupEntry + ByKind []RelationshipGroupEntry + ByType []RelationshipGroupEntry + BySubType []RelationshipGroupEntry +} + +func (relationshipFilter *RelationshipSummaryFilter) Validate() error { + for _, dim := range relationshipFilter.Include { + switch dim { + case RelationshipSummaryByModel, RelationshipSummaryByKind, RelationshipSummaryByType, RelationshipSummaryBySubType: + // valid + default: + return fmt.Errorf("unknown include dimension %s", dim) + } + } + return nil +} + +func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Handler) (*RelationshipSummary, error) { + if err := relationshipFilter.Validate(); err != nil { + return nil, err + } + summary := &RelationshipSummary{} + + base := db.Model(&relationship.RelationshipDefinition{}). + Joins("JOIN model_dbs ON relationship_definition_dbs.model_id = model_dbs.id"). + Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") + + status := "enabled" + + if relationshipFilter.Status != "" { + status = relationshipFilter.Status + } + + base = base.Where("model_dbs.status = ?", status) + + if relationshipFilter.Kind != "" { + if relationshipFilter.Greedy { + base = base.Where("relationship_definition_dbs.kind LIKE ?", "%"+relationshipFilter.Kind+"%") + } else { + base = base.Where("relationship_definition_dbs.kind = ?", relationshipFilter.Kind) + } + } + + if relationshipFilter.RelationshipType != "" { + base = base.Where("relationship_definition_dbs.type = ?", relationshipFilter.RelationshipType) + } + + if relationshipFilter.SubType != "" { + base = base.Where("relationship_definition_dbs.sub_type = ?", relationshipFilter.SubType) + } + if relationshipFilter.ModelName != "" { + base = base.Where("model_dbs.name = ?", relationshipFilter.ModelName) + } + if relationshipFilter.Version != "" { + base = base.Where("model_dbs.model->>'version' = ?", relationshipFilter.Version) + } + if err := base.Distinct("relationship_definition_dbs.id").Count(&summary.Total).Error; err != nil { + return nil, err + } + + shouldCompute := func(dim RelationshipSummaryDimension) bool { + if len(relationshipFilter.Include) == 0 { + return true + } + + return slices.Contains(relationshipFilter.Include, dim) + } + + if shouldCompute(RelationshipSummaryByModel) { + var rows []RelationshipGroupEntry + err := base.Session(&gorm.Session{}). + Select("model_dbs.name as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). + Group("model_dbs.name"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByModel = rows + } + if shouldCompute(RelationshipSummaryByKind) { + var rows []RelationshipGroupEntry + err := base.Session(&gorm.Session{}). + Select("relationship_definition_dbs.kind as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). + Group("relationship_definition_dbs.kind"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByKind = rows + } + if shouldCompute(RelationshipSummaryByType) { + var rows []RelationshipGroupEntry + err := base.Session(&gorm.Session{}). + Select("relationship_definition_dbs.type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). + Group("relationship_definition_dbs.type"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByType = rows + } + if shouldCompute(RelationshipSummaryBySubType) { + var rows []RelationshipGroupEntry + err := base.Session(&gorm.Session{}). + Select("relationship_definition_dbs.sub_type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). + Group("relationship_definition_dbs.sub_type"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.BySubType = rows + } + return summary, nil +} diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary_test.go b/models/meshmodel/registry/v1alpha3/relationship_summary_test.go new file mode 100644 index 00000000..57c603f5 --- /dev/null +++ b/models/meshmodel/registry/v1alpha3/relationship_summary_test.go @@ -0,0 +1,127 @@ +package v1alpha3 + +import ( + "path/filepath" + "testing" + + "github.com/gofrs/uuid" + "github.com/meshery/meshkit/database" + "github.com/meshery/schemas/models/v1alpha3/relationship" + "github.com/meshery/schemas/models/v1beta1/category" + "github.com/meshery/schemas/models/v1beta1/model" + "github.com/stretchr/testify/require" +) + +func TestRelationshipSummary_GetSummary(t *testing.T) { + db := newRelationshipSummaryTestDB(t) + seedRelationshipSummaryData(t, db) + + f := &RelationshipSummaryFilter{ + Include: []RelationshipSummaryDimension{ + RelationshipSummaryByModel, + RelationshipSummaryByKind, + }, + } + + summary, err := f.GetSummary(db) + require.NoError(t, err) + require.Equal(t, int64(2), summary.Total) + require.Equal(t, map[string]int{"model-a": 1, "model-b": 1}, relationshipGroupToMap(summary.ByModel)) + require.Equal(t, map[string]int{"edge": 1, "hierarchy": 1}, relationshipGroupToMap(summary.ByKind)) + require.Empty(t, summary.ByType) + require.Empty(t, summary.BySubType) +} + +func TestRelationshipSummary_Validate(t *testing.T) { + f := &RelationshipSummaryFilter{ + Include: []RelationshipSummaryDimension{"invalid_dimension"}, + } + err := f.Validate() + require.Error(t, err) +} + +func newRelationshipSummaryTestDB(t *testing.T) *database.Handler { + t.Helper() + + h, err := database.New(database.Options{ + Engine: database.SQLITE, + Filename: filepath.Join(t.TempDir(), "relationship-summary.db"), + }) + require.NoError(t, err) + t.Cleanup(func() { + _ = h.DBClose() + }) + + err = h.AutoMigrate( + &category.CategoryDefinition{}, + &model.ModelDefinition{}, + &relationship.RelationshipDefinition{}, + ) + require.NoError(t, err) + + return &h +} + +func seedRelationshipSummaryData(t *testing.T, db *database.Handler) { + t.Helper() + + cat := category.CategoryDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "infra", + } + require.NoError(t, db.Create(&cat).Error) + + modelStatus := model.ModelDefinitionStatus("enabled") + modelA := model.ModelDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "model-a", + DisplayName: "Model A", + Status: modelStatus, + CategoryId: cat.Id, + Model: struct { + Version string `json:"version" yaml:"version"` + }{Version: "v1.0.0"}, + } + modelB := model.ModelDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "model-b", + DisplayName: "Model B", + Status: modelStatus, + CategoryId: cat.Id, + Model: struct { + Version string `json:"version" yaml:"version"` + }{Version: "v1.0.0"}, + } + require.NoError(t, db.Create(&modelA).Error) + require.NoError(t, db.Create(&modelB).Error) + + relationshipStatus := relationship.RelationshipDefinitionStatus("enabled") + rel1 := relationship.RelationshipDefinition{ + Id: uuid.Must(uuid.NewV4()), + Kind: "edge", + RelationshipType: "binding", + SubType: "parent", + Status: &relationshipStatus, + ModelId: modelA.Id, + Version: "v1.0.0", + } + rel2 := relationship.RelationshipDefinition{ + Id: uuid.Must(uuid.NewV4()), + Kind: "hierarchy", + RelationshipType: "binding", + SubType: "child", + Status: &relationshipStatus, + ModelId: modelB.Id, + Version: "v1.0.0", + } + require.NoError(t, db.Create(&rel1).Error) + require.NoError(t, db.Create(&rel2).Error) +} + +func relationshipGroupToMap(rows []RelationshipGroupEntry) map[string]int { + out := make(map[string]int, len(rows)) + for _, row := range rows { + out[row.Key] = row.Count + } + return out +} diff --git a/models/meshmodel/registry/v1beta1/component_filter.go b/models/meshmodel/registry/v1beta1/component_filter.go index 55a0b19f..cc603c0b 100644 --- a/models/meshmodel/registry/v1beta1/component_filter.go +++ b/models/meshmodel/registry/v1beta1/component_filter.go @@ -73,11 +73,11 @@ func (componentFilter *ComponentFilter) Get(db *database.Handler) ([]entity.Enti Joins("JOIN connections ON connections.id = model_dbs.connection_id") componentStatus := "enabled" - if componentFilter.Status != "" { - componentStatus = componentFilter.Status - } - finder = finder.Where("component_definition_dbs.status = ?", componentStatus) - + if componentFilter.Status != "" { + componentStatus = componentFilter.Status + } + finder = finder.Where("component_definition_dbs.status = ?", componentStatus) + if componentFilter.Greedy { if componentFilter.Name != "" && componentFilter.DisplayName != "" { finder = finder.Where("component_definition_dbs.component->>'kind' LIKE ? OR display_name LIKE ?", "%"+componentFilter.Name+"%", componentFilter.DisplayName+"%") diff --git a/models/meshmodel/registry/v1beta1/component_summary.go b/models/meshmodel/registry/v1beta1/component_summary.go new file mode 100644 index 00000000..b9a7af7d --- /dev/null +++ b/models/meshmodel/registry/v1beta1/component_summary.go @@ -0,0 +1,138 @@ +package v1beta1 + +import ( + "fmt" + "slices" + + "github.com/meshery/meshkit/database" + "github.com/meshery/schemas/models/v1beta1/component" + "gorm.io/gorm" +) + +type ComponentSummaryFilter struct { + ModelName string + CategoryName string + Version string + Status string + Annotations string + Registrant string + + Include []ComponentSummaryDimension +} + +type ComponentSummaryDimension string + +const ( + ComponentSummaryByModel ComponentSummaryDimension = "by_model" + ComponentSummaryByCategory ComponentSummaryDimension = "by_category" + ComponentSummaryByRegistrant ComponentSummaryDimension = "by_registrant" +) + +type ComponentGroupEntry struct { + Key string + Count int +} + +func (c ComponentGroupEntry) KeyValue() string { + return c.Key +} +func (c ComponentGroupEntry) CountValue() int { + return c.Count +} + +type ComponentSummary struct { + Total int64 + ByModel []ComponentGroupEntry + ByCategory []ComponentGroupEntry + ByRegistrant []ComponentGroupEntry +} + +func (f *ComponentSummaryFilter) Validate() error { + for _, dim := range f.Include { + switch dim { + case ComponentSummaryByModel, ComponentSummaryByCategory, ComponentSummaryByRegistrant: + // valid + default: + return fmt.Errorf("unknown include dimension %s", dim) + } + } + return nil +} +func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) (*ComponentSummary, error) { + if err := componentFilter.Validate(); err != nil { + return nil, err + } + summary := &ComponentSummary{} + base := db.Model(&component.ComponentDefinition{}). + Joins("JOIN model_dbs ON component_definition_dbs.model_id = model_dbs.id"). + Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id"). + Joins("JOIN connections ON connections.id = model_dbs.connection_id") + componentStatus := "enabled" + if componentFilter.Status != "" { + componentStatus = componentFilter.Status + } + base = base.Where("component_definition_dbs.status = ?", componentStatus) + switch componentFilter.Annotations { + case "true": + base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = true") + case "false": + base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = false") + } + + if componentFilter.ModelName != "" && componentFilter.ModelName != "all" { + base = base.Where("model_dbs.name = ?", componentFilter.ModelName) + } + + if componentFilter.CategoryName != "" { + base = base.Where("category_dbs.name = ?", componentFilter.CategoryName) + } + if componentFilter.Version != "" { + base = base.Where("model_dbs.model->>'version' = ?", componentFilter.Version) + } + if err := base.Distinct("component_definition_dbs.id").Count(&summary.Total).Error; err != nil { + return nil, err + } + // per dimension + shouldCompute := func(dim ComponentSummaryDimension) bool { + // compute all if no include dimension + if len(componentFilter.Include) == 0 { + return true + } + return slices.Contains(componentFilter.Include, dim) + } + if shouldCompute(ComponentSummaryByModel) { + var rows []ComponentGroupEntry + err := base.Session(&gorm.Session{}). + Select("model_dbs.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). + Group("model_dbs.name"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByModel = rows + } + if shouldCompute(ComponentSummaryByCategory) { + var rows []ComponentGroupEntry + err := base.Session(&gorm.Session{}). + Select("category_dbs.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). + Group("category_dbs.name"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByCategory = rows + } + if shouldCompute(ComponentSummaryByRegistrant) { + var rows []ComponentGroupEntry + err := base.Session(&gorm.Session{}). + Select("connections.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). + Group("connections.name"). + Scan(&rows).Error + if err != nil { + return nil, err + } + summary.ByRegistrant = rows + } + + return summary, nil +} diff --git a/models/meshmodel/registry/v1beta1/component_summary_test.go b/models/meshmodel/registry/v1beta1/component_summary_test.go new file mode 100644 index 00000000..f1442920 --- /dev/null +++ b/models/meshmodel/registry/v1beta1/component_summary_test.go @@ -0,0 +1,149 @@ +package v1beta1 + +import ( + "path/filepath" + "testing" + + "github.com/gofrs/uuid" + "github.com/meshery/meshkit/database" + "github.com/meshery/schemas/models/v1beta1/category" + "github.com/meshery/schemas/models/v1beta1/component" + "github.com/meshery/schemas/models/v1beta1/connection" + "github.com/meshery/schemas/models/v1beta1/model" + "github.com/stretchr/testify/require" +) + +func TestComponentSummary_GetSummary(t *testing.T) { + db := newComponentSummaryTestDB(t) + seedComponentSummaryData(t, db) + + f := &ComponentSummaryFilter{ + Include: []ComponentSummaryDimension{ + ComponentSummaryByModel, + ComponentSummaryByRegistrant, + }, + } + + summary, err := f.GetSummary(db) + require.NoError(t, err) + require.Equal(t, int64(3), summary.Total) + + require.Equal(t, map[string]int{"model-a": 2, "model-b": 1}, componentGroupToMap(summary.ByModel)) + require.Equal(t, map[string]int{"registrant-a": 2, "registrant-b": 1}, componentGroupToMap(summary.ByRegistrant)) + require.Empty(t, summary.ByCategory) +} + +func TestComponentSummary_Validate(t *testing.T) { + f := &ComponentSummaryFilter{ + Include: []ComponentSummaryDimension{"unknown_dimension"}, + } + err := f.Validate() + require.Error(t, err) +} + +func newComponentSummaryTestDB(t *testing.T) *database.Handler { + t.Helper() + + h, err := database.New(database.Options{ + Engine: database.SQLITE, + Filename: filepath.Join(t.TempDir(), "component-summary.db"), + }) + require.NoError(t, err) + t.Cleanup(func() { + _ = h.DBClose() + }) + + err = h.AutoMigrate( + &connection.Connection{}, + &category.CategoryDefinition{}, + &model.ModelDefinition{}, + &component.ComponentDefinition{}, + ) + require.NoError(t, err) + + return &h +} + +func seedComponentSummaryData(t *testing.T, db *database.Handler) { + t.Helper() + + connA := connection.Connection{ID: uuid.Must(uuid.NewV4()), Name: "registrant-a"} + connB := connection.Connection{ID: uuid.Must(uuid.NewV4()), Name: "registrant-b"} + require.NoError(t, db.Create(&connA).Error) + require.NoError(t, db.Create(&connB).Error) + + cat := category.CategoryDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "infra", + } + require.NoError(t, db.Create(&cat).Error) + + modelStatus := model.ModelDefinitionStatus("enabled") + modelA := model.ModelDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "model-a", + DisplayName: "Model A", + Status: modelStatus, + CategoryId: cat.Id, + RegistrantId: connA.ID, + Model: struct { + Version string `json:"version" yaml:"version"` + }{Version: "v1.0.0"}, + } + modelB := model.ModelDefinition{ + Id: uuid.Must(uuid.NewV4()), + Name: "model-b", + DisplayName: "Model B", + Status: modelStatus, + CategoryId: cat.Id, + RegistrantId: connB.ID, + Model: struct { + Version string `json:"version" yaml:"version"` + }{Version: "v1.0.0"}, + } + require.NoError(t, db.Create(&modelA).Error) + require.NoError(t, db.Create(&modelB).Error) + + componentStatus := component.ComponentDefinitionStatus("enabled") + comp1 := component.ComponentDefinition{ + Id: uuid.Must(uuid.NewV4()), + DisplayName: "comp-1", + Status: &componentStatus, + ModelId: modelA.Id, + Component: component.Component{ + Kind: "Deployment", + Version: "v1", + }, + } + comp2 := component.ComponentDefinition{ + Id: uuid.Must(uuid.NewV4()), + DisplayName: "comp-2", + Status: &componentStatus, + ModelId: modelA.Id, + Component: component.Component{ + Kind: "Service", + Version: "v1", + }, + } + comp3 := component.ComponentDefinition{ + Id: uuid.Must(uuid.NewV4()), + DisplayName: "comp-3", + Status: &componentStatus, + ModelId: modelB.Id, + Component: component.Component{ + Kind: "Pod", + Version: "v1", + }, + } + require.NoError(t, db.Create(&comp1).Error) + require.NoError(t, db.Create(&comp2).Error) + require.NoError(t, db.Create(&comp3).Error) +} + +func componentGroupToMap(rows []ComponentGroupEntry) map[string]int { + out := make(map[string]int, len(rows)) + for _, row := range rows { + out[row.Key] = row.Count + } + return out +} From e157eeaab5f7a644b44d81db1672033b0630b743 Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Sat, 28 Feb 2026 18:07:46 +0200 Subject: [PATCH 3/6] refactor(filters): added missing dimesnsion, fork a session for the count Signed-off-by: Amr-Shams --- .../registry/v1alpha3/relationship_summary.go | 4 +++- .../meshmodel/registry/v1beta1/component_summary.go | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary.go b/models/meshmodel/registry/v1alpha3/relationship_summary.go index 7b288de3..ab767d6f 100644 --- a/models/meshmodel/registry/v1alpha3/relationship_summary.go +++ b/models/meshmodel/registry/v1alpha3/relationship_summary.go @@ -101,7 +101,9 @@ func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Han if relationshipFilter.Version != "" { base = base.Where("model_dbs.model->>'version' = ?", relationshipFilter.Version) } - if err := base.Distinct("relationship_definition_dbs.id").Count(&summary.Total).Error; err != nil { + if err := base.Session(&gorm.Session{}). + Distinct("relationship_definition_dbs.id"). + Count(&summary.Total).Error; err != nil { return nil, err } diff --git a/models/meshmodel/registry/v1beta1/component_summary.go b/models/meshmodel/registry/v1beta1/component_summary.go index b9a7af7d..e7adaf41 100644 --- a/models/meshmodel/registry/v1beta1/component_summary.go +++ b/models/meshmodel/registry/v1beta1/component_summary.go @@ -47,8 +47,8 @@ type ComponentSummary struct { ByRegistrant []ComponentGroupEntry } -func (f *ComponentSummaryFilter) Validate() error { - for _, dim := range f.Include { +func (componentSummaryFilter *ComponentSummaryFilter) Validate() error { + for _, dim := range componentSummaryFilter.Include { switch dim { case ComponentSummaryByModel, ComponentSummaryByCategory, ComponentSummaryByRegistrant: // valid @@ -89,7 +89,13 @@ func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) if componentFilter.Version != "" { base = base.Where("model_dbs.model->>'version' = ?", componentFilter.Version) } - if err := base.Distinct("component_definition_dbs.id").Count(&summary.Total).Error; err != nil { + if componentFilter.Registrant != "" { + base = base.Where("connections.name = ?", componentFilter.Registrant) + } + + if err := base.Session(&gorm.Session{}). + Distinct("component_definition_dbs.id"). + Count(&summary.Total).Error; err != nil { return nil, err } // per dimension @@ -100,6 +106,7 @@ func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) } return slices.Contains(componentFilter.Include, dim) } + // partial error is not tolerated so the populated summary should all be correct if shouldCompute(ComponentSummaryByModel) { var rows []ComponentGroupEntry err := base.Session(&gorm.Session{}). From 84ff3cb88a3cf2aa723ea3eceb54fef37eefd5a3 Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Sat, 28 Feb 2026 19:54:12 +0200 Subject: [PATCH 4/6] refactor(filters): use dry principle Signed-off-by: Amr-Shams --- .../registry/v1alpha3/relationship_summary.go | 63 +++++++------------ .../registry/v1beta1/component_summary.go | 53 +++++++--------- 2 files changed, 46 insertions(+), 70 deletions(-) diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary.go b/models/meshmodel/registry/v1alpha3/relationship_summary.go index ab767d6f..747c06b4 100644 --- a/models/meshmodel/registry/v1alpha3/relationship_summary.go +++ b/models/meshmodel/registry/v1alpha3/relationship_summary.go @@ -115,49 +115,32 @@ func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Han return slices.Contains(relationshipFilter.Include, dim) } - if shouldCompute(RelationshipSummaryByModel) { - var rows []RelationshipGroupEntry - err := base.Session(&gorm.Session{}). - Select("model_dbs.name as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). - Group("model_dbs.name"). - Scan(&rows).Error - if err != nil { - return nil, err - } - summary.ByModel = rows - } - if shouldCompute(RelationshipSummaryByKind) { - var rows []RelationshipGroupEntry - err := base.Session(&gorm.Session{}). - Select("relationship_definition_dbs.kind as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). - Group("relationship_definition_dbs.kind"). - Scan(&rows).Error - if err != nil { - return nil, err - } - summary.ByKind = rows + type dimensionInfo struct { + dim RelationshipSummaryDimension + selectExpr string + groupExpr string + receiver *[]RelationshipGroupEntry } - if shouldCompute(RelationshipSummaryByType) { - var rows []RelationshipGroupEntry - err := base.Session(&gorm.Session{}). - Select("relationship_definition_dbs.type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). - Group("relationship_definition_dbs.type"). - Scan(&rows).Error - if err != nil { - return nil, err - } - summary.ByType = rows + + dimensions := []dimensionInfo{ + {RelationshipSummaryByModel, "model_dbs.name as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "model_dbs.name", &summary.ByModel}, + {RelationshipSummaryByKind, "relationship_definition_dbs.kind as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.kind", &summary.ByKind}, + {RelationshipSummaryByType, "relationship_definition_dbs.type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.type", &summary.ByType}, + {RelationshipSummaryBySubType, "relationship_definition_dbs.sub_type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.sub_type", &summary.BySubType}, } - if shouldCompute(RelationshipSummaryBySubType) { - var rows []RelationshipGroupEntry - err := base.Session(&gorm.Session{}). - Select("relationship_definition_dbs.sub_type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count"). - Group("relationship_definition_dbs.sub_type"). - Scan(&rows).Error - if err != nil { - return nil, err + + for _, d := range dimensions { + if shouldCompute(d.dim) { + var rows []RelationshipGroupEntry + err := base.Session(&gorm.Session{}). + Select(d.selectExpr). + Group(d.groupExpr). + Scan(&rows).Error + if err != nil { + return nil, err + } + *d.receiver = rows } - summary.BySubType = rows } return summary, nil } diff --git a/models/meshmodel/registry/v1beta1/component_summary.go b/models/meshmodel/registry/v1beta1/component_summary.go index e7adaf41..eeda9d16 100644 --- a/models/meshmodel/registry/v1beta1/component_summary.go +++ b/models/meshmodel/registry/v1beta1/component_summary.go @@ -106,39 +106,32 @@ func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) } return slices.Contains(componentFilter.Include, dim) } - // partial error is not tolerated so the populated summary should all be correct - if shouldCompute(ComponentSummaryByModel) { - var rows []ComponentGroupEntry - err := base.Session(&gorm.Session{}). - Select("model_dbs.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). - Group("model_dbs.name"). - Scan(&rows).Error - if err != nil { - return nil, err - } - summary.ByModel = rows + type dimensionInfo struct { + dim ComponentSummaryDimension + selectExpr string + groupExpr string + receiver *[]ComponentGroupEntry } - if shouldCompute(ComponentSummaryByCategory) { - var rows []ComponentGroupEntry - err := base.Session(&gorm.Session{}). - Select("category_dbs.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). - Group("category_dbs.name"). - Scan(&rows).Error - if err != nil { - return nil, err - } - summary.ByCategory = rows + + dimensions := []dimensionInfo{ + {ComponentSummaryByModel, "model_dbs.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "model_dbs.name", &summary.ByModel}, + {ComponentSummaryByCategory, "model_dbs.category_id as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "model_dbs.category_id", &summary.ByCategory}, + {ComponentSummaryByRegistrant, "connections.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "connections.name", &summary.ByRegistrant}, } - if shouldCompute(ComponentSummaryByRegistrant) { - var rows []ComponentGroupEntry - err := base.Session(&gorm.Session{}). - Select("connections.name as key, COUNT(DISTINCT component_definition_dbs.id) as count"). - Group("connections.name"). - Scan(&rows).Error - if err != nil { - return nil, err + + // partial error is not tolerated so the populated summary should all be correct + for _, d := range dimensions { + if shouldCompute(d.dim) { + var rows []ComponentGroupEntry + err := base.Session(&gorm.Session{}). + Select(d.selectExpr). + Group(d.groupExpr). + Scan(&rows).Error + if err != nil { + return nil, err + } + *d.receiver = rows } - summary.ByRegistrant = rows } return summary, nil From a4fbf5c577aab8c852af0d78187db5182af2a6cc Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Tue, 3 Mar 2026 17:53:20 +0200 Subject: [PATCH 5/6] refactor to use the new schema from openAPI Signed-off-by: Amr-Shams --- go.mod | 7 +- go.sum | 5 +- models/meshmodel/registry/registry.go | 24 +-- .../registry/v1alpha3/relationship_summary.go | 164 +++++++++--------- .../v1alpha3/relationship_summary_test.go | 53 ++++-- .../registry/v1beta1/component_summary.go | 160 ++++++++--------- .../v1beta1/component_summary_test.go | 35 ++-- 7 files changed, 232 insertions(+), 216 deletions(-) diff --git a/go.mod b/go.mod index bb542625..fc7cff55 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,7 @@ replace ( // github.com/docker/libcompose => github.com/docker/libcompose v0.4.1-0.20190808084053-143e0f3f1ab9 github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.5.5 github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 - -//github.com/meshery/schemas v0.8.34 => ../schemas + github.com/meshery/schemas => ../schemas ) require ( @@ -204,7 +203,7 @@ require ( github.com/lestrrat-go/httprc/v3 v3.0.2 // indirect github.com/lestrrat-go/jwx/v3 v3.0.12 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect - github.com/lib/pq v1.10.9 // indirect + github.com/lib/pq v1.11.1 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -309,3 +308,5 @@ require ( ) replace github.com/meshery/meshkit => ../meshkit + +replace github.com/meshery/schemas => ../schemas diff --git a/go.sum b/go.sum index 02052f4f..ad79384c 100644 --- a/go.sum +++ b/go.sum @@ -409,8 +409,9 @@ github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJ github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI= +github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= @@ -428,8 +429,6 @@ github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuE github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/meshery/meshery-operator v0.8.11 h1:eDo2Sw0jjVrXsvvhF8LenADM58pK+7Z68ROPVIejTPc= github.com/meshery/meshery-operator v0.8.11/go.mod h1:hQEtFKKa5Fr/Mskk6bV5ip3bQ0+3F0u1voYS3XisBp4= -github.com/meshery/schemas v0.8.113 h1:gQMKIJSKGTT6PuJje0XTxuHfCv9ERVLX9IrG1JyFAM8= -github.com/meshery/schemas v0.8.113/go.mod h1:UwoDPg3j/2BMfytdgaalpemRKUxB9HhFsUZqJA01/G4= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= diff --git a/models/meshmodel/registry/registry.go b/models/meshmodel/registry/registry.go index 72385938..73037a48 100644 --- a/models/meshmodel/registry/registry.go +++ b/models/meshmodel/registry/registry.go @@ -12,9 +12,9 @@ import ( "github.com/meshery/meshkit/models/meshmodel/entity" regv1alpha3 "github.com/meshery/meshkit/models/meshmodel/registry/v1alpha3" regv1beta1 "github.com/meshery/meshkit/models/meshmodel/registry/v1beta1" - "github.com/meshery/schemas/models/v1alpha3/relationship" + schemarelationship "github.com/meshery/schemas/models/v1alpha3/relationship" "github.com/meshery/schemas/models/v1beta1/category" - "github.com/meshery/schemas/models/v1beta1/component" + schemacomponent "github.com/meshery/schemas/models/v1beta1/component" "github.com/meshery/schemas/models/v1beta1/connection" "github.com/meshery/schemas/models/v1beta1/model" "golang.org/x/text/cases" @@ -97,8 +97,8 @@ func NewRegistryManager(db *database.Handler) (*RegistryManager, error) { err := rm.db.AutoMigrate( &Registry{}, &connection.Connection{}, - &component.ComponentDefinition{}, - &relationship.RelationshipDefinition{}, + &schemacomponent.ComponentDefinition{}, + &schemarelationship.RelationshipDefinition{}, &models.PolicyDefinition{}, &model.ModelDefinition{}, &category.CategoryDefinition{}, @@ -110,25 +110,25 @@ func NewRegistryManager(db *database.Handler) (*RegistryManager, error) { } func (rm *RegistryManager) GetComponentSummary( - f *regv1beta1.ComponentSummaryFilter, -) (*regv1beta1.ComponentSummary, error) { - return f.GetSummary(rm.db) + f *schemacomponent.ComponentSummaryFilter, +) (*schemacomponent.ComponentSummary, error) { + return regv1beta1.GetSummary(f, rm.db) } func (rm *RegistryManager) GetRelationshipSummary( - f *regv1alpha3.RelationshipSummaryFilter, -) (*regv1alpha3.RelationshipSummary, error) { - return f.GetSummary(rm.db) + f *schemarelationship.RelationshipSummaryFilter, +) (*schemarelationship.RelationshipSummary, error) { + return regv1alpha3.GetSummary(f, rm.db) } func (rm *RegistryManager) Cleanup() { _ = rm.db.Migrator().DropTable( &Registry{}, &connection.Connection{}, - &component.ComponentDefinition{}, + &schemacomponent.ComponentDefinition{}, &model.ModelDefinition{}, &category.CategoryDefinition{}, - &relationship.RelationshipDefinition{}, + &schemarelationship.RelationshipDefinition{}, ) } func (rm *RegistryManager) RegisterEntity(h connection.Connection, en entity.Entity) (bool, bool, error) { diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary.go b/models/meshmodel/registry/v1alpha3/relationship_summary.go index 747c06b4..dcc78611 100644 --- a/models/meshmodel/registry/v1alpha3/relationship_summary.go +++ b/models/meshmodel/registry/v1alpha3/relationship_summary.go @@ -5,101 +5,51 @@ import ( "slices" "github.com/meshery/meshkit/database" - "github.com/meshery/schemas/models/v1alpha3/relationship" + relationship "github.com/meshery/schemas/models/v1alpha3/relationship" "gorm.io/gorm" ) -type RelationshipSummaryFilter struct { - Kind string - Greedy bool - SubType string - RelationshipType string - Version string - ModelName string - Status string - Include []RelationshipSummaryDimension -} - -type RelationshipSummaryDimension string - -const ( - RelationshipSummaryByModel RelationshipSummaryDimension = "by_model" - RelationshipSummaryByKind RelationshipSummaryDimension = "by_kind" - RelationshipSummaryByType RelationshipSummaryDimension = "by_type" - RelationshipSummaryBySubType RelationshipSummaryDimension = "by_subtype" -) - -type RelationshipGroupEntry struct { - Key string - Count int -} - -func (r RelationshipGroupEntry) KeyValue() string { - return r.Key -} -func (r RelationshipGroupEntry) CountValue() int { - return r.Count -} - -type RelationshipSummary struct { - Total int64 - ByModel []RelationshipGroupEntry - ByKind []RelationshipGroupEntry - ByType []RelationshipGroupEntry - BySubType []RelationshipGroupEntry -} - -func (relationshipFilter *RelationshipSummaryFilter) Validate() error { - for _, dim := range relationshipFilter.Include { - switch dim { - case RelationshipSummaryByModel, RelationshipSummaryByKind, RelationshipSummaryByType, RelationshipSummaryBySubType: - // valid - default: - return fmt.Errorf("unknown include dimension %s", dim) - } - } - return nil -} - -func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Handler) (*RelationshipSummary, error) { - if err := relationshipFilter.Validate(); err != nil { +func GetSummary(relationshipFilter *relationship.RelationshipSummaryFilter, db *database.Handler) (*relationship.RelationshipSummary, error) { + if err := validate(relationshipFilter); err != nil { return nil, err } - summary := &RelationshipSummary{} - base := db.Model(&relationship.RelationshipDefinition{}). + summary := &relationship.RelationshipSummary{} + + base := db.Table("relationship_definition_dbs"). Joins("JOIN model_dbs ON relationship_definition_dbs.model_id = model_dbs.id"). Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") status := "enabled" - if relationshipFilter.Status != "" { - status = relationshipFilter.Status + if relationshipFilter.Status != nil { + status = *relationshipFilter.Status } base = base.Where("model_dbs.status = ?", status) - if relationshipFilter.Kind != "" { - if relationshipFilter.Greedy { - base = base.Where("relationship_definition_dbs.kind LIKE ?", "%"+relationshipFilter.Kind+"%") + if relationshipFilter.Kind != nil { + greedy := relationshipFilter.Greedy != nil && *relationshipFilter.Greedy + if greedy { + base = base.Where("relationship_definition_dbs.kind LIKE ?", "%"+*relationshipFilter.Kind+"%") } else { - base = base.Where("relationship_definition_dbs.kind = ?", relationshipFilter.Kind) + base = base.Where("relationship_definition_dbs.kind = ?", *relationshipFilter.Kind) } } - if relationshipFilter.RelationshipType != "" { - base = base.Where("relationship_definition_dbs.type = ?", relationshipFilter.RelationshipType) + if relationshipFilter.RelationshipType != nil { + base = base.Where("relationship_definition_dbs.type = ?", *relationshipFilter.RelationshipType) } - if relationshipFilter.SubType != "" { - base = base.Where("relationship_definition_dbs.sub_type = ?", relationshipFilter.SubType) + if relationshipFilter.SubType != nil { + base = base.Where("relationship_definition_dbs.sub_type = ?", *relationshipFilter.SubType) } - if relationshipFilter.ModelName != "" { - base = base.Where("model_dbs.name = ?", relationshipFilter.ModelName) + if relationshipFilter.ModelName != nil { + base = base.Where("model_dbs.name = ?", *relationshipFilter.ModelName) } - if relationshipFilter.Version != "" { - base = base.Where("model_dbs.model->>'version' = ?", relationshipFilter.Version) + if relationshipFilter.Version != nil { + base = base.Where("model_dbs.model->>'version' = ?", *relationshipFilter.Version) } if err := base.Session(&gorm.Session{}). Distinct("relationship_definition_dbs.id"). @@ -107,31 +57,64 @@ func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Han return nil, err } - shouldCompute := func(dim RelationshipSummaryDimension) bool { - if len(relationshipFilter.Include) == 0 { + shouldCompute := func(dim relationship.RelationshipSummaryFilterInclude) bool { + if relationshipFilter.Include == nil || len(*relationshipFilter.Include) == 0 { return true } - return slices.Contains(relationshipFilter.Include, dim) + return slices.Contains(*relationshipFilter.Include, dim) + } + + type groupEntry = struct { + Count int32 `json:"count" yaml:"count"` + Key string `json:"key" yaml:"key"` } type dimensionInfo struct { - dim RelationshipSummaryDimension + dim relationship.RelationshipSummaryFilterInclude selectExpr string groupExpr string - receiver *[]RelationshipGroupEntry + setRows func([]groupEntry) } dimensions := []dimensionInfo{ - {RelationshipSummaryByModel, "model_dbs.name as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "model_dbs.name", &summary.ByModel}, - {RelationshipSummaryByKind, "relationship_definition_dbs.kind as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.kind", &summary.ByKind}, - {RelationshipSummaryByType, "relationship_definition_dbs.type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.type", &summary.ByType}, - {RelationshipSummaryBySubType, "relationship_definition_dbs.sub_type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", "relationship_definition_dbs.sub_type", &summary.BySubType}, + { + dim: relationship.ByModel, + selectExpr: "model_dbs.name as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", + groupExpr: "model_dbs.name", + setRows: func(rows []groupEntry) { + summary.ByModel = &rows + }, + }, + { + dim: relationship.ByKind, + selectExpr: "relationship_definition_dbs.kind as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", + groupExpr: "relationship_definition_dbs.kind", + setRows: func(rows []groupEntry) { + summary.ByKind = &rows + }, + }, + { + dim: relationship.ByType, + selectExpr: "relationship_definition_dbs.type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", + groupExpr: "relationship_definition_dbs.type", + setRows: func(rows []groupEntry) { + summary.ByType = &rows + }, + }, + { + dim: relationship.BySubtype, + selectExpr: "relationship_definition_dbs.sub_type as Key, COUNT(DISTINCT(relationship_definition_dbs.id)) as Count", + groupExpr: "relationship_definition_dbs.sub_type", + setRows: func(rows []groupEntry) { + summary.BySubType = &rows + }, + }, } for _, d := range dimensions { if shouldCompute(d.dim) { - var rows []RelationshipGroupEntry + var rows []groupEntry err := base.Session(&gorm.Session{}). Select(d.selectExpr). Group(d.groupExpr). @@ -139,8 +122,25 @@ func (relationshipFilter *RelationshipSummaryFilter) GetSummary(db *database.Han if err != nil { return nil, err } - *d.receiver = rows + d.setRows(rows) } } return summary, nil } + +func validate(relationshipFilter *relationship.RelationshipSummaryFilter) error { + if relationshipFilter == nil { + return fmt.Errorf("nil relationship summary filter") + } + + if relationshipFilter.Include == nil { + return nil + } + + for _, dim := range *relationshipFilter.Include { + if !dim.Valid() { + return fmt.Errorf("unknown include dimension %s", dim) + } + } + return nil +} diff --git a/models/meshmodel/registry/v1alpha3/relationship_summary_test.go b/models/meshmodel/registry/v1alpha3/relationship_summary_test.go index 57c603f5..848e469f 100644 --- a/models/meshmodel/registry/v1alpha3/relationship_summary_test.go +++ b/models/meshmodel/registry/v1alpha3/relationship_summary_test.go @@ -12,31 +12,40 @@ import ( "github.com/stretchr/testify/require" ) +type relationshipSummaryRecord struct { + Id uuid.UUID `gorm:"column:id;primaryKey"` + Kind string `gorm:"column:kind"` + RelationshipType string `gorm:"column:type"` + SubType string `gorm:"column:sub_type"` + Status *relationship.RelationshipDefinitionStatus `gorm:"column:status"` + ModelId uuid.UUID `gorm:"column:model_id"` + Version string `gorm:"column:version"` +} + +func (relationshipSummaryRecord) TableName() string { + return "relationship_definition_dbs" +} + func TestRelationshipSummary_GetSummary(t *testing.T) { db := newRelationshipSummaryTestDB(t) seedRelationshipSummaryData(t, db) - f := &RelationshipSummaryFilter{ - Include: []RelationshipSummaryDimension{ - RelationshipSummaryByModel, - RelationshipSummaryByKind, - }, - } + include := []relationship.RelationshipSummaryFilterInclude{relationship.ByModel, relationship.ByKind} + f := &relationship.RelationshipSummaryFilter{Include: &include} - summary, err := f.GetSummary(db) + summary, err := GetSummary(f, db) require.NoError(t, err) require.Equal(t, int64(2), summary.Total) - require.Equal(t, map[string]int{"model-a": 1, "model-b": 1}, relationshipGroupToMap(summary.ByModel)) - require.Equal(t, map[string]int{"edge": 1, "hierarchy": 1}, relationshipGroupToMap(summary.ByKind)) + require.Equal(t, map[string]int32{"model-a": 1, "model-b": 1}, relationshipGroupToMap(summary.ByModel)) + require.Equal(t, map[string]int32{"edge": 1, "hierarchy": 1}, relationshipGroupToMap(summary.ByKind)) require.Empty(t, summary.ByType) require.Empty(t, summary.BySubType) } func TestRelationshipSummary_Validate(t *testing.T) { - f := &RelationshipSummaryFilter{ - Include: []RelationshipSummaryDimension{"invalid_dimension"}, - } - err := f.Validate() + include := []relationship.RelationshipSummaryFilterInclude{relationship.RelationshipSummaryFilterInclude("invalid_dimension")} + f := &relationship.RelationshipSummaryFilter{Include: &include} + _, err := GetSummary(f, newRelationshipSummaryTestDB(t)) require.Error(t, err) } @@ -55,7 +64,7 @@ func newRelationshipSummaryTestDB(t *testing.T) *database.Handler { err = h.AutoMigrate( &category.CategoryDefinition{}, &model.ModelDefinition{}, - &relationship.RelationshipDefinition{}, + &relationshipSummaryRecord{}, ) require.NoError(t, err) @@ -96,7 +105,7 @@ func seedRelationshipSummaryData(t *testing.T, db *database.Handler) { require.NoError(t, db.Create(&modelB).Error) relationshipStatus := relationship.RelationshipDefinitionStatus("enabled") - rel1 := relationship.RelationshipDefinition{ + rel1 := relationshipSummaryRecord{ Id: uuid.Must(uuid.NewV4()), Kind: "edge", RelationshipType: "binding", @@ -105,7 +114,7 @@ func seedRelationshipSummaryData(t *testing.T, db *database.Handler) { ModelId: modelA.Id, Version: "v1.0.0", } - rel2 := relationship.RelationshipDefinition{ + rel2 := relationshipSummaryRecord{ Id: uuid.Must(uuid.NewV4()), Kind: "hierarchy", RelationshipType: "binding", @@ -118,9 +127,15 @@ func seedRelationshipSummaryData(t *testing.T, db *database.Handler) { require.NoError(t, db.Create(&rel2).Error) } -func relationshipGroupToMap(rows []RelationshipGroupEntry) map[string]int { - out := make(map[string]int, len(rows)) - for _, row := range rows { +func relationshipGroupToMap(rows *[]struct { + Count int32 `json:"count" yaml:"count"` + Key string `json:"key" yaml:"key"` +}) map[string]int32 { + if rows == nil { + return map[string]int32{} + } + out := make(map[string]int32, len(*rows)) + for _, row := range *rows { out[row.Key] = row.Count } return out diff --git a/models/meshmodel/registry/v1beta1/component_summary.go b/models/meshmodel/registry/v1beta1/component_summary.go index eeda9d16..aa64400b 100644 --- a/models/meshmodel/registry/v1beta1/component_summary.go +++ b/models/meshmodel/registry/v1beta1/component_summary.go @@ -9,88 +9,42 @@ import ( "gorm.io/gorm" ) -type ComponentSummaryFilter struct { - ModelName string - CategoryName string - Version string - Status string - Annotations string - Registrant string - - Include []ComponentSummaryDimension -} - -type ComponentSummaryDimension string - -const ( - ComponentSummaryByModel ComponentSummaryDimension = "by_model" - ComponentSummaryByCategory ComponentSummaryDimension = "by_category" - ComponentSummaryByRegistrant ComponentSummaryDimension = "by_registrant" -) - -type ComponentGroupEntry struct { - Key string - Count int -} - -func (c ComponentGroupEntry) KeyValue() string { - return c.Key -} -func (c ComponentGroupEntry) CountValue() int { - return c.Count -} - -type ComponentSummary struct { - Total int64 - ByModel []ComponentGroupEntry - ByCategory []ComponentGroupEntry - ByRegistrant []ComponentGroupEntry -} - -func (componentSummaryFilter *ComponentSummaryFilter) Validate() error { - for _, dim := range componentSummaryFilter.Include { - switch dim { - case ComponentSummaryByModel, ComponentSummaryByCategory, ComponentSummaryByRegistrant: - // valid - default: - return fmt.Errorf("unknown include dimension %s", dim) - } - } - return nil -} -func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) (*ComponentSummary, error) { - if err := componentFilter.Validate(); err != nil { +func GetSummary(componentFilter *component.ComponentSummaryFilter, db *database.Handler) (*component.ComponentSummary, error) { + if err := validate(componentFilter); err != nil { return nil, err } - summary := &ComponentSummary{} + + summary := &component.ComponentSummary{} base := db.Model(&component.ComponentDefinition{}). Joins("JOIN model_dbs ON component_definition_dbs.model_id = model_dbs.id"). Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id"). Joins("JOIN connections ON connections.id = model_dbs.connection_id") componentStatus := "enabled" - if componentFilter.Status != "" { - componentStatus = componentFilter.Status + if componentFilter.Status != nil { + componentStatus = *componentFilter.Status } base = base.Where("component_definition_dbs.status = ?", componentStatus) - switch componentFilter.Annotations { - case "true": - base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = true") - case "false": - base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = false") + if componentFilter.Annotations != nil { + switch *componentFilter.Annotations { + case component.True: + base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = true") + case component.False: + base = base.Where("component_definition_dbs.metadata->>'isAnnotation' = false") + } } - if componentFilter.ModelName != "" && componentFilter.ModelName != "all" { - base = base.Where("model_dbs.name = ?", componentFilter.ModelName) + if componentFilter.ModelName != nil && *componentFilter.ModelName != "all" { + base = base.Where("model_dbs.name = ?", *componentFilter.ModelName) } - if componentFilter.CategoryName != "" { - base = base.Where("category_dbs.name = ?", componentFilter.CategoryName) + if componentFilter.CategoryName != nil { + base = base.Where("category_dbs.name = ?", *componentFilter.CategoryName) } - if componentFilter.Version != "" { - base = base.Where("model_dbs.model->>'version' = ?", componentFilter.Version) + if componentFilter.Version != nil { + base = base.Where("model_dbs.model->>'version' = ?", *componentFilter.Version) } - if componentFilter.Registrant != "" { - base = base.Where("connections.name = ?", componentFilter.Registrant) + if componentFilter.Registrant != nil { + base = base.Where("connections.name = ?", *componentFilter.Registrant) } if err := base.Session(&gorm.Session{}). @@ -98,31 +52,56 @@ func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) Count(&summary.Total).Error; err != nil { return nil, err } - // per dimension - shouldCompute := func(dim ComponentSummaryDimension) bool { - // compute all if no include dimension - if len(componentFilter.Include) == 0 { + + type groupEntry = struct { + Count int32 `json:"count" yaml:"count"` + Key string `json:"key" yaml:"key"` + } + + shouldCompute := func(dim component.ComponentSummaryFilterInclude) bool { + if componentFilter.Include == nil || len(*componentFilter.Include) == 0 { return true } - return slices.Contains(componentFilter.Include, dim) + return slices.Contains(*componentFilter.Include, dim) } + type dimensionInfo struct { - dim ComponentSummaryDimension + dim component.ComponentSummaryFilterInclude selectExpr string groupExpr string - receiver *[]ComponentGroupEntry + setRows func([]groupEntry) } dimensions := []dimensionInfo{ - {ComponentSummaryByModel, "model_dbs.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "model_dbs.name", &summary.ByModel}, - {ComponentSummaryByCategory, "model_dbs.category_id as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "model_dbs.category_id", &summary.ByCategory}, - {ComponentSummaryByRegistrant, "connections.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", "connections.name", &summary.ByRegistrant}, + { + dim: component.ByModel, + selectExpr: "model_dbs.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", + groupExpr: "model_dbs.name", + setRows: func(rows []groupEntry) { + summary.ByModel = &rows + }, + }, + { + dim: component.ByCategory, + selectExpr: "category_dbs.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", + groupExpr: "category_dbs.name", + setRows: func(rows []groupEntry) { + summary.ByCategory = &rows + }, + }, + { + dim: component.ByRegistrant, + selectExpr: "connections.name as Key, COUNT(DISTINCT(component_definition_dbs.id)) as Count", + groupExpr: "connections.name", + setRows: func(rows []groupEntry) { + summary.ByRegistrant = &rows + }, + }, } - // partial error is not tolerated so the populated summary should all be correct for _, d := range dimensions { if shouldCompute(d.dim) { - var rows []ComponentGroupEntry + var rows []groupEntry err := base.Session(&gorm.Session{}). Select(d.selectExpr). Group(d.groupExpr). @@ -130,9 +109,30 @@ func (componentFilter *ComponentSummaryFilter) GetSummary(db *database.Handler) if err != nil { return nil, err } - *d.receiver = rows + d.setRows(rows) } } return summary, nil } + +func validate(componentFilter *component.ComponentSummaryFilter) error { + if componentFilter == nil { + return fmt.Errorf("nil component summary filter") + } + + if componentFilter.Annotations != nil && !componentFilter.Annotations.Valid() { + return fmt.Errorf("unknown annotations value %s", *componentFilter.Annotations) + } + + if componentFilter.Include == nil { + return nil + } + + for _, dim := range *componentFilter.Include { + if !dim.Valid() { + return fmt.Errorf("unknown include dimension %s", dim) + } + } + return nil +} diff --git a/models/meshmodel/registry/v1beta1/component_summary_test.go b/models/meshmodel/registry/v1beta1/component_summary_test.go index f1442920..28a11a91 100644 --- a/models/meshmodel/registry/v1beta1/component_summary_test.go +++ b/models/meshmodel/registry/v1beta1/component_summary_test.go @@ -17,27 +17,22 @@ func TestComponentSummary_GetSummary(t *testing.T) { db := newComponentSummaryTestDB(t) seedComponentSummaryData(t, db) - f := &ComponentSummaryFilter{ - Include: []ComponentSummaryDimension{ - ComponentSummaryByModel, - ComponentSummaryByRegistrant, - }, - } + include := []component.ComponentSummaryFilterInclude{component.ByModel, component.ByCategory, component.ByRegistrant} + f := &component.ComponentSummaryFilter{Include: &include} - summary, err := f.GetSummary(db) + summary, err := GetSummary(f, db) require.NoError(t, err) require.Equal(t, int64(3), summary.Total) - require.Equal(t, map[string]int{"model-a": 2, "model-b": 1}, componentGroupToMap(summary.ByModel)) - require.Equal(t, map[string]int{"registrant-a": 2, "registrant-b": 1}, componentGroupToMap(summary.ByRegistrant)) - require.Empty(t, summary.ByCategory) + require.Equal(t, map[string]int32{"model-a": 2, "model-b": 1}, componentGroupToMap(summary.ByModel)) + require.Equal(t, map[string]int32{"infra": 3}, componentGroupToMap(summary.ByCategory)) + require.Equal(t, map[string]int32{"registrant-a": 2, "registrant-b": 1}, componentGroupToMap(summary.ByRegistrant)) } func TestComponentSummary_Validate(t *testing.T) { - f := &ComponentSummaryFilter{ - Include: []ComponentSummaryDimension{"unknown_dimension"}, - } - err := f.Validate() + include := []component.ComponentSummaryFilterInclude{component.ComponentSummaryFilterInclude("unknown_dimension")} + f := &component.ComponentSummaryFilter{Include: &include} + _, err := GetSummary(f, newComponentSummaryTestDB(t)) require.Error(t, err) } @@ -140,9 +135,15 @@ func seedComponentSummaryData(t *testing.T, db *database.Handler) { require.NoError(t, db.Create(&comp3).Error) } -func componentGroupToMap(rows []ComponentGroupEntry) map[string]int { - out := make(map[string]int, len(rows)) - for _, row := range rows { +func componentGroupToMap(rows *[]struct { + Count int32 `json:"count" yaml:"count"` + Key string `json:"key" yaml:"key"` +}) map[string]int32 { + if rows == nil { + return map[string]int32{} + } + out := make(map[string]int32, len(*rows)) + for _, row := range *rows { out[row.Key] = row.Count } return out From ab92b53a533dc3ede95008705f8ba4dd58d8d036 Mon Sep 17 00:00:00 2001 From: Amr-Shams Date: Tue, 3 Mar 2026 18:14:16 +0200 Subject: [PATCH 6/6] removing extra an non used types Signed-off-by: Amr-Shams --- models/meshmodel/entity/types.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/models/meshmodel/entity/types.go b/models/meshmodel/entity/types.go index ca502fe6..aee0c112 100644 --- a/models/meshmodel/entity/types.go +++ b/models/meshmodel/entity/types.go @@ -31,12 +31,3 @@ type Entity interface { GetID() uuid.UUID Create(db *database.Handler, hostID uuid.UUID) (entityID uuid.UUID, err error) } -type Summary interface { - KeyValue() string - CountValue() int -} - -// SummaryFilter is the interface for entities that expose aggregated summary data. -type SummaryFilter[T Summary] interface { - GetSummary(db *database.Handler) (*T, error) -}