From 0dd6b024fb7e91bbf25a8471767612ba03498514 Mon Sep 17 00:00:00 2001 From: Arthur Pitman Date: Tue, 9 Aug 2022 13:06:01 +0200 Subject: [PATCH] Generate better error messages Signed-off-by: Arthur Pitman --- .../sli/dashboard/data_explorer_thresholds.go | 282 ++++++++++++------ ...trics_from_dashboard_data_explorer_test.go | 165 ++++------ ...ard_data_explorer_threshold_errors_test.go | 232 ++++++++++++++ .../dashboard_invalid_color_1.json | 119 -------- .../dashboard_invalid_color_2.json | 119 -------- .../dashboard_invalid_color_3.json | 119 -------- .../dashboard_invalid_sequence_no_fail.json | 119 -------- .../dashboard_invalid_sequence_two_fail.json | 119 -------- .../dashboard_invalid_sequence_two_pass.json | 119 -------- .../dashboard_invalid_sequence_two_warn.json | 119 -------- ...son => dashboard_thresholds_template.json} | 14 +- .../dashboard_too_many_rules.json | 123 -------- ...sible_thresholds_with_invalid_values.json} | 8 +- ...visible_thresholds_with_valid_values.json} | 6 +- ...rd_visible_thresholds_without_values.json} | 3 - .../dashboard.json} | 0 .../templating_payload_based_url_handler.go | 4 +- 17 files changed, 490 insertions(+), 1180 deletions(-) create mode 100644 internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_threshold_errors_test.go delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_1.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_2.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_3.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_fail.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_fail.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_pass.json delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_warn.json rename internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/{dashboard_too_few_rules.json => dashboard_thresholds_template.json} (90%) delete mode 100644 internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_many_rules.json rename internal/sli/testdata/dashboards/data_explorer/{tile_thresholds_errors/dashboard_invalid_sequence_descending_ascending.json => tile_thresholds_success/dashboard_not_visible_thresholds_with_invalid_values.json} (96%) rename internal/sli/testdata/dashboards/data_explorer/{tile_thresholds_errors/dashboard_invalid_sequence_ascending_descending.json => tile_thresholds_success/dashboard_not_visible_thresholds_with_valid_values.json} (98%) rename internal/sli/testdata/dashboards/data_explorer/{tile_thresholds_errors/dashboard_invalid_sequence_no_warn.json => tile_thresholds_success/dashboard_visible_thresholds_without_values.json} (96%) rename internal/sli/testdata/dashboards/data_explorer/{tile_thresholds_errors/dashboard_unit_transform_millisecond.json => unit_transform_is_not_auto/dashboard.json} (100%) diff --git a/internal/sli/dashboard/data_explorer_thresholds.go b/internal/sli/dashboard/data_explorer_thresholds.go index aa646ecc1..d772c4123 100644 --- a/internal/sli/dashboard/data_explorer_thresholds.go +++ b/internal/sli/dashboard/data_explorer_thresholds.go @@ -3,10 +3,11 @@ package dashboard import ( "errors" "fmt" + "strings" "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" keptnapi "github.com/keptn/go-utils/pkg/lib" - "golang.org/x/exp/slices" + log "github.com/sirupsen/logrus" ) type passAndWarningCriteria struct { @@ -14,65 +15,134 @@ type passAndWarningCriteria struct { warning keptnapi.SLOCriteria } -var passColors = []string{ - "#006613", - "#1f7e1e", - "#5ead35", - "#7dc540", - "#9cd575", - "#e8f9dc", - "#048855", - "#009e60", - "#2ab06f", - "#54c27d", - "#99dea8", - "#e1f7dc", +type thresholdColor int + +const ( + unknownThresholdColor thresholdColor = 0 + passThresholdColor thresholdColor = 1 + warnThresholdColor thresholdColor = 2 + failThresholdColor thresholdColor = 3 +) + +type thresholdColorSequence int + +const ( + unknownColorSequence thresholdColorSequence = 0 + passWarnFailColorSequence thresholdColorSequence = 1 + failWarnPassColorSequence thresholdColorSequence = 2 +) + +var thresholdColors = map[string]thresholdColor{ + // pass colors + "#006613": passThresholdColor, + "#1f7e1e": passThresholdColor, + "#5ead35": passThresholdColor, + "#7dc540": passThresholdColor, + "#9cd575": passThresholdColor, + "#e8f9dc": passThresholdColor, + "#048855": passThresholdColor, + "#009e60": passThresholdColor, + "#2ab06f": passThresholdColor, + "#54c27d": passThresholdColor, + "#99dea8": passThresholdColor, + "#e1f7dc": passThresholdColor, + + // warn colors + "#ef651f": warnThresholdColor, + "#fd8232": warnThresholdColor, + "#ffa86c": warnThresholdColor, + "#ffd0ab": warnThresholdColor, + "#c9a000": warnThresholdColor, + "#e6be00": warnThresholdColor, + "#f5d30f": warnThresholdColor, + "#ffe11c": warnThresholdColor, + "#ffee7c": warnThresholdColor, + "#fff9d5": warnThresholdColor, + + // fail colors + "#93060e": failThresholdColor, + "#ab0c17": failThresholdColor, + "#c41425": failThresholdColor, + "#dc172a": failThresholdColor, + "#f28289": failThresholdColor, + "#ffeaea": failThresholdColor, +} + +func getColorType(c string) thresholdColor { + v, ok := thresholdColors[c] + if !ok { + return unknownThresholdColor + } + + return v +} + +func getColorTypeString(colorType thresholdColor) string { + switch colorType { + case passThresholdColor: + return "pass" + case warnThresholdColor: + return "warn" + case failThresholdColor: + return "fail" + } + return "unknown" +} + +type thresholdParsingErrors struct { + errors []error } -var warnColors = []string{ - "#ef651f", - "#fd8232", - "#ffa86c", - "#ffd0ab", - "#c9a000", - "#e6be00", - "#f5d30f", - "#ffe11c", - "#ffee7c", - "#fff9d5", +func (err *thresholdParsingErrors) Error() string { + var errStrings = make([]string, len(err.errors)) + for i, e := range err.errors { + errStrings[i] = e.Error() + } + return strings.Join(errStrings, "; ") +} + +type incorrectThresholdRuleCountError struct { + count int +} + +func (err *incorrectThresholdRuleCountError) Error() string { + return fmt.Sprintf("expected 3 rules rather than %d rules", err.count) +} + +type invalidThresholdColorError struct { + position int + color string } -var failColors = []string{ - "#93060e", - "#ab0c17", - "#c41425", - "#dc172a", - "#f28289", - "#ffeaea", +func (err *invalidThresholdColorError) Error() string { + return fmt.Sprintf("invalid color %s at position %d ", err.color, err.position) } -func isPassColor(color string) bool { - return slices.Contains(passColors, color) +type missingThresholdValueError struct { + position int } -func isPassRule(rule dynatrace.ThresholdRule) bool { - return isPassColor(rule.Color) && rule.Value != nil +func (err *missingThresholdValueError) Error() string { + return fmt.Sprintf("missing value at position %d ", err.position) } -func isWarnColor(color string) bool { - return slices.Contains(warnColors, color) +type strictlyMonotonicallyIncreasingConstraintError struct { + value1 float64 + value2 float64 } -func isWarnRule(rule dynatrace.ThresholdRule) bool { - return isWarnColor(rule.Color) && rule.Value != nil +func (err *strictlyMonotonicallyIncreasingConstraintError) Error() string { + return fmt.Sprintf("values (%f %f) must increase strictly monotonically", err.value1, err.value2) } -func isFailColor(color string) bool { - return slices.Contains(failColors, color) +type invalidThresholdColorSequenceError struct { + colorType1 thresholdColor + colorType2 thresholdColor + colorType3 thresholdColor } -func isFailRule(rule dynatrace.ThresholdRule) bool { - return isFailColor(rule.Color) && rule.Value != nil +func (err *invalidThresholdColorSequenceError) Error() string { + return fmt.Sprintf("invalid color sequence: %s %s %s", getColorTypeString(err.colorType1), getColorTypeString(err.colorType2), getColorTypeString(err.colorType3)) } // tryGetThresholdPassAndWarningCriteria tries to get pass and warning criteria defined using the thresholds placed on a Data Explorer tile. @@ -88,7 +158,7 @@ func tryGetThresholdPassAndWarningCriteria(tile *dynatrace.Tile) (*passAndWarnin } if len(visualConfig.Thresholds) > 1 { - return nil, errors.New("Too many threshold configurations") + return nil, errors.New("too many threshold configurations") } thresholdConfiguration := &visualConfig.Thresholds[0] @@ -117,56 +187,107 @@ func areThresholdsEnabled(threshold *dynatrace.Threshold) bool { // parseThresholds parses a dashboard threshold struct and returns pass and warning SLO criteria or an error. func parseThresholds(threshold *dynatrace.Threshold) (*passAndWarningCriteria, error) { if !threshold.Visible { + log.Error("parseThresholds should not be called for thresholds that are not visible") return nil, errors.New("threshold is not visible") } - if len(threshold.Rules) != 3 { - return nil, errors.New("expected 3 threshold rules") + err := validateThresholdRules(threshold.Rules) + if err != nil { + return nil, err } - for _, rule := range threshold.Rules { + return convertThresholdRulesToPassAndWarningCriteria(threshold.Rules) +} + +// validateThresholdRules checks that the threshold rules are complete or returns an error. +func validateThresholdRules(rules []dynatrace.ThresholdRule) error { + var errs []error + + if len(rules) != 3 { + // log this error as it may mean something has changed on the Data Explorer side + log.WithField("ruleCount", len(rules)).Error("Encountered unexpected number of threshold rules") + + errs = append(errs, &incorrectThresholdRuleCountError{count: len(rules)}) + } + + for i, rule := range rules { if rule.Value == nil { - return nil, errors.New("missing threshold value") + errs = append(errs, &missingThresholdValueError{position: i + 1}) } - if !(isPassColor(rule.Color) || isWarnColor(rule.Color) || isFailColor(rule.Color)) { - return nil, fmt.Errorf("invalid threshold color: %s", rule.Color) + if getColorType(rule.Color) == unknownThresholdColor { + errs = append(errs, &invalidThresholdColorError{color: rule.Color, position: i + 1}) } } - if criteria := tryParsePassWarnFailThresholdRules(threshold.Rules); criteria != nil { - return criteria, nil + if len(errs) > 0 { + return &thresholdParsingErrors{errors: errs} } - if criteria := tryParseFailWarnPassThresholdRules(threshold.Rules); criteria != nil { - return criteria, nil + return nil +} + +// convertThresholdRulesToPassAndWarningCriteria converts the threshold rules to SLO pass and warning criteria or returns an error. +// Note: assumes rules have passed validateThresholdRules +func convertThresholdRulesToPassAndWarningCriteria(rules []dynatrace.ThresholdRule) (*passAndWarningCriteria, error) { + var errs []error + + v1 := *rules[0].Value + v2 := *rules[1].Value + v3 := *rules[2].Value + + if v1 >= v2 { + errs = append(errs, &strictlyMonotonicallyIncreasingConstraintError{value1: v1, value2: v2}) } - return nil, errors.New("invalid threshold sequence") -} + if v2 >= v3 { + errs = append(errs, &strictlyMonotonicallyIncreasingConstraintError{value1: v2, value2: v3}) + } -// tryParsePassWarnFailThresholdRules tries to parse a pass-warn-fail dashboard threshold struct and returns pass and warning SLO criteria or nil. -func tryParsePassWarnFailThresholdRules(rules []dynatrace.ThresholdRule) *passAndWarningCriteria { - if len(rules) != 3 { - return nil + colorSequence, err := getThresholdColorSequence(rules) + if err != nil { + errs = append(errs, err) } - if !isPassRule(rules[0]) || !isWarnRule(rules[1]) || !isFailRule(rules[2]) { - return nil + if len(errs) > 0 { + return nil, &thresholdParsingErrors{errors: errs} } - passThreshold := *rules[0].Value - warnThreshold := *rules[1].Value - failThreshold := *rules[2].Value + switch colorSequence { + case passWarnFailColorSequence: + return convertPassWarnFailThresholdsToSLOCriteria(rules), nil + case failWarnPassColorSequence: + return convertFailWarnPassThresholdsToSLOCriteria(rules), nil + } - if passThreshold >= warnThreshold { - return nil + // log this error as this should never occur + log.Error("Encountered unexpected threshold color sequence") + return nil, errors.New("unable to generate SLO pass and warning criteria for color sequence") +} + +// getThresholdColorSequence returns the color sequence that the thresholds follow or an error. +// Note: assumes rules have passed validateThresholdRules +func getThresholdColorSequence(rules []dynatrace.ThresholdRule) (thresholdColorSequence, error) { + colorType1 := getColorType(rules[0].Color) + colorType2 := getColorType(rules[1].Color) + colorType3 := getColorType(rules[2].Color) + + if (colorType1 == passThresholdColor) && (colorType2 == warnThresholdColor) && (colorType3 == failThresholdColor) { + return passWarnFailColorSequence, nil } - if warnThreshold >= failThreshold { - return nil + if (colorType1 == failThresholdColor) && (colorType2 == warnThresholdColor) && (colorType3 == passThresholdColor) { + return failWarnPassColorSequence, nil } + return unknownColorSequence, &invalidThresholdColorSequenceError{colorType1: colorType1, colorType2: colorType2, colorType3: colorType3} +} + +func convertPassWarnFailThresholdsToSLOCriteria(rules []dynatrace.ThresholdRule) *passAndWarningCriteria { + passThreshold := *rules[0].Value + warnThreshold := *rules[1].Value + failThreshold := *rules[2].Value + return &passAndWarningCriteria{ pass: keptnapi.SLOCriteria{ Criteria: []string{ @@ -176,34 +297,17 @@ func tryParsePassWarnFailThresholdRules(rules []dynatrace.ThresholdRule) *passAn }, warning: keptnapi.SLOCriteria{ Criteria: []string{ + fmt.Sprintf(">=%f", passThreshold), fmt.Sprintf("<%f", failThreshold), }, }, } } -// tryParseFailWarnPassThresholdRules tries to parse a fail-warn-pass dashboard threshold struct and returns pass and warning SLO criteria or nil. -func tryParseFailWarnPassThresholdRules(rules []dynatrace.ThresholdRule) *passAndWarningCriteria { - if len(rules) != 3 { - return nil - } - - if !isFailRule(rules[0]) || !isWarnRule(rules[1]) || !isPassRule(rules[2]) { - return nil - } - - failThreshold := *rules[0].Value +func convertFailWarnPassThresholdsToSLOCriteria(rules []dynatrace.ThresholdRule) *passAndWarningCriteria { warnThreshold := *rules[1].Value passThreshold := *rules[2].Value - if failThreshold >= warnThreshold { - return nil - } - - if warnThreshold >= passThreshold { - return nil - } - return &passAndWarningCriteria{ pass: keptnapi.SLOCriteria{ Criteria: []string{ diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go index 280f1000d..e2bf1ccfd 100644 --- a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_test.go @@ -488,14 +488,7 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_ExcludedTile(t *testing.T) runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventSuccessAssertionsFunc, sliResultsAssertionsFuncs...) } -type tileThresholdsTemplateData struct { - TileTitle string - TileThresholds string -} - -// TestRetrieveMetricsFromDashboardDataExplorerTile_TileThreshold tests setting pass and warning criteria via thresholds on the tile. -// This is will result in a SLIResult with success, as this is supported. -// Here also the SLO is checked, including the display name, weight and key SLI. +// TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork tests that setting pass and warning criteria via thresholds on the tile works as expected. func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork(t *testing.T) { const testDataFolder = "./testdata/dashboards/data_explorer/tile_thresholds_success/" @@ -509,42 +502,37 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork(t *test expectedSLO *keptnapi.SLO }{ - { name: "Valid pass-warn-fail thresholds and no pass or warning defined in title", dashboardFilename: testDataFolder + "dashboard_just_thresholds_pass_warn_fail.json", - expectedSLO: &keptnapi.SLO{ - SLI: "srt", - DisplayName: "Service Response Time", - Pass: []*keptnapi.SLOCriteria{{Criteria: []string{fmt.Sprintf(">=%f", float64(0)), fmt.Sprintf("<%f", float64(68000))}}}, - Warning: []*keptnapi.SLOCriteria{{Criteria: []string{fmt.Sprintf("<%f", float64(69000))}}}, - Weight: 1, - KeySLI: false, - }, + expectedSLO: createExpectedServiceResponseTimeSLO(createBandSLOCriteria(0, 68000), createBandSLOCriteria(0, 69000)), }, { name: "Valid fail-warn-pass thresholds and no pass or warning defined in title", dashboardFilename: testDataFolder + "dashboard_just_thresholds_fail_warn_pass.json", - expectedSLO: &keptnapi.SLO{ - SLI: "srt", - DisplayName: "Service Response Time", - Pass: []*keptnapi.SLOCriteria{{Criteria: []string{fmt.Sprintf(">=%f", float64(69000))}}}, - Warning: []*keptnapi.SLOCriteria{{Criteria: []string{fmt.Sprintf(">=%f", float64(68000))}}}, - Weight: 1, - KeySLI: false, - }, + expectedSLO: createExpectedServiceResponseTimeSLO(createLowerBoundSLOCriteria(69000), createLowerBoundSLOCriteria(68000)), }, { name: "Pass or warning defined in title take precedence over valid thresholds ", dashboardFilename: testDataFolder + "dashboard_both_thresholds_and_pass_and_warning_in_title.json", - expectedSLO: &keptnapi.SLO{ - SLI: "srt", - DisplayName: "Service Response Time", - Pass: []*keptnapi.SLOCriteria{{Criteria: []string{"<70000"}}}, - Warning: []*keptnapi.SLOCriteria{{Criteria: []string{"<71000"}}}, - Weight: 1, - KeySLI: false, - }, + expectedSLO: createExpectedServiceResponseTimeSLO( + []*keptnapi.SLOCriteria{{Criteria: []string{"<70000"}}}, + []*keptnapi.SLOCriteria{{Criteria: []string{"<71000"}}}), + }, + { + name: "Visible thresholds with no values are ignored", + dashboardFilename: testDataFolder + "dashboard_visible_thresholds_without_values.json", + expectedSLO: createExpectedServiceResponseTimeSLO(nil, nil), + }, + { + name: "Not visible thresholds with valid values are ignored", + dashboardFilename: testDataFolder + "dashboard_not_visible_thresholds_with_valid_values.json", + expectedSLO: createExpectedServiceResponseTimeSLO(nil, nil), + }, + { + name: "Not visible thresholds with invalid values are ignored", + dashboardFilename: testDataFolder + "dashboard_not_visible_thresholds_with_invalid_values.json", + expectedSLO: createExpectedServiceResponseTimeSLO(nil, nil), }, } @@ -567,91 +555,38 @@ func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsWork(t *test } } -// TestRetrieveMetricsFromDashboardDataExplorerTile_TileThreshold tests setting pass and warning criteria via thresholds on the tile. -// This is will result in a SLIResult with success, as this is supported. -// Here also the SLO is checked, including the display name, weight and key SLI. -func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdsErrors(t *testing.T) { - const testDataFolder = "./testdata/dashboards/data_explorer/tile_thresholds_errors/" +// TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto tests that unit transforms other than auto are not allowed. +// This is will result in a SLIResult with failure, as this is not allowed. +func TestRetrieveMetricsFromDashboardDataExplorerTile_UnitTransformIsNotAuto(t *testing.T) { + handler := test.NewFileBasedURLHandler(t) + handler.AddExact(dynatrace.DashboardsPath+"/"+testDashboardID, "./testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json") - tests := []struct { - name string - dashboardFilename string - sliResultAssertionsFunc func(t *testing.T, actual sliResult) - }{ - { - name: "Too few rules in thresholds", - dashboardFilename: testDataFolder + "dashboard_too_few_rules.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "expected 3 threshold rules"), - }, - { - name: "Too many rules in thresholds", - dashboardFilename: testDataFolder + "dashboard_too_many_rules.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "expected 3 threshold rules"), - }, - { - name: "Invalid color in thresholds 1", - dashboardFilename: testDataFolder + "dashboard_invalid_color_1.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold color"), - }, - { - name: "Invalid color in thresholds 2", - dashboardFilename: testDataFolder + "dashboard_invalid_color_2.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold color"), - }, - { - name: "Invalid color in thresholds 3", - dashboardFilename: testDataFolder + "dashboard_invalid_color_3.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold color"), - }, - { - name: "Invalid ascending-descending sequence", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_ascending_descending.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid descending-ascending sequence", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_descending_ascending.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid sequence with no warn", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_no_warn.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid sequence with no fail", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_no_fail.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid sequence with two pass", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_two_pass.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid sequence with two warn", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_two_warn.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Invalid sequence with two fail", - dashboardFilename: testDataFolder + "dashboard_invalid_sequence_two_fail.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "invalid threshold sequence"), - }, - { - name: "Unit transform set to MilliSecond (not Auto)", - dashboardFilename: testDataFolder + "dashboard_unit_transform_millisecond.json", - sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", "must be set to 'Auto'"), - }, + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, createFailedSLIResultAssertionsFunc("srt", "must be set to 'Auto'")) +} + +func createExpectedServiceResponseTimeSLO(passCriteria []*keptnapi.SLOCriteria, warningCriteria []*keptnapi.SLOCriteria) *keptnapi.SLO { + return &keptnapi.SLO{ + SLI: "srt", + DisplayName: "Service Response Time", + Pass: passCriteria, + Warning: warningCriteria, + Weight: 1, + KeySLI: false, } +} - for _, thresholdTest := range tests { - t.Run(thresholdTest.name, func(t *testing.T) { +func createBandSLOCriteria(lowerBoundInclusive float64, upperBoundExclusive float64) []*keptnapi.SLOCriteria { + return []*keptnapi.SLOCriteria{{Criteria: []string{createGreaterThanOrEqualSLOCriterion(lowerBoundInclusive), createLessThanSLOCriterion(upperBoundExclusive)}}} +} - handler := test.NewFileBasedURLHandler(t) - handler.AddExact(dynatrace.DashboardsPath+"/"+testDashboardID, thresholdTest.dashboardFilename) +func createLowerBoundSLOCriteria(lowerBoundInclusive float64) []*keptnapi.SLOCriteria { + return []*keptnapi.SLOCriteria{{Criteria: []string{createGreaterThanOrEqualSLOCriterion(lowerBoundInclusive)}}} +} - runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, thresholdTest.sliResultAssertionsFunc) - }) - } +func createGreaterThanOrEqualSLOCriterion(v float64) string { + return fmt.Sprintf(">=%f", v) +} + +func createLessThanSLOCriterion(v float64) string { + return fmt.Sprintf("<%f", v) } diff --git a/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_threshold_errors_test.go b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_threshold_errors_test.go new file mode 100644 index 000000000..ea9fe676a --- /dev/null +++ b/internal/sli/get_sli_triggered_event_handler_retrieve_metrics_from_dashboard_data_explorer_threshold_errors_test.go @@ -0,0 +1,232 @@ +package sli + +import ( + "fmt" + "testing" + + "github.com/keptn-contrib/dynatrace-service/internal/dynatrace" + "github.com/keptn-contrib/dynatrace-service/internal/test" +) + +type dataExplorerThresholdErrorsTest struct { + name string + thresholdValues []*float64 + thresholdColors []string + sliResultAssertionsFunc func(t *testing.T, actual sliResult) +} + +type tileThresholdsTemplateData struct { + ThresholdValues []*float64 + ThresholdColors []string +} + +const ( + missingValueErrorSubstring = "missing value" + invalidColorErrorSubstring = "invalid color" + expected3RulesErrorSubstring = "expected 3 rules" + invalidColorSequenceErrorSubstring = "invalid color sequence" + expectedMonotonicallyIncreasingErrorSubstring = "must increase strictly monotonically" + atPosition1ErrorSubstring = "at position 1" + atPosition2ErrorSubstring = "at position 2" + atPosition3ErrorSubstring = "at position 3" +) + +const ( + invalidThresholdColor = "#14a8f5" + passThresholdColor = "#7dc540" + warnThresholdColor = "#f5d30f" + failThresholdColor = "#dc172a" +) + +var passWarnFailThresholdColors = []string{passThresholdColor, warnThresholdColor, failThresholdColor} + +var thresholdValue0 float64 = 0 +var thresholdValue68000 float64 = 68000 +var thresholdValue69000 float64 = 69000 + +var validThresholdValues = []*float64{&thresholdValue0, &thresholdValue68000, &thresholdValue69000} + +// TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdRuleParsingErrors tests that errors while parsing Data Explorer tile thresholds are generated as expected. +// Includes tests with multiple errors are these should all be included in the overall error message. +func TestRetrieveMetricsFromDashboardDataExplorerTile_TileThresholdRuleParsingErrors(t *testing.T) { + const testDataFolder = "./testdata/dashboards/data_explorer/tile_thresholds_errors/" + + tests := []struct { + name string + thresholdValues []*float64 + thresholdColors []string + sliResultAssertionsFunc func(t *testing.T, actual sliResult) + }{ + // Rule count + { + name: "Too few rules in thresholds", + thresholdValues: []*float64{&thresholdValue0, &thresholdValue69000}, + thresholdColors: []string{passThresholdColor, warnThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", expected3RulesErrorSubstring), + }, + { + name: "Too many rules in thresholds", + thresholdValues: []*float64{&thresholdValue0, &thresholdValue68000, &thresholdValue69000, &thresholdValue69000}, + thresholdColors: []string{passThresholdColor, warnThresholdColor, failThresholdColor, failThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", expected3RulesErrorSubstring), + }, + + // Missing values + createDataExplorerThresholdsErrorTestWithMissingValues("Missing value at position 1", nil, &thresholdValue68000, &thresholdValue69000, atPosition1ErrorSubstring), + createDataExplorerThresholdsErrorTestWithMissingValues("Missing value at position 2", &thresholdValue0, nil, &thresholdValue69000, atPosition2ErrorSubstring), + createDataExplorerThresholdsErrorTestWithMissingValues("Missing value at position 3", &thresholdValue0, &thresholdValue68000, nil, atPosition3ErrorSubstring), + createDataExplorerThresholdsErrorTestWithMissingValues("Missing values at position 1 and 2", nil, nil, &thresholdValue69000, atPosition1ErrorSubstring, atPosition2ErrorSubstring), + createDataExplorerThresholdsErrorTestWithMissingValues("Missing values at position 2 and 3", &thresholdValue0, nil, nil, atPosition2ErrorSubstring, atPosition3ErrorSubstring), + createDataExplorerThresholdsErrorTestWithMissingValues("Missing values at position 1 and 3", nil, &thresholdValue68000, nil, atPosition1ErrorSubstring, atPosition3ErrorSubstring), + + // Combined rule count and missing value + { + name: "Too many rules in thresholds and missing value", + thresholdValues: []*float64{&thresholdValue0, &thresholdValue68000, nil, &thresholdValue69000}, + thresholdColors: []string{passThresholdColor, warnThresholdColor, failThresholdColor, failThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", expected3RulesErrorSubstring, missingValueErrorSubstring, atPosition3ErrorSubstring), + }, + + // Invalid color + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 1", invalidThresholdColor, warnThresholdColor, failThresholdColor, atPosition1ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 2", passThresholdColor, invalidThresholdColor, failThresholdColor, atPosition2ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 3", passThresholdColor, warnThresholdColor, invalidThresholdColor, atPosition3ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 1 and 2", invalidThresholdColor, invalidThresholdColor, failThresholdColor, atPosition1ErrorSubstring, atPosition2ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 2 and 3", passThresholdColor, invalidThresholdColor, invalidThresholdColor, atPosition2ErrorSubstring, atPosition3ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 1 and 3", invalidThresholdColor, warnThresholdColor, invalidThresholdColor, atPosition1ErrorSubstring, atPosition3ErrorSubstring), + createDataExplorerThresholdsErrorTestWithInvalidColors("Invalid color at position 1, 2 and 3", invalidThresholdColor, invalidThresholdColor, invalidThresholdColor, atPosition1ErrorSubstring, atPosition2ErrorSubstring, atPosition3ErrorSubstring), + + // Combined invalid color and missing value + { + name: "Invalid color at position 1 and missing value at position 2", + thresholdValues: []*float64{&thresholdValue0, nil, &thresholdValue69000}, + thresholdColors: []string{invalidThresholdColor, warnThresholdColor, failThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", invalidColorErrorSubstring, atPosition1ErrorSubstring, missingValueErrorSubstring, atPosition2ErrorSubstring), + }, + + // Combined invalid color, missing value and too many rules + { + name: "Invalid color at position 1 and missing value at position 2 and too many rules", + thresholdValues: []*float64{&thresholdValue0, nil, &thresholdValue69000, &thresholdValue69000}, + thresholdColors: []string{invalidThresholdColor, warnThresholdColor, failThresholdColor, failThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", invalidColorErrorSubstring, atPosition1ErrorSubstring, missingValueErrorSubstring, atPosition2ErrorSubstring, expected3RulesErrorSubstring), + }, + + // Invalid color sequences + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-pass-pass sequence", passThresholdColor, passThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-pass-pass sequence", warnThresholdColor, passThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-pass-pass sequence", failThresholdColor, passThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-warn-pass sequence", passThresholdColor, warnThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-warn-pass sequence", warnThresholdColor, warnThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-fail-pass sequence", passThresholdColor, failThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-fail-pass sequence", warnThresholdColor, failThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-fail-pass sequence", failThresholdColor, failThresholdColor, passThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-pass-warn sequence", passThresholdColor, passThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-pass-warn sequence", warnThresholdColor, passThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-pass-warn sequence", failThresholdColor, passThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-warn-warn sequence", passThresholdColor, warnThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-warn-warn sequence", warnThresholdColor, warnThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-warn-warn sequence", failThresholdColor, warnThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-fail-warn sequence", passThresholdColor, failThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-fail-warn sequence", warnThresholdColor, failThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-fail-warn sequence", failThresholdColor, failThresholdColor, warnThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-pass-fail sequence", passThresholdColor, passThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-pass-fail sequence", warnThresholdColor, passThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-pass-fail sequence", failThresholdColor, passThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-warn-fail sequence", warnThresholdColor, warnThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-warn-fail sequence", failThresholdColor, warnThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid pass-fail-fail sequence", passThresholdColor, failThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid warn-fail-fail sequence", warnThresholdColor, failThresholdColor, failThresholdColor), + createDataExplorerThresholdsErrorTestWithColorSequence("Invalid fail-fail-fail sequence", failThresholdColor, failThresholdColor, failThresholdColor), + + // Not strictly monotonically increasing values + createDataExplorerThresholdsErrorTestWithWrongValues(0, 0, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 0, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 0, 0), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 68000, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 68000, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 68000, 0), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 69000, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 69000, 0), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 69000, 0), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 0, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 0, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 0, 68000), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 68000, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 68000, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 68000, 68000), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 69000, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 69000, 68000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 69000, 68000), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 0, 69000), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 0, 69000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 0, 69000), + + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 68000, 69000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 68000, 69000), + + createDataExplorerThresholdsErrorTestWithWrongValues(0, 69000, 69000), + createDataExplorerThresholdsErrorTestWithWrongValues(68000, 69000, 69000), + createDataExplorerThresholdsErrorTestWithWrongValues(69000, 69000, 69000), + + // Combined invalid color sequence and not strictly monotonically increasing values + { + name: "Invalid color sequence and not strictly monotonically increasing values", + thresholdValues: []*float64{&thresholdValue68000, &thresholdValue69000, &thresholdValue69000}, + thresholdColors: []string{warnThresholdColor, warnThresholdColor, failThresholdColor}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", invalidColorSequenceErrorSubstring, expectedMonotonicallyIncreasingErrorSubstring), + }, + } + + for _, thresholdTest := range tests { + t.Run(thresholdTest.name, func(t *testing.T) { + handler := test.NewTemplatingPayloadBasedURLHandler(t, testDataFolder+"dashboard_thresholds_template.json") + handler.AddExact(dynatrace.DashboardsPath+"/"+testDashboardID, tileThresholdsTemplateData{ThresholdValues: thresholdTest.thresholdValues, ThresholdColors: thresholdTest.thresholdColors}) + + runGetSLIsFromDashboardTestAndCheckSLIs(t, handler, testGetSLIEventData, getSLIFinishedEventFailureAssertionsFunc, thresholdTest.sliResultAssertionsFunc) + }) + } +} + +func createDataExplorerThresholdsErrorTestWithMissingValues(name string, v1 *float64, v2 *float64, v3 *float64, positionErrorSubstrings ...string) dataExplorerThresholdErrorsTest { + return createDataExplorerThresholdsErrorTestWithValues(name, v1, v2, v3, missingValueErrorSubstring, positionErrorSubstrings...) +} + +func createDataExplorerThresholdsErrorTestWithWrongValues(v1 float64, v2 float64, v3 float64) dataExplorerThresholdErrorsTest { + name := fmt.Sprintf("Invalid not strictly monotonically increasing values test %f %f %f", v1, v2, v3) + return createDataExplorerThresholdsErrorTestWithValues(name, &v1, &v2, &v3, expectedMonotonicallyIncreasingErrorSubstring) +} + +func createDataExplorerThresholdsErrorTestWithValues(name string, v1 *float64, v2 *float64, v3 *float64, mainErrorSubstring string, positionErrorSubstrings ...string) dataExplorerThresholdErrorsTest { + expectedErrorSubstrings := append([]string{mainErrorSubstring}, positionErrorSubstrings...) + return dataExplorerThresholdErrorsTest{ + name: name, + thresholdValues: []*float64{v1, v2, v3}, + thresholdColors: passWarnFailThresholdColors, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", expectedErrorSubstrings...), + } +} + +func createDataExplorerThresholdsErrorTestWithInvalidColors(name string, c1 string, c2 string, c3 string, positionErrorSubstrings ...string) dataExplorerThresholdErrorsTest { + return createDataExplorerThresholdsErrorTestWithColors(name, c1, c2, c3, invalidColorErrorSubstring, positionErrorSubstrings...) +} + +func createDataExplorerThresholdsErrorTestWithColorSequence(name string, c1 string, c2 string, c3 string) dataExplorerThresholdErrorsTest { + return createDataExplorerThresholdsErrorTestWithColors(name, c1, c2, c3, invalidColorSequenceErrorSubstring) +} + +func createDataExplorerThresholdsErrorTestWithColors(name string, c1 string, c2 string, c3 string, mainErrorSubstring string, positionErrorSubstrings ...string) dataExplorerThresholdErrorsTest { + expectedErrorSubstrings := append([]string{mainErrorSubstring}, positionErrorSubstrings...) + return dataExplorerThresholdErrorsTest{ + name: name, + thresholdValues: validThresholdValues, + thresholdColors: []string{c1, c2, c3}, + sliResultAssertionsFunc: createFailedSLIResultAssertionsFunc("srt", expectedErrorSubstrings...), + } +} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_1.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_1.json deleted file mode 100644 index 383e58329..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_1.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#14a8f5" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_2.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_2.json deleted file mode 100644 index 54cd12cdd..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_2.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#14a8f5" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_3.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_3.json deleted file mode 100644 index ea9e24bb0..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_color_3.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#14a8f5" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_fail.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_fail.json deleted file mode 100644 index 5ae2b8ada..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_fail.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 69000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_fail.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_fail.json deleted file mode 100644 index ec94c8c3b..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_fail.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#dc172a" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_pass.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_pass.json deleted file mode 100644 index 310dc8ce9..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_pass.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#7dc540" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_warn.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_warn.json deleted file mode 100644 index b17bbfcfd..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_two_warn.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#f5d30f" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_few_rules.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_thresholds_template.json similarity index 90% rename from internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_few_rules.json rename to internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_thresholds_template.json index eae1940e4..6b0884d7a 100644 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_few_rules.json +++ b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_thresholds_template.json @@ -7,7 +7,7 @@ }, "id": "12345678-1111-4444-8888-123456789012", "dashboardMetadata": { - "name": "Management Zone Test", + "name": "", "shared": false, "owner": "", "popularity": 1 @@ -78,15 +78,11 @@ "thresholds": [ { "axisTarget": "LEFT", - "rules": [ + "rules": [{{$first := true}}{{$colors := .ThresholdColors}}{{range $i, $e := .ThresholdValues}}{{if $first}}{{$first = false}}{{else}},{{end}} { - "value": 0, - "color": "#7dc540" - }, - { - "value": 68000, - "color": "#f5d30f" - } + {{if not $e}}{{else}}"value":{{$e}},{{end}} + "color": "{{index $colors $i}}" + }{{end}} ], "queryId": "", "visible": true diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_many_rules.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_many_rules.json deleted file mode 100644 index 1d1b378d5..000000000 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_too_many_rules.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "metadata": { - "configurationVersions": [ - 5 - ], - "clusterVersion": "1.245.0.20220804-195908" - }, - "id": "12345678-1111-4444-8888-123456789012", - "dashboardMetadata": { - "name": "Management Zone Test", - "shared": false, - "owner": "", - "popularity": 1 - }, - "tiles": [ - { - "name": "Service Response Time; sli=srt", - "tileType": "DATA_EXPLORER", - "configured": true, - "bounds": { - "top": 266, - "left": 0, - "width": 1140, - "height": 190 - }, - "tileFilter": {}, - "customName": "Data explorer results", - "queries": [ - { - "id": "A", - "metric": "builtin:service.response.time", - "spaceAggregation": "AVG", - "timeAggregation": "DEFAULT", - "splitBy": [], - "filterBy": { - "nestedFilters": [], - "criteria": [] - }, - "enabled": true - } - ], - "visualConfig": { - "type": "GRAPH_CHART", - "global": { - "hideLegend": false - }, - "rules": [ - { - "matcher": "A:", - "properties": { - "color": "DEFAULT" - }, - "seriesOverrides": [] - } - ], - "axes": { - "xAxis": { - "displayName": "", - "visible": true - }, - "yAxes": [ - { - "displayName": "", - "visible": true, - "min": "AUTO", - "max": "AUTO", - "position": "LEFT", - "queryIds": [ - "A" - ], - "defaultAxis": true - } - ] - }, - "heatmapSettings": { - "yAxis": "VALUE" - }, - "thresholds": [ - { - "axisTarget": "LEFT", - "rules": [ - { - "value": 0, - "color": "#7dc540" - }, - { - "value": 67000, - "color": "#f5d30f" - }, - { - "value": 68000, - "color": "#f5d30f" - }, - { - "value": 69000, - "color": "#dc172a" - } - ], - "queryId": "", - "visible": true - } - ], - "tableSettings": { - "isThresholdBackgroundAppliedToCell": false - }, - "graphChartSettings": { - "connectNulls": false - }, - "honeycombSettings": { - "showHive": true, - "showLegend": true, - "showLabels": false - } - }, - "queriesSettings": { - "resolution": "" - }, - "metricExpressions": [ - "resolution=null&(builtin:service.response.time:splitBy():avg:auto:sort(value(avg,descending)):limit(10)):limit(100):names" - ] - } - ] -} diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_descending_ascending.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_invalid_values.json similarity index 96% rename from internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_descending_ascending.json rename to internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_invalid_values.json index 87760ec0c..fc3c4caf0 100644 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_descending_ascending.json +++ b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_invalid_values.json @@ -80,20 +80,20 @@ "axisTarget": "LEFT", "rules": [ { - "value": 68000, + "value": 0, "color": "#7dc540" }, { - "value": 67000, + "value": 68000, "color": "#f5d30f" }, { - "value": 69000, + "value": 68000, "color": "#dc172a" } ], "queryId": "", - "visible": true + "visible": false } ], "tableSettings": { diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_ascending_descending.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_valid_values.json similarity index 98% rename from internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_ascending_descending.json rename to internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_valid_values.json index 07e2b3ef6..0d4ed9b0f 100644 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_ascending_descending.json +++ b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_not_visible_thresholds_with_valid_values.json @@ -84,16 +84,16 @@ "color": "#7dc540" }, { - "value": 69000, + "value": 68000, "color": "#f5d30f" }, { - "value": 68000, + "value": 69000, "color": "#dc172a" } ], "queryId": "", - "visible": true + "visible": false } ], "tableSettings": { diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_warn.json b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_visible_thresholds_without_values.json similarity index 96% rename from internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_warn.json rename to internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_visible_thresholds_without_values.json index 67cb1ccee..3efa9e6a3 100644 --- a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_invalid_sequence_no_warn.json +++ b/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_success/dashboard_visible_thresholds_without_values.json @@ -80,15 +80,12 @@ "axisTarget": "LEFT", "rules": [ { - "value": 0, "color": "#7dc540" }, { - "value": 0, "color": "#f5d30f" }, { - "value": 69000, "color": "#dc172a" } ], diff --git a/internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_unit_transform_millisecond.json b/internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json similarity index 100% rename from internal/sli/testdata/dashboards/data_explorer/tile_thresholds_errors/dashboard_unit_transform_millisecond.json rename to internal/sli/testdata/dashboards/data_explorer/unit_transform_is_not_auto/dashboard.json diff --git a/internal/test/templating_payload_based_url_handler.go b/internal/test/templating_payload_based_url_handler.go index 8e5a5ddb0..b269af527 100644 --- a/internal/test/templating_payload_based_url_handler.go +++ b/internal/test/templating_payload_based_url_handler.go @@ -5,6 +5,8 @@ import ( "net/http" "testing" "text/template" + + "github.com/sirupsen/logrus" ) // TemplatingPayloadBasedURLHandler encapsulates a payload-based URL handler and extends its with templating functionality @@ -48,6 +50,6 @@ func (h *TemplatingPayloadBasedURLHandler) writeToBuffer(templatingData interfac if err != nil { h.t.Fatalf("could not write to buffer: %s", err) } - + logrus.Info(buf.String()) return buf.Bytes() }