mirror of
https://github.com/TwiN/gatus.git
synced 2026-02-04 12:56:48 +00:00
feat(alerting): Add RESULT_CONDITIONS in custom alert to have more information (#1086)
feat(alerting): Add RESULT_CONDITIONS in custom alert to have more information on an alert while using custom alerting module Add testing of new feature Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
@@ -2450,7 +2450,8 @@ Furthermore, you may use the following placeholders in the body (`alerting.custo
|
|||||||
- `[ENDPOINT_GROUP]` (resolved from `endpoints[].group`)
|
- `[ENDPOINT_GROUP]` (resolved from `endpoints[].group`)
|
||||||
- `[ENDPOINT_URL]` (resolved from `endpoints[].url`)
|
- `[ENDPOINT_URL]` (resolved from `endpoints[].url`)
|
||||||
- `[RESULT_ERRORS]` (resolved from the health evaluation of a given health check)
|
- `[RESULT_ERRORS]` (resolved from the health evaluation of a given health check)
|
||||||
|
- `[RESULT_CONDITIONS]` (condition results from the health evaluation of a given health check)
|
||||||
|
-
|
||||||
If you have an alert using the `custom` provider with `send-on-resolved` set to `true`, you can use the
|
If you have an alert using the `custom` provider with `send-on-resolved` set to `true`, you can use the
|
||||||
`[ALERT_TRIGGERED_OR_RESOLVED]` placeholder to differentiate the notifications.
|
`[ALERT_TRIGGERED_OR_RESOLVED]` placeholder to differentiate the notifications.
|
||||||
The aforementioned placeholder will be replaced by `TRIGGERED` or `RESOLVED` accordingly, though it can be modified
|
The aforementioned placeholder will be replaced by `TRIGGERED` or `RESOLVED` accordingly, though it can be modified
|
||||||
|
|||||||
@@ -111,6 +111,25 @@ func (provider *AlertProvider) buildHTTPRequest(cfg *Config, ep *endpoint.Endpoi
|
|||||||
resultErrors := strings.ReplaceAll(strings.Join(result.Errors, ","), "\"", "\\\"")
|
resultErrors := strings.ReplaceAll(strings.Join(result.Errors, ","), "\"", "\\\"")
|
||||||
body = strings.ReplaceAll(body, "[RESULT_ERRORS]", resultErrors)
|
body = strings.ReplaceAll(body, "[RESULT_ERRORS]", resultErrors)
|
||||||
url = strings.ReplaceAll(url, "[RESULT_ERRORS]", resultErrors)
|
url = strings.ReplaceAll(url, "[RESULT_ERRORS]", resultErrors)
|
||||||
|
|
||||||
|
if len(result.ConditionResults) > 0 && strings.Contains(body, "[RESULT_CONDITIONS]") {
|
||||||
|
var formattedConditionResults string
|
||||||
|
for index, conditionResult := range result.ConditionResults {
|
||||||
|
var prefix string
|
||||||
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("%s - `%s`", prefix, conditionResult.Condition)
|
||||||
|
if index < len(result.ConditionResults)-1 {
|
||||||
|
formattedConditionResults += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body = strings.ReplaceAll(body, "[RESULT_CONDITIONS]", formattedConditionResults)
|
||||||
|
url = strings.ReplaceAll(url, "[RESULT_CONDITIONS]", formattedConditionResults)
|
||||||
|
}
|
||||||
|
|
||||||
if resolved {
|
if resolved {
|
||||||
body = strings.ReplaceAll(body, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true))
|
body = strings.ReplaceAll(body, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true))
|
||||||
url = strings.ReplaceAll(url, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true))
|
url = strings.ReplaceAll(url, "[ALERT_TRIGGERED_OR_RESOLVED]", provider.GetAlertStatePlaceholderValue(cfg, true))
|
||||||
|
|||||||
@@ -261,6 +261,69 @@ func TestAlertProvider_buildHTTPRequestWithCustomPlaceholder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlertProvider_buildHTTPRequestWithCustomPlaceholderAndResultConditions(t *testing.T) {
|
||||||
|
alertProvider := &AlertProvider{
|
||||||
|
DefaultConfig: Config{
|
||||||
|
URL: "https://example.com/[ENDPOINT_GROUP]/[ENDPOINT_NAME]?event=[ALERT_TRIGGERED_OR_RESOLVED]&description=[ALERT_DESCRIPTION]",
|
||||||
|
Body: "[ENDPOINT_NAME],[ENDPOINT_GROUP],[ALERT_DESCRIPTION],[ALERT_TRIGGERED_OR_RESOLVED],[RESULT_CONDITIONS]",
|
||||||
|
Headers: nil,
|
||||||
|
Placeholders: map[string]map[string]string{
|
||||||
|
"ALERT_TRIGGERED_OR_RESOLVED": {
|
||||||
|
"RESOLVED": "fixed",
|
||||||
|
"TRIGGERED": "boom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
alertDescription := "alert-description"
|
||||||
|
scenarios := []struct {
|
||||||
|
AlertProvider *AlertProvider
|
||||||
|
Resolved bool
|
||||||
|
ExpectedURL string
|
||||||
|
ExpectedBody string
|
||||||
|
NoConditions bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
AlertProvider: alertProvider,
|
||||||
|
Resolved: true,
|
||||||
|
ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=fixed&description=alert-description",
|
||||||
|
ExpectedBody: "endpoint-name,endpoint-group,alert-description,fixed,✅ - `[CONNECTED] == true`, ✅ - `[STATUS] == 200`",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AlertProvider: alertProvider,
|
||||||
|
Resolved: false,
|
||||||
|
ExpectedURL: "https://example.com/endpoint-group/endpoint-name?event=boom&description=alert-description",
|
||||||
|
ExpectedBody: "endpoint-name,endpoint-group,alert-description,boom,❌ - `[CONNECTED] == true`, ❌ - `[STATUS] == 200`",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(fmt.Sprintf("resolved-%v-with-custom-placeholders", scenario.Resolved), func(t *testing.T) {
|
||||||
|
var conditionResults []*endpoint.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*endpoint.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||||
|
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request := alertProvider.buildHTTPRequest(
|
||||||
|
&alertProvider.DefaultConfig,
|
||||||
|
&endpoint.Endpoint{Name: "endpoint-name", Group: "endpoint-group"},
|
||||||
|
&alert.Alert{Description: &alertDescription},
|
||||||
|
&endpoint.Result{ConditionResults: conditionResults},
|
||||||
|
scenario.Resolved,
|
||||||
|
)
|
||||||
|
if request.URL.String() != scenario.ExpectedURL {
|
||||||
|
t.Error("expected URL to be", scenario.ExpectedURL, "got", request.URL.String())
|
||||||
|
}
|
||||||
|
body, _ := io.ReadAll(request.Body)
|
||||||
|
if string(body) != scenario.ExpectedBody {
|
||||||
|
t.Error("expected body to be", scenario.ExpectedBody, "got", string(body))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAlertProvider_GetAlertStatePlaceholderValueDefaults(t *testing.T) {
|
func TestAlertProvider_GetAlertStatePlaceholderValueDefaults(t *testing.T) {
|
||||||
alertProvider := &AlertProvider{
|
alertProvider := &AlertProvider{
|
||||||
DefaultConfig: Config{
|
DefaultConfig: Config{
|
||||||
|
|||||||
Reference in New Issue
Block a user