Skip to content

Commit b0f2168

Browse files
committed
feat(query): add grouped summary logging for query results
Implement QueryLogSummary interface to provide category-based summaries in query logs. Adds methods to multiple models (ConfigAnalysis, ConfigAccessSummary, ConfigChangeRow, RelatedConfig) and creates groupedSummary function to display counts by category (e.g. "CodeDeployment: 5, BackupCompleted: 3"). Also improves error handling in config tree resolution and adds embedded postgres cleanup hook.
1 parent 27a19bb commit b0f2168

8 files changed

Lines changed: 96 additions & 11 deletions

File tree

models/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,10 @@ func (a ConfigAnalysis) PK() string {
974974
return a.ID.String()
975975
}
976976

977+
func (a ConfigAnalysis) QueryLogSummary() string {
978+
return string(a.AnalysisType) + "/" + a.Analyzer
979+
}
980+
977981
func (a ConfigAnalysis) TableName() string {
978982
return "config_analysis"
979983
}

models/config_access.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ type ConfigAccessSummary struct {
237237
LastReviewedBy *uuid.UUID `json:"last_reviewed_by,omitempty"`
238238
}
239239

240+
func (e ConfigAccessSummary) QueryLogSummary() string {
241+
return e.ConfigType
242+
}
243+
240244
func (e ConfigAccessSummary) TableName() string {
241245
return "config_access_summary"
242246
}

