diff --git a/functions/drop.sql b/functions/drop.sql index 208efb3c..6f094878 100644 --- a/functions/drop.sql +++ b/functions/drop.sql @@ -3,5 +3,3 @@ DROP VIEW IF EXISTS configs CASCADE; DROP VIEW IF EXISTS config_detail CASCADE; DROP VIEW IF EXISTS config_tags CASCADE; - -DROP VIEW IF EXISTS topology CASCADE; \ No newline at end of file diff --git a/models/components.go b/models/components.go index bae209d7..ff808440 100644 --- a/models/components.go +++ b/models/components.go @@ -73,6 +73,10 @@ type Component struct { // ConfigID is the id of the config from which this component is derived ConfigID *uuid.UUID `json:"config_id,omitempty"` + // StatusExpr allows defining a cel expression to evaluate the status of a component + // based on the summary. + StatusExpr string `json:"status_expr,omitempty" gorm:"column:status_expr;default:null"` + // HealthExpr allows defining a cel expression to evaluate the health of a component // based on the summary. HealthExpr string `json:"health_expr,omitempty" gorm:"column:health_expr;default:null"` @@ -158,6 +162,22 @@ func (c *Component) ObjectMeta() metav1.ObjectMeta { } } +func (c Component) GetStatus() (string, error) { + if c.StatusExpr != "" { + env := map[string]any{ + "summary": c.Summary.AsEnv(), + } + out, err := gomplate.RunTemplate(env, gomplate.Template{Expression: c.StatusExpr}) + if err != nil { + return "", fmt.Errorf("failed to evaluate status expression %s: %v", c.StatusExpr, err) + } + + return out, nil + } + + return string(c.Status), nil +} + func (c Component) GetHealth() (string, error) { if c.HealthExpr != "" { env := map[string]any{ @@ -184,10 +204,6 @@ func (c Component) GetHealth() (string, error) { } } -func (c Component) GetStatus() (string, error) { - return string(c.Status), nil -} - func (c *Component) AsMap(removeFields ...string) map[string]any { return asMap(c, removeFields...) } @@ -258,6 +274,7 @@ func (component Component) Clone() Component { ExternalId: component.ExternalId, Schedule: component.Schedule, Health: component.Health, + StatusExpr: component.StatusExpr, HealthExpr: component.HealthExpr, } diff --git a/query/topology.go b/query/topology.go index a5c9b9cc..116c3ad9 100644 --- a/query/topology.go +++ b/query/topology.go @@ -11,6 +11,7 @@ import ( "github.com/flanksource/commons/collections" "github.com/flanksource/duty/context" "github.com/flanksource/duty/models" + "github.com/flanksource/duty/types" "github.com/jackc/pgx/v5" gocache "github.com/patrickmn/go-cache" "github.com/samber/lo" @@ -32,7 +33,7 @@ func (opt TopologyOptions) selectClause() string { } // parents & (incidents, analysis, checks) columns need to fetched to create the topology tree even though they may not be essential to the UI. - return "name, namespace, id, is_leaf, status, health_expr, status_reason, icon, summary, topology_type, labels, team_names, type, parent_id, parents, incidents, analysis, checks" + return "name, namespace, id, is_leaf, status, status_expr, health_expr, status_reason, icon, summary, topology_type, labels, team_names, type, parent_id, parents, incidents, analysis, checks" } func (opt TopologyOptions) componentWhereClause() string { @@ -397,6 +398,12 @@ func generateTree(components models.Components, compChildrenMap map[string]model c.Health = lo.ToPtr(models.Health(health)) } + if status, err := c.GetStatus(); err != nil { + return nil, err + } else { + c.Status = types.ComponentStatus(status) + } + nodes = append(nodes, c) } diff --git a/schema/components.hcl b/schema/components.hcl index 9e119a9b..705901ee 100644 --- a/schema/components.hcl +++ b/schema/components.hcl @@ -216,6 +216,10 @@ table "components" { null = false type = text } + column "status_expr" { + null = true + type = text + } column "health_expr" { null = true type = text @@ -366,9 +370,9 @@ table "components" { } index "components_path_is_pushed_idx" { on { - expr = "length(path)" + expr = "length(path)" } - where = "is_pushed IS FALSE" + where = "is_pushed IS FALSE" } index "idx_components_deleted_at" { columns = [column.deleted_at] diff --git a/views/006_config_views.sql b/views/006_config_views.sql index fa7aaf53..8e680c25 100644 --- a/views/006_config_views.sql +++ b/views/006_config_views.sql @@ -513,6 +513,7 @@ END; $$ LANGUAGE plpgsql; + CREATE OR REPLACE FUNCTION drop_config_items(ids text[]) RETURNS void as $$ BEGIN ALTER TABLE config_items diff --git a/views/010_topology.sql b/views/010_topology.sql index b7691df0..4280fca9 100644 --- a/views/010_topology.sql +++ b/views/010_topology.sql @@ -196,7 +196,8 @@ CREATE OR REPLACE VIEW incident_summary_by_component AS SELECT id, jsonb_object_agg(key, value) as incidents FROM (select id, json_object_agg(type,json) incidents from type_summary group by id, type) i, json_each(incidents) group by id; -- Topology view -CREATE OR REPLACE VIEW topology AS +CREATE OR REPLACE VIEW + topology AS WITH children AS ( SELECT