Skip to content

Commit

Permalink
Add thresholds validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasz-dobek committed Aug 28, 2024
1 parent 1cc0282 commit deba8d2
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
2 changes: 1 addition & 1 deletion manifest/v1alpha/report/system_health_review.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type ReportThresholds struct {
RedLowerThanOrEqual *float64 `json:"redLte" validate:"required" example:"0.8"`
// Yellow is calculated as the difference between Red and Green
// thresholds. If Red and Green are the same, Yellow is not used on the report.
GreenGreaterThan *float64 `json:"greenGte" validate:"required" example:"0.95"`
GreenGreaterThan *float64 `json:"greenGt" validate:"required" example:"0.95"`
// ShowNoData customizes the report to either show or hide rows with no data.
ShowNoData bool `json:"noData"`
}
Expand Down
2 changes: 1 addition & 1 deletion manifest/v1alpha/report/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func validate(r Report) *v1alpha.ObjectError {
return v1alpha.ValidateObject(validator, r, manifest.KindReport)
}

var validator = validation.New(
var validator = validation.New[Report](
validationV1Alpha.FieldRuleAPIVersion(func(r Report) manifest.Version { return r.APIVersion }),
validationV1Alpha.FieldRuleKind(func(r Report) manifest.Kind { return r.Kind }, manifest.KindReport),
validation.For(func(r Report) Metadata { return r.Metadata }).
Expand Down
29 changes: 29 additions & 0 deletions manifest/v1alpha/report/validation_system_health_review.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ var systemHealthReviewValidation = validation.New[SystemHealthReviewConfig](
WithName("timeFrame").
Required().
Include(timeFrameValidation),
validation.For(func(s SystemHealthReviewConfig) ReportThresholds { return s.Thresholds }).
WithName("thresholds").
Required().
Include(reportThresholdsValidation),
)

var columnValidation = validation.New[ColumnSpec](
Expand Down Expand Up @@ -52,6 +56,31 @@ var timeFrameValidation = validation.New[SystemHealthReviewTimeFrame](
Include(snapshotTimeFrameLatestPointValidation),
)

var reportThresholdsValidation = validation.New[ReportThresholds](
validation.For(validation.GetSelf[ReportThresholds]()).
Rules(redLteValidation),
validation.ForPointer(func(s ReportThresholds) *float64 { return s.RedLowerThanOrEqual }).
WithName("redLte").
Required().
Rules(validation.GreaterThanOrEqualTo(0.0), validation.LessThanOrEqualTo(1.0)),
validation.ForPointer(func(s ReportThresholds) *float64 { return s.GreenGreaterThan }).
WithName("greenGt").
Required().
Rules(validation.GreaterThanOrEqualTo(0.0), validation.LessThanOrEqualTo(1.0)),
)

var redLteValidation = validation.NewSingleRule(func(v ReportThresholds) error {
if v.RedLowerThanOrEqual != nil && v.GreenGreaterThan != nil {
if *v.RedLowerThanOrEqual >= *v.GreenGreaterThan {
return validation.NewPropertyError(
"redLte",
v.RedLowerThanOrEqual,
errors.Errorf("must be less than or equal to 'greenGt' (%v)", *v.GreenGreaterThan))
}
}
return nil
})

var snapshotValidation = validation.New[SnapshotTimeFrame](
validation.For(func(s SnapshotTimeFrame) SnapshotPoint { return s.Point }).
WithName("point").
Expand Down
101 changes: 101 additions & 0 deletions manifest/v1alpha/report/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
Columns: []ColumnSpec{
{DisplayName: "Column 1", Labels: properLabel},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.0),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.2),
},
},
},
"fails with empty columns": {
Expand All @@ -530,6 +534,10 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
},
RowGroupBy: RowGroupByProject,
Columns: []ColumnSpec{},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.0),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.2),
},
},
},
"fails with too many columns": {
Expand Down Expand Up @@ -581,6 +589,10 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
{DisplayName: "Column 30", Labels: properLabel},
{DisplayName: "Column 31", Labels: properLabel},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.0),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.2),
},
},
},
"fails with empty labels": {
Expand All @@ -602,6 +614,10 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
Columns: []ColumnSpec{
{DisplayName: "Column 1", Labels: map[LabelKey][]LabelValue{}},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.0),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.2),
},
},
},
"fails with empty displayName": {
Expand All @@ -623,6 +639,60 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
Columns: []ColumnSpec{
{Labels: properLabel},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.0),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.2),
},
},
},
"fails with empty thresholds": {
ExpectedErrorsCount: 1,
ExpectedErrors: []testutils.ExpectedError{
{
Prop: "spec.systemHealthReview.thresholds",
Code: validation.ErrorCodeRequired,
},
},
Config: SystemHealthReviewConfig{
TimeFrame: SystemHealthReviewTimeFrame{
Snapshot: SnapshotTimeFrame{
Point: SnapshotPointLatest,
},
TimeZone: "Europe/Warsaw",
},
RowGroupBy: RowGroupByProject,
Columns: []ColumnSpec{
{DisplayName: "Column 1", Labels: properLabel},
},
},
},
"fails with invalid thresholds": {
ExpectedErrorsCount: 2,
ExpectedErrors: []testutils.ExpectedError{
{
Prop: "spec.systemHealthReview.thresholds.redLte",
Code: validation.ErrorCodeGreaterThanOrEqualTo,
},
{
Prop: "spec.systemHealthReview.thresholds.greenGt",
Code: validation.ErrorCodeLessThanOrEqualTo,
},
},
Config: SystemHealthReviewConfig{
TimeFrame: SystemHealthReviewTimeFrame{
Snapshot: SnapshotTimeFrame{
Point: SnapshotPointLatest,
},
TimeZone: "Europe/Warsaw",
},
RowGroupBy: RowGroupByProject,
Columns: []ColumnSpec{
{DisplayName: "Column 1", Labels: properLabel},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(-0.1),
GreenGreaterThan: func(f float64) *float64 { return &f }(1.1),
},
},
},
} {
Expand All @@ -633,6 +703,32 @@ func TestValidate_Spec_SystemHealthReview(t *testing.T) {
testutils.AssertContainsErrors(t, report, err, test.ExpectedErrorsCount, test.ExpectedErrors...)
})
}

t.Run("fails when red is greater than green", func(t *testing.T) {
report := validReport()
report.Spec.SystemHealthReview = &SystemHealthReviewConfig{
TimeFrame: SystemHealthReviewTimeFrame{
Snapshot: SnapshotTimeFrame{
Point: SnapshotPointLatest,
},
TimeZone: "Europe/Warsaw",
},
RowGroupBy: RowGroupByProject,
Columns: []ColumnSpec{
{DisplayName: "Column 1", Labels: properLabel},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.2),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.1),
},
}
err := validate(report)
testutils.AssertContainsErrors(t, report, err, 1, testutils.ExpectedError{
Prop: "spec.systemHealthReview.thresholds.redLte",
Message: "must be less than or equal to 'greenGt' (0.1)",
},
)
})
}

func TestValidate_Spec_SystemHealthReview_TimeFrame(t *testing.T) {
Expand Down Expand Up @@ -852,6 +948,11 @@ func validReport() Report {
},
},
},
Thresholds: ReportThresholds{
RedLowerThanOrEqual: func(f float64) *float64 { return &f }(0.8),
GreenGreaterThan: func(f float64) *float64 { return &f }(0.95),
ShowNoData: true,
},
},
},
}
Expand Down

0 comments on commit deba8d2

Please sign in to comment.