query/catalog_search.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,13 @@ func (b *BaseCatalogSearch) String() string {
174174
}
175175
if b.From != "" {
176176
s += fmt.Sprintf("from=%s ", b.From)
177+
} else if b.FromTime != nil {
178+
s += fmt.Sprintf("from=%s ", b.FromTime.Format("2006-01-02"))
177179
}
178180
if b.To != "" {
179181
s += fmt.Sprintf("to=%s ", b.To)
182+
} else if b.ToTime != nil {
183+
s += fmt.Sprintf("to=%s ", b.ToTime.Format("2006-01-02"))
180184
}
181185
if b.Recursive != "" {
182186
s += fmt.Sprintf("recursive=%s ", b.Recursive)

query/config_changes.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ type ConfigChangeRow struct {
149149
InsertedAt *time.Time `gorm:"column:inserted_at" json:"inserted_at,omitempty"`
150150
}
151151

152+
func (r ConfigChangeRow) QueryLogSummary() string {
153+
return r.ChangeType
154+
}
155+
152156
type CatalogChangesSearchResponse struct {
153157
Summary map[string]int `json:"summary,omitempty"`
154158
Total int64 `json:"total,omitempty"`
@@ -164,7 +168,6 @@ func (t *CatalogChangesSearchResponse) Summarize() {
164168

165169
func formSeverityQuery(severity string) string {
166170
if strings.HasPrefix(severity, "!") {
167-
// For `Not` queries, we don't need to make any changes.
168171
return severity
169172
}
170173

@@ -177,13 +180,19 @@ func formSeverityQuery(severity string) string {
177180
}
178181

179182
var applicable []string
183+
found := false
180184
for _, s := range severities {
181185
applicable = append(applicable, string(s))
182186
if string(s) == severity {
187+
found = true
183188
break
184189
}
185190
}
186191

192+
if !found {
193+
return "__invalid__"
194+
}
195+
187196
return strings.Join(applicable, ",")
188197
}
189198

query/config_relations.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
func (r RelatedConfig) QueryLogSummary() string {
15-
return r.Type + "/" + r.Name
15+
return r.Type
1616
}
1717

1818
type RelatedConfig struct {

query/config_tree.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package query
22

33
import (
4+
"fmt"
45
"strings"
56

67
"github.com/flanksource/duty/context"
@@ -22,6 +23,25 @@ type ConfigTreeOptions struct {
2223
Outgoing RelationType
2324
}
2425

26+
func (n *ConfigTreeNode) OutgoingIDs() []uuid.UUID {
27+
var ids []uuid.UUID
28+
n.collectOutgoing(&ids, make(map[uuid.UUID]bool))
29+
return ids
30+
}
31+
32+
func (n *ConfigTreeNode) collectOutgoing(ids *[]uuid.UUID, seen map[uuid.UUID]bool) {
33+
if seen[n.ID] {
34+
return
35+
}
36+
seen[n.ID] = true
37+
if n.EdgeType != "parent" {
38+
*ids = append(*ids, n.ID)
39+
}
40+
for _, c := range n.Children {
41+
c.collectOutgoing(ids, seen)
42+
}
43+
}
44+
2545
func ConfigTree(ctx context.Context, configID uuid.UUID, opts ConfigTreeOptions) (*ConfigTreeNode, error) {
2646
config, err := GetCachedConfig(ctx, configID.String())
2747
if err != nil {
@@ -31,7 +51,10 @@ func ConfigTree(ctx context.Context, configID uuid.UUID, opts ConfigTreeOptions)
3151
return nil, nil
3252
}
3353

34-
parents := resolveParentsFromPath(ctx, config)
54+
parents, err := resolveParentsFromPath(ctx, config)
55+
if err != nil {
56+
return nil, err
57+
}
3558

3659
childIDs, err := ExpandConfigChildren(ctx, []uuid.UUID{config.ID})
3760
if err != nil {
@@ -54,7 +77,7 @@ func ConfigTree(ctx context.Context, configID uuid.UUID, opts ConfigTreeOptions)
5477
if opts.Incoming != "" {
5578
relType = opts.Incoming
5679
}
57-
outType := Hard
80+
outType := Both
5881
if opts.Outgoing != "" {
5982
outType = opts.Outgoing
6083
}
@@ -72,9 +95,9 @@ func ConfigTree(ctx context.Context, configID uuid.UUID, opts ConfigTreeOptions)
7295
return buildConfigTree(config, parents, children, related), nil
7396
}
7497

75-
func resolveParentsFromPath(ctx context.Context, config *models.ConfigItem) []models.ConfigItem {
98+
func resolveParentsFromPath(ctx context.Context, config *models.ConfigItem) ([]models.ConfigItem, error) {
7699
if config.Path == "" {
77-
return nil
100+
return nil, nil
78101
}
79102
segments := strings.Split(config.Path, ".")
80103
var parentIDs []uuid.UUID
@@ -86,11 +109,14 @@ func resolveParentsFromPath(ctx context.Context, config *models.ConfigItem) []mo
86109
parentIDs = append(parentIDs, id)
87110
}
88111
if len(parentIDs) == 0 {
89-
return nil
112+
return nil, nil
90113
}
91114
items, err := GetConfigsByIDs(ctx, parentIDs)
92-
if err != nil || len(items) == 0 {
93-
return nil
115+
if err != nil {
116+
return nil, fmt.Errorf("resolving parents from path: %w", err)
117+
}
118+
if len(items) == 0 {
119+
return nil, nil
94120
}
95121
byID := make(map[uuid.UUID]models.ConfigItem, len(items))
96122
for _, ci := range items {
@@ -102,7 +128,7 @@ func resolveParentsFromPath(ctx context.Context, config *models.ConfigItem) []mo
102128
parents = append(parents, ci)
103129
}
104130
}
105-
return parents
131+
return parents, nil
106132
}
107133

108134
func ExpandConfigChildren(ctx context.Context, ids []uuid.UUID) ([]uuid.UUID, error) {

query/query_logger.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ func (q QueryLogger) Start(entity string) *QueryTimer {
4040
}
4141

4242
func (t *QueryTimer) Arg(key string, value any) *QueryTimer {
43+
s := fmt.Sprintf("%v", value)
44+
if len(s) > 80 {
45+
s = s[:77] + "..."
46+
}
4347
t.label = t.label.AddText(fmt.Sprintf(" %s=", key), "text-gray-500").
44-
AddText(fmt.Sprintf("%v", value))
48+
AddText(s)
4549
return t
4650
}
4751

@@ -101,6 +105,11 @@ func summaryText(v any, count int) string {
101105
return ""
102106
}
103107

108+
// Try grouped summary first (e.g. "CodeDeployment: 5, BackupCompleted: 3")
109+
if grouped := groupedSummary(rv); grouped != "" {
110+
return " [" + grouped + "]"
111+
}
112+
104113
const maxInline = 2
105114
shown := count
106115
if shown > maxInline {
@@ -117,3 +126,27 @@ func summaryText(v any, count int) string {
117126
}
118127
return " [" + summary + "]"
119128
}
129+
130+
func groupedSummary(rv reflect.Value) string {
131+
if rv.Len() == 0 {
132+
return ""
133+
}
134+
first := rv.Index(0).Interface()
135+
if _, ok := first.(QueryLogSummary); !ok {
136+
return ""
137+
}
138+
counts := make(map[string]int)
139+
var order []string
140+
for i := 0; i < rv.Len(); i++ {
141+
s := rv.Index(i).Interface().(QueryLogSummary).QueryLogSummary()
142+
if counts[s] == 0 {
143+
order = append(order, s)
144+
}
145+
counts[s]++
146+
}
147+
var parts []string
148+
for _, key := range order {
149+
parts = append(parts, fmt.Sprintf("%s: %d", key, counts[key]))
150+
}
151+
return strings.Join(parts, ", ")
152+
}

tests/setup/template.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ func ensurePostgres(port int) (string, error) {
234234
if err := postgresServer.Start(); err != nil {
235235
return "", err
236236
}
237+
shutdown.AddHookWithPriority("stop embedded postgres", shutdown.PriorityCritical, func() {
238+
if err := postgresServer.Stop(); err != nil {
239+
logger.Errorf("failed to stop embedded postgres: %v", err)
240+
}
241+
})
237242
logger.Infof("Started postgres on port %d", port)
238243
}
239244

0 commit comments

Comments
 (0)