Skip to content

Commit

Permalink
feat: process topology query in duty
Browse files Browse the repository at this point in the history
  • Loading branch information
yashmehrotra authored and moshloop committed Feb 28, 2023
1 parent 2b33e23 commit eeb9a90
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 21 deletions.
8 changes: 4 additions & 4 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import (

"github.com/flanksource/commons/logger"
"github.com/flanksource/duty/migrate"
"github.com/jackc/pgx/v4/log/logrusadapter"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/sirupsen/logrus"
gormpostgres "gorm.io/driver/postgres"
"gorm.io/gorm"
Expand Down Expand Up @@ -93,9 +92,10 @@ func NewPgxPool(connection string) (*pgxpool.Pool, error) {
ExitFunc: os.Exit,
ReportCaller: false,
}
config.ConnConfig.Logger = logrusadapter.NewLogger(logrusLogger)
_ = logrusLogger
//config.ConnConfig.Logger = logrusadapter.NewLogger(logrusLogger)
}
pool, err = pgxpool.ConnectConfig(context.Background(), config)
pool, err = pgxpool.NewWithConfig(context.Background(), config)
if err != nil {
return nil, err
}
Expand Down
39 changes: 31 additions & 8 deletions models/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import (

type Components []*Component

type ComponentStatus string

const (
ComponentStatusHealthy ComponentStatus = "healthy"
ComponentStatusUnhealthy ComponentStatus = "unhealthy"
ComponentStatusWarning ComponentStatus = "warning"
ComponentStatusError ComponentStatus = "error"
ComponentStatusInfo ComponentStatus = "info"
)

type Component struct {
ID uuid.UUID `json:"id,omitempty" gorm:"default:generate_ulid()"` //nolint
SystemTemplateID *uuid.UUID `json:"system_template_id,omitempty"`
Expand Down Expand Up @@ -51,19 +61,32 @@ type Component struct {
CostTotal7d float64 `json:"cost_total_7d,omitempty" gorm:"column:cost_total_7d"`
CostTotal30d float64 `json:"cost_total_30d,omitempty" gorm:"column:cost_total_30d"`
CreatedBy *uuid.UUID `json:"created_by,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty" time_format:"postgres_timestamp"`
UpdatedAt time.Time `json:"updated_at,omitempty" time_format:"postgres_timestamp"`
CreatedAt LocalTime `json:"created_at,omitempty" time_format:"postgres_timestamp" gorm:"default:CURRENT_TIMESTAMP()"`
UpdatedAt LocalTime `json:"updated_at,omitempty" time_format:"postgres_timestamp" gorm:"default:CURRENT_TIMESTAMP()"`
DeletedAt *time.Time `json:"deleted_at,omitempty" time_format:"postgres_timestamp" swaggerignore:"true"`

// Auxiliary fields
Checks Checks `json:"checks,omitempty" gorm:"-"`
Components Components `json:"components,omitempty" gorm:"-"`
Order int `json:"order,omitempty" gorm:"-"`
SelectorID string `json:"-" gorm:"-"`
Checks Checks `json:"checks,omitempty" gorm:"-"`
Components []Component `json:"components,omitempty" gorm:"-"`
Order int `json:"order,omitempty" gorm:"-"`
SelectorID string `json:"-" gorm:"-"`
RelationshipID *uuid.UUID `json:"relationship_id,omitempty" gorm:"-"`
}

func (c Component) GetStatus() ComponentStatus {
if c.Summary.Healthy > 0 && c.Summary.Unhealthy > 0 {
return ComponentStatusWarning
} else if c.Summary.Unhealthy > 0 {
return ComponentStatusUnhealthy
} else if c.Summary.Warning > 0 {
return ComponentStatusWarning
} else if c.Summary.Healthy > 0 {
return ComponentStatusHealthy
} else {
return ComponentStatusInfo
}
}

type ComponentStatus string

type Text struct {
Tooltip string `json:"tooltip,omitempty"`
Icon string `json:"icon,omitempty"`
Expand Down
46 changes: 46 additions & 0 deletions models/local_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package models

import (
"database/sql/driver"
"time"
)

const TimeFormat = "2006-01-02T15:04:05"

type LocalTime time.Time

func (t *LocalTime) UnmarshalJSON(data []byte) (err error) {
if len(data) == 2 {
*t = LocalTime(time.Time{})
return
}

now, err := time.Parse(`"`+TimeFormat+`"`, string(data))
*t = LocalTime(now)
return
}

func (t LocalTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(TimeFormat)+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, TimeFormat)
b = append(b, '"')
return b, nil
}

func (t LocalTime) Value() (driver.Value, error) {
if t.String() == "0001-01-01 00:00:00" {
return nil, nil
}
return []byte(time.Time(t).Format(TimeFormat)), nil
}

func (t *LocalTime) Scan(v interface{}) error {
tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String())
*t = LocalTime(tTime)
return nil
}

func (t LocalTime) String() string {
return time.Time(t).Format(TimeFormat)
}
101 changes: 92 additions & 9 deletions topology.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
package duty

import "fmt"
import (
"context"
"encoding/json"
"fmt"

"github.com/flanksource/duty/models"
"github.com/jackc/pgx/v5"
)

type TopologyOptions struct {
ID string `query:"id"`
Owner string `query:"owner"`
Labels map[string]string `query:"labels"`
ID string `query:"id"`
Owner string `query:"owner"`
Labels map[string]string `query:"labels"`
Flatten bool
}

func (opt TopologyOptions) String() string {
return fmt.Sprintf("%#v", opt)
}

func (opt TopologyOptions) componentWhereClause() string {
s := "where components.deleted_at is null "
s := "WHERE components.deleted_at IS NULL "
if opt.ID != "" {
s += `and (starts_with(path,
(SELECT
(CASE WHEN (path IS NULL OR path = '') THEN id :: text ELSE concat(path,'.', id) END)
FROM components where id = :id)
FROM components where id = @id)
) or id = :id or path = :id :: text)`
}
if opt.Owner != "" {
s += " AND (components.owner = :owner or id = :id)"
s += " AND (components.owner = @owner or id = @id)"
}
if opt.Labels != nil {
s += " AND (components.labels @> :labels"
s += " AND (components.labels @> @labels"
if opt.ID != "" {
s += " or id = :id"
s += " or id = @id"
}
s += ")"
}
Expand Down Expand Up @@ -106,3 +114,78 @@ func (opts TopologyOptions) configAnalysisSummaryForComponents() string {
func (p TopologyOptions) incidentSummaryForComponents() string {
return `(SELECT incidents FROM incident_summary_by_component WHERE id = topology_result.id)`
}

func QueryTopology() ([]models.Component, error) {
params := TopologyOptions{}
query, args := TopologyQuery(params)
rows, err := pool.Query(context.Background(), query, pgx.NamedArgs(args))
if err != nil {
return nil, err
}

var results []models.Component
for rows.Next() {
var components []models.Component
if rows.RawValues()[0] == nil {
continue
}

if err := json.Unmarshal(rows.RawValues()[0], &components); err != nil {
return nil, fmt.Errorf("failed to unmarshal components:%v for %s", err, rows.RawValues()[0])
}
results = append(results, components...)
}

for _, c := range results {
c.Status = c.GetStatus()
}

if !params.Flatten {
results = createComponentTree(results)
}
return results, nil
}

func tree(cs []models.Component, compChildrenMap map[string][]models.Component) []models.Component {
var root []models.Component
for _, c := range cs {
if children, exists := compChildrenMap[c.ID.String()]; exists {
c.Components = tree(children, compChildrenMap)
}
root = append(root, c)
}
return root
}

func createComponentTree(cs []models.Component) []models.Component {
// ComponentID with its component
compMap := make(map[string]models.Component)
// ComponentID with its children
compChildrenMap := make(map[string][]models.Component)

for _, c := range cs {
compMap[c.ID.String()] = c
compChildrenMap[c.ID.String()] = []models.Component{}
}
for _, c := range cs {
if c.ParentId != nil {
if _, exists := compChildrenMap[c.ParentId.String()]; exists {
compChildrenMap[c.ParentId.String()] = append(compChildrenMap[c.ParentId.String()], c)
}
}
if c.RelationshipID != nil {
if _, exists := compChildrenMap[c.RelationshipID.String()]; exists {
compChildrenMap[c.RelationshipID.String()] = append(compChildrenMap[c.RelationshipID.String()], c)
}
}
}

ctree := tree(cs, compChildrenMap)
var final []models.Component
for _, c := range ctree {
if c.ParentId == nil {
final = append(final, c)
}
}
return final
}

0 comments on commit eeb9a90

Please sign in to comment.