Skip to content

Commit 290dc7d

Browse files
authored
Exclude profiles with labels from history. (#4910)
Filter by labels when listing history. History list showed evaluation results even for profiles having labels. This is different from the behaviour of Profile list and status, which by default only showed results for profiles without labels. This change makes the behaviour of History list closer to the one Profile list and status by adding a new filter to the `ListEvaluationHistory` endpoint. Both inclusion and exclusion are supported, and the additional `*` flag is allowed specifically for inclusion. Fixes stacklok/minder-stories#102
1 parent 220f695 commit 290dc7d

File tree

11 files changed

+1397
-966
lines changed

11 files changed

+1397
-966
lines changed

database/query/eval_history.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ SELECT s.id::uuid AS evaluation_id,
119119
ri.name AS rule_name,
120120
rt.severity_value as rule_severity,
121121
p.name AS profile_name,
122+
p.labels as profile_labels,
122123
-- evaluation status and details
123124
s.status AS evaluation_status,
124125
s.details AS evaluation_details,
@@ -158,6 +159,13 @@ SELECT s.id::uuid AS evaluation_id,
158159
AND (sqlc.narg(tots)::timestamp without time zone IS NULL OR s.evaluation_time < sqlc.narg(tots))
159160
-- implicit filter by project id
160161
AND j.id = sqlc.arg(projectId)
162+
-- implicit filter by profile labels
163+
AND ((sqlc.slice(labels)::text[] IS NULL AND p.labels = array[]::text[]) -- include only unlabelled records
164+
OR ((sqlc.slice(labels)::text[] IS NOT NULL AND sqlc.slice(labels)::text[] = array['*']::text[]) -- include all labels
165+
OR (sqlc.slice(labels)::text[] IS NOT NULL AND p.labels && sqlc.slice(labels)::text[]) -- include only specified labels
166+
)
167+
)
168+
AND (sqlc.slice(notLabels)::text[] IS NULL OR NOT p.labels && sqlc.slice(notLabels)::text[]) -- exclude only specified labels
161169
ORDER BY
162170
CASE WHEN sqlc.narg(next)::timestamp without time zone IS NULL THEN s.evaluation_time END ASC,
163171
CASE WHEN sqlc.narg(prev)::timestamp without time zone IS NULL THEN s.evaluation_time END DESC

docs/docs/ref/proto.md

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/controlplane/handlers_evalstatus.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ func (s *Server) ListEvaluationHistory(
131131
opts = append(opts, FilterOptsFromStrings(in.GetEntityType(), history.WithEntityType)...)
132132
opts = append(opts, FilterOptsFromStrings(in.GetEntityName(), history.WithEntityName)...)
133133
opts = append(opts, FilterOptsFromStrings(in.GetProfileName(), history.WithProfileName)...)
134+
opts = append(opts, FilterOptsFromStrings(in.GetLabelFilter(), history.WithLabel)...)
134135
opts = append(opts, FilterOptsFromStrings(in.GetStatus(), history.WithStatus)...)
135136
opts = append(opts, FilterOptsFromStrings(in.GetRemediation(), history.WithRemediation)...)
136137
opts = append(opts, FilterOptsFromStrings(in.GetAlert(), history.WithAlert)...)

internal/db/eval_history.sql.go

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/db/eval_history_test.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,34 @@ func TestListEvaluationHistoryFilters(t *testing.T) {
3636
ere1 := createRandomEvaluationRuleEntity(t, riID1, repo1.ID)
3737
es1 := createRandomEvaluationStatus(t, ere1)
3838

39+
// Evaluations for this profile should not show up in the
40+
// results.
41+
ruleType2 := createRandomRuleType(t, proj.ID)
42+
profile2 := createRandomProfile(t, proj.ID, []string{"label2"})
43+
fmt.Println(profile2)
44+
riID2 := createRandomRuleInstance(
45+
t,
46+
proj.ID,
47+
profile2.ID,
48+
ruleType2.ID,
49+
)
50+
ere2 := createRandomEvaluationRuleEntity(t, riID2, repo1.ID)
51+
es2 := createRandomEvaluationStatus(t, ere2)
52+
53+
// Evaluations for this profile should not show up in the
54+
// results.
55+
ruleType3 := createRandomRuleType(t, proj.ID)
56+
profile3 := createRandomProfile(t, proj.ID, []string{"label3"})
57+
fmt.Println(profile3)
58+
riID3 := createRandomRuleInstance(
59+
t,
60+
proj.ID,
61+
profile3.ID,
62+
ruleType3.ID,
63+
)
64+
ere3 := createRandomEvaluationRuleEntity(t, riID3, repo1.ID)
65+
es3 := createRandomEvaluationStatus(t, ere3)
66+
3967
tests := []struct {
4068
name string
4169
params ListEvaluationHistoryParams
@@ -392,6 +420,193 @@ func TestListEvaluationHistoryFilters(t *testing.T) {
392420
},
393421
},
394422

423+
// profile labels filter
424+
{
425+
name: "profile labels filter missing",
426+
params: ListEvaluationHistoryParams{
427+
Next: sql.NullTime{
428+
Time: time.UnixMicro(999999999999999999).UTC(),
429+
Valid: true,
430+
},
431+
Projectid: proj.ID,
432+
Size: 5,
433+
},
434+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
435+
t.Helper()
436+
require.Len(t, rows, 1)
437+
row := rows[0]
438+
require.Equal(t, es1, row.EvaluationID)
439+
require.Equal(t, EntitiesRepository, row.EntityType)
440+
require.Equal(t, repo1.ID, row.EntityID)
441+
require.Empty(t, row.ProfileLabels)
442+
},
443+
},
444+
{
445+
name: "profile labels filter include",
446+
params: ListEvaluationHistoryParams{
447+
Next: sql.NullTime{
448+
Time: time.UnixMicro(999999999999999999).UTC(),
449+
Valid: true,
450+
},
451+
Labels: []string{"nonexisting"},
452+
Projectid: proj.ID,
453+
Size: 5,
454+
},
455+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
456+
t.Helper()
457+
require.Len(t, rows, 0)
458+
},
459+
},
460+
{
461+
name: "profile labels filter include match label2",
462+
params: ListEvaluationHistoryParams{
463+
Next: sql.NullTime{
464+
Time: time.UnixMicro(999999999999999999).UTC(),
465+
Valid: true,
466+
},
467+
Labels: []string{"label2"},
468+
Projectid: proj.ID,
469+
Size: 5,
470+
},
471+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
472+
t.Helper()
473+
require.Len(t, rows, 1)
474+
row := rows[0]
475+
require.Equal(t, es2, row.EvaluationID)
476+
require.Equal(t, EntitiesRepository, row.EntityType)
477+
require.Equal(t, repo1.ID, row.EntityID)
478+
require.Equal(t, profile2.Labels, row.ProfileLabels)
479+
},
480+
},
481+
{
482+
name: "profile labels filter include match label3",
483+
params: ListEvaluationHistoryParams{
484+
Next: sql.NullTime{
485+
Time: time.UnixMicro(999999999999999999).UTC(),
486+
Valid: true,
487+
},
488+
Labels: []string{"label3"},
489+
Projectid: proj.ID,
490+
Size: 5,
491+
},
492+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
493+
t.Helper()
494+
require.Len(t, rows, 1)
495+
row := rows[0]
496+
require.Equal(t, es3, row.EvaluationID)
497+
require.Equal(t, EntitiesRepository, row.EntityType)
498+
require.Equal(t, repo1.ID, row.EntityID)
499+
require.Equal(t, profile3.Labels, row.ProfileLabels)
500+
},
501+
},
502+
{
503+
name: "profile labels filter match *",
504+
params: ListEvaluationHistoryParams{
505+
Next: sql.NullTime{
506+
Time: time.UnixMicro(999999999999999999).UTC(),
507+
Valid: true,
508+
},
509+
Labels: []string{"*"},
510+
Projectid: proj.ID,
511+
Size: 5,
512+
},
513+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
514+
t.Helper()
515+
require.Len(t, rows, 3)
516+
517+
row := rows[0]
518+
require.Equal(t, es3, row.EvaluationID)
519+
require.Equal(t, EntitiesRepository, row.EntityType)
520+
require.Equal(t, repo1.ID, row.EntityID)
521+
require.Equal(t, profile3.Labels, row.ProfileLabels)
522+
523+
row = rows[1]
524+
require.Equal(t, es2, row.EvaluationID)
525+
require.Equal(t, EntitiesRepository, row.EntityType)
526+
require.Equal(t, repo1.ID, row.EntityID)
527+
require.Equal(t, profile2.Labels, row.ProfileLabels)
528+
529+
row = rows[2]
530+
require.Equal(t, es1, row.EvaluationID)
531+
require.Equal(t, EntitiesRepository, row.EntityType)
532+
require.Equal(t, repo1.ID, row.EntityID)
533+
require.Equal(t, profile1.Labels, row.ProfileLabels)
534+
},
535+
},
536+
{
537+
name: "profile labels filter exclude label2",
538+
params: ListEvaluationHistoryParams{
539+
Next: sql.NullTime{
540+
Time: time.UnixMicro(999999999999999999).UTC(),
541+
Valid: true,
542+
},
543+
Notlabels: []string{"label2"},
544+
Projectid: proj.ID,
545+
Size: 5,
546+
},
547+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
548+
t.Helper()
549+
require.Len(t, rows, 1)
550+
551+
row := rows[0]
552+
require.Equal(t, es1, row.EvaluationID)
553+
require.Equal(t, EntitiesRepository, row.EntityType)
554+
require.Equal(t, repo1.ID, row.EntityID)
555+
require.Equal(t, profile1.Labels, row.ProfileLabels)
556+
},
557+
},
558+
{
559+
name: "profile labels filter exclude label3",
560+
params: ListEvaluationHistoryParams{
561+
Next: sql.NullTime{
562+
Time: time.UnixMicro(999999999999999999).UTC(),
563+
Valid: true,
564+
},
565+
Notlabels: []string{"label3"},
566+
Projectid: proj.ID,
567+
Size: 5,
568+
},
569+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
570+
t.Helper()
571+
require.Len(t, rows, 1)
572+
573+
row := rows[0]
574+
require.Equal(t, es1, row.EvaluationID)
575+
require.Equal(t, EntitiesRepository, row.EntityType)
576+
require.Equal(t, repo1.ID, row.EntityID)
577+
require.Equal(t, profile1.Labels, row.ProfileLabels)
578+
},
579+
},
580+
{
581+
name: "profile labels filter include * exclude label2",
582+
params: ListEvaluationHistoryParams{
583+
Next: sql.NullTime{
584+
Time: time.UnixMicro(999999999999999999).UTC(),
585+
Valid: true,
586+
},
587+
Labels: []string{"*"},
588+
Notlabels: []string{"label3"},
589+
Projectid: proj.ID,
590+
Size: 5,
591+
},
592+
checkf: func(t *testing.T, rows []ListEvaluationHistoryRow) {
593+
t.Helper()
594+
require.Len(t, rows, 2)
595+
596+
row := rows[0]
597+
require.Equal(t, es2, row.EvaluationID)
598+
require.Equal(t, EntitiesRepository, row.EntityType)
599+
require.Equal(t, repo1.ID, row.EntityID)
600+
require.Equal(t, profile2.Labels, row.ProfileLabels)
601+
602+
row = rows[1]
603+
require.Equal(t, es1, row.EvaluationID)
604+
require.Equal(t, EntitiesRepository, row.EntityType)
605+
require.Equal(t, repo1.ID, row.EntityID)
606+
require.Equal(t, profile1.Labels, row.ProfileLabels)
607+
},
608+
},
609+
395610
// time range filter
396611
{
397612
name: "time range filter from +1h",
@@ -671,6 +886,7 @@ func TestGetEvaluationHistory(t *testing.T) {
671886
for i := 0; i < 10; i++ {
672887
repos = append(repos, createRandomRepository(t, proj.ID, prov))
673888
}
889+
674890
ruleType1 := createRandomRuleType(t, proj.ID)
675891
profile1 := createRandomProfile(t, proj.ID, []string{})
676892
riID1 := createRandomRuleInstance(

0 commit comments

Comments
 (0)