Alerting: Webhook Improvements - Templateable Payloads (#103818)
* Template editor syntax highlighting when preview is json-like * Add new template editor language examples, snippets, and functions * Use updated NewTemplate function * Add new fields to webhook notifier - CustomPayload - ExtraHeaders * Documentation * Update grafana/alerting to in-progress PR (needs updating after merge) * Fix integration test * Remove docs reference to .Extra template context No longer exists, was part of a previous iteration * make update-workspace * Update grafana/alerting to actual merged commit
This commit is contained in:
+80
-13
@@ -33,6 +33,11 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/manage-contact-points/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/manage-contact-points/
|
||||
notification-templates-namespaced-functions:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/configure-notifications/template-notifications/reference/#namespaced-functions
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/reference/#namespaced-functions
|
||||
---
|
||||
|
||||
# Configure webhook notifications
|
||||
@@ -70,6 +75,7 @@ For more details on contact points, including how to test them and enable notifi
|
||||
| Basic Authentication Password | Password for HTTP Basic Authentication. |
|
||||
| Authentication Header Scheme | Scheme for the `Authorization` Request Header. Default is `Bearer`. |
|
||||
| Authentication Header Credentials | Credentials for the `Authorization` Request header. |
|
||||
| Extra Headers | Additional HTTP headers to include in the request. |
|
||||
| Max Alerts | Maximum number of alerts to include in a notification. Any alerts exceeding this limit are ignored. `0` means no limit. |
|
||||
| TLS | TLS configuration options, including CA certificate, client certificate, and client key. |
|
||||
| HMAC Signature | HMAC signature configuration options. |
|
||||
@@ -115,18 +121,11 @@ To validate incoming webhook requests from Grafana, follow these steps:
|
||||
|
||||
Use the following settings to include custom data within the [JSON payload](#body). Both options support using [notification templates](ref:notification-templates).
|
||||
|
||||
| Option | Description |
|
||||
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Title | Sends the value as a string in the `title` field of the [JSON payload](#body). Supports [notification templates](ref:notification-templates). |
|
||||
| Message | Sends the value as a string in the `message` field of the [JSON payload](#body). Supports [notification templates](ref:notification-templates). |
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
You can customize the `title` and `message` options to include custom messages and notification data using notification templates. These fields are always sent as strings in the JSON payload.
|
||||
|
||||
However, you cannot customize the webhook data structure, such as adding or changing other JSON fields and HTTP headers, or sending data in a different format like XML.
|
||||
|
||||
If you need to format these fields as JSON or modify other webhook request options, consider sending webhook notifications to a proxy server that adjusts the webhook request before forwarding it to the final destination.
|
||||
{{< /admonition >}}
|
||||
| Option | Description |
|
||||
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Title | Sends the value as a string in the `title` field of the [JSON payload](#body). Supports [notification templates](ref:notification-templates). |
|
||||
| Message | Sends the value as a string in the `message` field of the [JSON payload](#body). Supports [notification templates](ref:notification-templates). |
|
||||
| [Custom Payload](#custom-payload) | Optionally override the default payload format with a custom template. |
|
||||
|
||||
#### Optional notification settings
|
||||
|
||||
@@ -134,7 +133,7 @@ If you need to format these fields as JSON or modify other webhook request optio
|
||||
| ------------------------ | ------------------------------------------------------------------- |
|
||||
| Disable resolved message | Enable this option to prevent notifications when an alert resolves. |
|
||||
|
||||
## JSON payload
|
||||
## Default JSON payload
|
||||
|
||||
The following example shows the payload of a webhook notification containing information about two firing alerts:
|
||||
|
||||
@@ -252,3 +251,71 @@ The Alert object represents an alert included in the notification group, as prov
|
||||
| `dashboardURL` | string | A link to the Grafana Dashboard if the alert has a Dashboard UID annotation. |
|
||||
| `panelURL` | string | A link to the panel if the alert has a Panel ID annotation. |
|
||||
| `imageURL` | string | URL of a screenshot of a panel assigned to the rule that created this notification. |
|
||||
|
||||
## Custom Payload
|
||||
|
||||
The `Custom Payload` option allows you to completely customize the webhook payload using templates. This gives you full control over the structure and content of the webhook request.
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| Payload Template | Template string that defines the structure of the webhook payload. |
|
||||
| Payload Variables | Key-value pairs that define additional variables available in the template under `.Vars.<variable_name>`. |
|
||||
|
||||
Example of a custom payload template that includes variables:
|
||||
|
||||
```
|
||||
{
|
||||
"alert_name": "{{ .CommonLabels.alertname }}",
|
||||
"status": "{{ .Status }}",
|
||||
"environment": "{{ .Vars.environment }}",
|
||||
"custom_field": "{{ .Vars.custom_field }}"
|
||||
}
|
||||
```
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
When using Custom Payload, the Title and Message fields are ignored as the entire payload structure is determined by your template.
|
||||
{{< /admonition >}}
|
||||
|
||||
### JSON Template Functions
|
||||
|
||||
When creating custom payloads, several template functions are available to help generate valid JSON structures. These include functions for creating dictionaries (`coll.Dict`), arrays (`coll.Slice`, `coll.Append`), and converting between JSON strings and objects (`data.ToJSON`, `data.JSON`).
|
||||
|
||||
For detailed information about these and other template functions, refer to [notification template functions](ref:notification-templates-namespaced-functions).
|
||||
|
||||
Example using JSON helper functions:
|
||||
|
||||
```
|
||||
{{ define "webhook.custom.payload" -}}
|
||||
{{ coll.Dict
|
||||
"receiver" .Receiver
|
||||
"status" .Status
|
||||
"alerts" (tmpl.Exec "webhook.custom.simple_alerts" .Alerts | data.JSON)
|
||||
"groupLabels" .GroupLabels
|
||||
"commonLabels" .CommonLabels
|
||||
"commonAnnotations" .CommonAnnotations
|
||||
"externalURL" .ExternalURL
|
||||
"version" "1"
|
||||
"orgId" (index .Alerts 0).OrgID
|
||||
"truncatedAlerts" .TruncatedAlerts
|
||||
"groupKey" .GroupKey
|
||||
"state" (tmpl.Inline "{{ if eq .Status \"resolved\" }}ok{{ else }}alerting{{ end }}" . )
|
||||
"allVariables" .Vars
|
||||
"title" (tmpl.Exec "default.title" . )
|
||||
"message" (tmpl.Exec "default.message" . )
|
||||
| data.ToJSONPretty " "}}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Embed json templates in other json templates. */ -}}
|
||||
{{ define "webhook.custom.simple_alerts" -}}
|
||||
{{- $alerts := coll.Slice -}}
|
||||
{{- range . -}}
|
||||
{{ $alerts = coll.Append (coll.Dict
|
||||
"status" .Status
|
||||
"labels" .Labels
|
||||
"startsAt" .StartsAt
|
||||
"endsAt" .EndsAt
|
||||
) $alerts}}
|
||||
{{- end -}}
|
||||
{{- $alerts | data.ToJSON -}}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
+184
-11
@@ -56,17 +56,19 @@ This documentation lists the data available for use in notification templates.
|
||||
|
||||
In notification templates, dot (`.`) is initialized with the following data:
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------------- | ----------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `Receiver` | string | The name of the contact point sending the notification |
|
||||
| `Status` | string | The status is `firing` if at least one alert is firing, otherwise `resolved`. |
|
||||
| `Alerts` | [][Alert](#alert) | List of all firing and resolved alerts in this notification. |
|
||||
| `Alerts.Firing` | [][Alert](#alert) | List of all firing alerts in this notification. |
|
||||
| `Alerts.Resolved` | [][Alert](#alert) | List of all resolved alerts in this notification. |
|
||||
| `GroupLabels` | [KV](#kv) | The labels that group these alerts in this notification based on the `Group by` option. |
|
||||
| `CommonLabels` | [KV](#kv) | The labels common to all alerts in this notification. |
|
||||
| `CommonAnnotations` | [KV](#kv) | The annotations common to all alerts in this notification. |
|
||||
| `ExternalURL` | string | A link to Grafana, or the Alertmanager that sent this notification if using an external Alertmanager. |
|
||||
| Name | Type | Description |
|
||||
| ------------------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `Receiver` | string | The name of the contact point sending the notification |
|
||||
| `Status` | string | The status is `firing` if at least one alert is firing, otherwise `resolved`. |
|
||||
| `Alerts` | [][Alert](#alert) | List of all firing and resolved alerts in this notification. |
|
||||
| `Alerts.Firing` | [][Alert](#alert) | List of all firing alerts in this notification. |
|
||||
| `Alerts.Resolved` | [][Alert](#alert) | List of all resolved alerts in this notification. |
|
||||
| `GroupLabels` | [KV](#kv) | The labels that group these alerts in this notification based on the `Group by` option. |
|
||||
| `CommonLabels` | [KV](#kv) | The labels common to all alerts in this notification. |
|
||||
| `CommonAnnotations` | [KV](#kv) | The annotations common to all alerts in this notification. |
|
||||
| `ExternalURL` | string | A link to Grafana, or the Alertmanager that sent this notification if using an external Alertmanager. |
|
||||
| `GroupKey` | string | The key used to identify this alert group. |
|
||||
| `TruncatedAlerts` | integer | The number of alerts, if any, that were truncated in the notification. Supported by Webhook and OnCall. |
|
||||
|
||||
It's important to remember that [a single notification can group multiple alerts](ref:alert-grouping) to reduce the number of alerts you receive. `Alerts` is an array that includes all the alerts in the notification.
|
||||
|
||||
@@ -115,6 +117,7 @@ Grafana-managed alerts include these additional properties:
|
||||
| `SilenceURL` | string | A link to silence the alert. |
|
||||
| `Values` | [KV](#kv) | The values of expressions used to evaluate the alert condition. Only relevant values are included. |
|
||||
| `ValueString` | string | A string that contains the labels and value of each reduced expression in the alert. |
|
||||
| `OrgID` | integer | The ID of the organization that owns the alert. |
|
||||
|
||||
This example iterates over the list of firing and resolved alerts (`.Alerts`) in the notification and prints the data for each alert:
|
||||
|
||||
@@ -264,6 +267,176 @@ You can then use `tz` to change the timezone from UTC to local time, such as `Eu
|
||||
21:01:45 CET
|
||||
```
|
||||
|
||||
## Namespaced Functions
|
||||
|
||||
In addition to the top-level functions, the following namespaced functions are also available:
|
||||
|
||||
### Collection Functions
|
||||
|
||||
| Name | Arguments | Returns | Description |
|
||||
| ------------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `coll.Dict` | key string, value any, ... | map | Creates a map with string keys from key/value pairs. All keys are converted to strings. If an odd number of arguments is provided, the last key will have an empty string value. |
|
||||
| `coll.Slice` | ...any | []any | Creates a slice (array/list) from the provided arguments. Useful for creating lists that can be iterated over with `range`. |
|
||||
| `coll.Append` | value any, list []any | []any | Creates a new list by appending a value to the end of an existing list. Does not modify the original list. |
|
||||
|
||||
Example using collection functions:
|
||||
|
||||
```go
|
||||
{{ define "collection.example" }}
|
||||
{{- /* Create a dictionary of alert metadata */ -}}
|
||||
{{- $metadata := coll.Dict
|
||||
"severity" "critical"
|
||||
"team" "infrastructure"
|
||||
"environment" "production"
|
||||
-}}
|
||||
|
||||
{{- /* Create a slice of affected services */ -}}
|
||||
{{- $services := coll.Slice "database" "cache" "api" -}}
|
||||
|
||||
{{- /* Append a new service to the list */ -}}
|
||||
{{- $services = coll.Append "web" $services -}}
|
||||
|
||||
{{- /* Use the collections in a template */ -}}
|
||||
Affected Services: {{ range $services }}{{ . }},{{ end }}
|
||||
|
||||
Alert Metadata:
|
||||
{{- range $k, $v := $metadata }}
|
||||
{{ $k }}: {{ $v }}
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Affected Services: database,cache,api,web,
|
||||
|
||||
Alert Metadata:
|
||||
environment: production
|
||||
severity: critical
|
||||
team: infrastructure
|
||||
```
|
||||
|
||||
### Data Functions
|
||||
|
||||
| Name | Arguments | Returns | Description |
|
||||
| ------------------- | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `data.JSON` | jsonString string | any | Parses a JSON string into an object that can be manipulated in the template. Works with both JSON objects and arrays. |
|
||||
| `data.ToJSON` | obj any | string | Serializes any object (maps, arrays, etc.) into a JSON string. Useful for creating webhook payloads. |
|
||||
| `data.ToJSONPretty` | indent string, obj any | string | Creates an indented JSON string representation of an object. The first argument specifies the indentation string (e.g., spaces). |
|
||||
|
||||
Example using data functions:
|
||||
|
||||
```go
|
||||
{{ define "data.example" }}
|
||||
{{- /* First, let's create some alert data as a JSON string */ -}}
|
||||
{{ $jsonString := `{
|
||||
"service": {
|
||||
"name": "payment-api",
|
||||
"environment": "production",
|
||||
"thresholds": {
|
||||
"error_rate": 5,
|
||||
"latency_ms": 100
|
||||
}
|
||||
}
|
||||
}` }}
|
||||
|
||||
{{- /* Parse the JSON string into an object we can work with */ -}}
|
||||
{{ $config := $jsonString | data.JSON }}
|
||||
|
||||
{{- /* Create a new alert payload */ -}}
|
||||
{{ $payload := coll.Dict
|
||||
"service" $config.service.name
|
||||
"environment" $config.service.environment
|
||||
"status" .Status
|
||||
"errorThreshold" $config.service.thresholds.error_rate
|
||||
}}
|
||||
|
||||
{{- /* Output the payload in different JSON formats */ -}}
|
||||
Compact JSON: {{ $payload | data.ToJSON }}
|
||||
|
||||
Pretty JSON with 2-space indent:
|
||||
{{ $payload | data.ToJSONPretty " " }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Compact JSON: {"environment":"production","errorThreshold":5,"service":"payment-api","status":"resolved"}
|
||||
|
||||
Pretty JSON with 2-space indent:
|
||||
{
|
||||
"environment": "production",
|
||||
"errorThreshold": 5,
|
||||
"service": "payment-api",
|
||||
"status": "resolved"
|
||||
}
|
||||
```
|
||||
|
||||
### Template Functions
|
||||
|
||||
| Name | Arguments | Returns | Description |
|
||||
| ------------- | ---------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `tmpl.Exec` | name string, [context any] | string | Executes a named template and returns the result as a string. Similar to the `template` action but allows for post-processing of the result. |
|
||||
| `tmpl.Inline` | template string, context any | string | Renders a string as a template. |
|
||||
|
||||
```go
|
||||
{{ define "template.example" -}}
|
||||
{{ coll.Dict
|
||||
"info" (tmpl.Exec `info` . | data.JSON)
|
||||
"severity" (tmpl.Inline `{{ print "critical" | toUpper }}` . )
|
||||
| data.ToJSONPretty " "}}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Define a sub-template */ -}}
|
||||
{{ define "info" -}}
|
||||
{{coll.Dict
|
||||
"team" "infrastructure"
|
||||
"environment" "production" | data.ToJSON }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"environment": "production",
|
||||
"team": "infrastructure"
|
||||
},
|
||||
"severity": "CRITICAL"
|
||||
}
|
||||
```
|
||||
|
||||
### Time Functions
|
||||
|
||||
| Name | Arguments | Returns | Description |
|
||||
| ---------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `time.Now` | | Time | Returns the current local time as a time.Time object. Can be formatted using Go's time formatting functions. |
|
||||
|
||||
Example using time functions:
|
||||
|
||||
```go
|
||||
{{ define "time.example" }}
|
||||
{{- /* Get current time in different formats */ -}}
|
||||
Current Time (UTC): {{ (time.Now).UTC.Format "2006-01-02 15:04:05 MST" }}
|
||||
Current Time (Local): {{ (time.Now).Format "Monday, January 2, 2006 at 15:04:05" }}
|
||||
|
||||
{{- /* Compare alert time with current time */ -}}
|
||||
{{ $timeAgo := (time.Now).Sub .StartsAt }}
|
||||
Alert fired: {{ $timeAgo }} ago
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
Current Time (UTC): 2025-03-08 18:14:27 UTC
|
||||
Current Time (Local): Saturday, March 8, 2025 at 14:14:27
|
||||
Alert fired: 25h49m32.78574723s ago
|
||||
```
|
||||
|
||||
## Differences with annotation and label templates
|
||||
|
||||
In the alert rule, you can also template annotations and labels to include additional information. For example, you might add a `summary` annotation that displays the query value triggering the alert.
|
||||
|
||||
@@ -77,7 +77,7 @@ require (
|
||||
github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage
|
||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||
github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692 // @grafana/alerting-backend
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
|
||||
@@ -1565,8 +1565,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692 h1:5mOChD6fbkxeafK1iuy/iO/qKLyHeougMTtRJSgvAUM=
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692/go.mod h1:3ER/8BhIEhvrddcztLQSc5ez1f1jNHIPdquc1F+DzOw=
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430 h1:qT0D7AIV0GRu8JUrSJYuyzj86kqLgksKQjwD++DqyOM=
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430/go.mod h1:3ER/8BhIEhvrddcztLQSc5ez1f1jNHIPdquc1F+DzOw=
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8=
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us=
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=
|
||||
|
||||
@@ -575,6 +575,7 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/
|
||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
|
||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
|
||||
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/grafana/alerting/templates"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ func (t *NotificationTemplate) Validate() error {
|
||||
|
||||
// Validate template contents. We try to stick as close to what will actually happen when the templates are parsed
|
||||
// by the alertmanager as possible.
|
||||
tmpl, err := template.New()
|
||||
tmpl, err := templates.NewTemplate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create template: %w", err)
|
||||
}
|
||||
|
||||
@@ -313,16 +313,24 @@ type WebhookIntegration struct {
|
||||
|
||||
URL string `json:"url" yaml:"url" hcl:"url"`
|
||||
|
||||
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
|
||||
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
|
||||
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
|
||||
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
|
||||
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
|
||||
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
|
||||
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
|
||||
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
|
||||
TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tlsConfig,block"`
|
||||
HMACConfig *HMACConfig `json:"hmacConfig,omitempty" yaml:"hmacConfig,omitempty" hcl:"hmacConfig,block"`
|
||||
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
|
||||
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
|
||||
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
|
||||
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
|
||||
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
|
||||
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
|
||||
ExtraHeaders *map[string]string `json:"headers,omitempty" yaml:"headers,omitempty" hcl:"headers"`
|
||||
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
|
||||
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
|
||||
TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tlsConfig,block"`
|
||||
HMACConfig *HMACConfig `json:"hmacConfig,omitempty" yaml:"hmacConfig,omitempty" hcl:"hmacConfig,block"`
|
||||
|
||||
Payload *CustomPayload `json:"payload,omitempty" yaml:"payload,omitempty" hcl:"payload,block"`
|
||||
}
|
||||
|
||||
type CustomPayload struct {
|
||||
Template *string `json:"template,omitempty" yaml:"template,omitempty" hcl:"template"`
|
||||
Vars *map[string]string `json:"vars,omitempty" yaml:"vars,omitempty" hcl:"vars"`
|
||||
}
|
||||
|
||||
type HMACConfig struct {
|
||||
|
||||
@@ -952,6 +952,13 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
PropertyName: "authorization_credentials",
|
||||
Secure: true,
|
||||
},
|
||||
{ // New in 12.0.
|
||||
Label: "Extra Headers",
|
||||
Description: "Optionally provide extra headers to be used in the request.",
|
||||
Element: ElementTypeKeyValueMap,
|
||||
InputType: InputTypeText,
|
||||
PropertyName: "headers",
|
||||
},
|
||||
{ // New in 8.0. TODO: How to enforce only numbers?
|
||||
Label: "Max Alerts",
|
||||
Description: "Max alerts to include in a notification. Remaining alerts in the same batch will be ignored above this number. 0 means no limit.",
|
||||
@@ -974,6 +981,30 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
PropertyName: "message",
|
||||
Placeholder: alertingTemplates.DefaultMessageEmbed,
|
||||
},
|
||||
{ // New in 12.0.
|
||||
Label: "Custom Payload",
|
||||
Description: "Optionally provide a templated payload. Overrides 'Message' and 'Title' field.",
|
||||
Element: ElementTypeSubform,
|
||||
PropertyName: "payload",
|
||||
SubformOptions: []NotifierOption{
|
||||
{
|
||||
Label: "Payload Template",
|
||||
Description: "Custom payload template.",
|
||||
Element: ElementTypeTextArea,
|
||||
PropertyName: "template",
|
||||
Placeholder: `{{ template "webhook.default.payload" . }}`,
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Label: "Payload Variables",
|
||||
Description: "Optionally provide a variables to be used in the payload template. They will be available in the template as `.Vars.<variable_name>`.",
|
||||
Element: ElementTypeKeyValueMap,
|
||||
InputType: InputTypeText,
|
||||
PropertyName: "vars",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Label: "TLS",
|
||||
PropertyName: "tlsConfig",
|
||||
|
||||
@@ -202,7 +202,7 @@ require (
|
||||
github.com/googleapis/go-sql-spanner v1.11.1 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d // indirect
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect
|
||||
|
||||
@@ -1249,8 +1249,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692 h1:5mOChD6fbkxeafK1iuy/iO/qKLyHeougMTtRJSgvAUM=
|
||||
github.com/grafana/alerting v0.0.0-20250408102153-2412c378a692/go.mod h1:3ER/8BhIEhvrddcztLQSc5ez1f1jNHIPdquc1F+DzOw=
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430 h1:qT0D7AIV0GRu8JUrSJYuyzj86kqLgksKQjwD++DqyOM=
|
||||
github.com/grafana/alerting v0.0.0-20250411135245-cad0d384d430/go.mod h1:3ER/8BhIEhvrddcztLQSc5ez1f1jNHIPdquc1F+DzOw=
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d h1:TDVZemfYeJHPyXeYCnqL7BQqsa+mpaZYth/Qm3TKaT8=
|
||||
github.com/grafana/authlib v0.0.0-20250325095148-d6da9c164a7d/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us=
|
||||
github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM=
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
@@ -2536,6 +2537,7 @@ var expEmailNotifications = []*notifications.SendEmailCommandSync{
|
||||
SilenceURL: "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%3DUID_EmailAlert&orgId=1",
|
||||
DashboardURL: "",
|
||||
PanelURL: "",
|
||||
OrgID: util.Pointer(int64(1)),
|
||||
Values: map[string]float64{"A": 1},
|
||||
ValueString: "[ var='A' labels={} value=1 ]",
|
||||
},
|
||||
@@ -2699,7 +2701,8 @@ var expNonEmailNotifications = map[string][]string{
|
||||
"fingerprint": "15c59b0a380bd9f1",
|
||||
"silenceURL": "http://localhost:3000/alerting/silence/new?alertmanager=grafana&matcher=__alert_rule_uid__%%3DUID_WebhookAlert&orgId=1",
|
||||
"dashboardURL": "",
|
||||
"panelURL": ""
|
||||
"panelURL": "",
|
||||
"orgId": 1
|
||||
}
|
||||
],
|
||||
"groupLabels": {
|
||||
|
||||
@@ -145,4 +145,43 @@ Alert annotations: {{ len .Annotations.SortedPairs }}
|
||||
- RunbookURL: {{ .Annotations.runbook_url}}
|
||||
{{ end -}}`,
|
||||
},
|
||||
{
|
||||
description: 'Create JSON payload for webhook contact point',
|
||||
example: `{{- /* Example displaying a custom JSON payload for a webhook contact point.*/ -}}
|
||||
{{- /* Edit the template name and template content as needed. */ -}}
|
||||
{{- /* Variables defined in the webhook contact point can be accessed in .Vars but will not be previewable. */ -}}
|
||||
{{ define "webhook.custom.payload" -}}
|
||||
{{ coll.Dict
|
||||
"receiver" .Receiver
|
||||
"status" .Status
|
||||
"alerts" (tmpl.Exec "webhook.custom.simple_alerts" .Alerts | data.JSON)
|
||||
"groupLabels" .GroupLabels
|
||||
"commonLabels" .CommonLabels
|
||||
"commonAnnotations" .CommonAnnotations
|
||||
"externalURL" .ExternalURL
|
||||
"version" "1"
|
||||
"orgId" (index .Alerts 0).OrgID
|
||||
"truncatedAlerts" .TruncatedAlerts
|
||||
"groupKey" .GroupKey
|
||||
"state" (tmpl.Inline "{{ if eq .Status \\"resolved\\" }}ok{{ else }}alerting{{ end }}" . )
|
||||
"allVariables" .Vars
|
||||
"title" (tmpl.Exec "default.title" . )
|
||||
"message" (tmpl.Exec "default.message" . )
|
||||
| data.ToJSONPretty " "}}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Example showcasing embedding json templates in other json templates. */ -}}
|
||||
{{ define "webhook.custom.simple_alerts" -}}
|
||||
{{- $alerts := coll.Slice -}}
|
||||
{{- range . -}}
|
||||
{{ $alerts = coll.Append (coll.Dict
|
||||
"status" .Status
|
||||
"labels" .Labels
|
||||
"startsAt" .StartsAt
|
||||
"endsAt" .EndsAt
|
||||
) $alerts}}
|
||||
{{- end -}}
|
||||
{{- $alerts | data.ToJSON -}}
|
||||
{{- end }}`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -184,3 +184,36 @@ describe('TemplatePreview component', () => {
|
||||
expect(within(previewContent).getByTestId('mockeditor')).toHaveValue('This is the template result bla bla bla');
|
||||
});
|
||||
});
|
||||
|
||||
it('Should render preview type , if response contains valid json ', async () => {
|
||||
const response: TemplatePreviewResponse = {
|
||||
results: [
|
||||
{ name: 'template_text', text: 'This is the template result bla bla bla' },
|
||||
{ name: 'template_valid', text: '{"test":"value","test2":"value2"}' },
|
||||
{ name: 'template_invalid', text: '{"test":"value","test2":"value2",}' },
|
||||
],
|
||||
};
|
||||
mockPreviewTemplateResponse(server, response);
|
||||
render(
|
||||
<TemplatePreview
|
||||
payload={'[{"a":"b"}]'}
|
||||
templateName="potato"
|
||||
templateContent={`{{ define "potato" }}{{ . }}{{ end }}`}
|
||||
payloadFormatError={null}
|
||||
setPayloadFormatError={jest.fn()}
|
||||
/>,
|
||||
{ wrapper: getProviderWraper() }
|
||||
);
|
||||
|
||||
const previews = ui.resultItems.getAll;
|
||||
await waitFor(() => {
|
||||
expect(previews()).toHaveLength(3);
|
||||
});
|
||||
const previewItems = previews();
|
||||
expect(within(previewItems[0]).getByRole('banner')).toHaveTextContent('template_text');
|
||||
expect(within(previewItems[0]).getByRole('banner')).toHaveTextContent('plaintext');
|
||||
expect(within(previewItems[1]).getByRole('banner')).toHaveTextContent('template_valid');
|
||||
expect(within(previewItems[1]).getByRole('banner')).toHaveTextContent('json');
|
||||
expect(within(previewItems[2]).getByRole('banner')).toHaveTextContent('template_invalid');
|
||||
expect(within(previewItems[2]).getByRole('banner')).toHaveTextContent('plaintext');
|
||||
});
|
||||
|
||||
@@ -69,15 +69,30 @@ function PreviewResultViewer({ previews }: { previews: TemplatePreviewResult[] }
|
||||
// If there is only one template, we don't need to show the name
|
||||
const singleTemplate = previews.length === 1;
|
||||
|
||||
const isValidJson = (text: string) => {
|
||||
try {
|
||||
JSON.parse(text);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className={styles.viewer.container} data-testid="template-preview">
|
||||
{previews.map((preview) => {
|
||||
const language = isValidJson(preview.text) ? 'json' : 'plaintext';
|
||||
return (
|
||||
<li className={styles.viewer.box} key={preview.name}>
|
||||
{singleTemplate ? null : <header className={styles.viewer.header}>{preview.name}</header>}
|
||||
{singleTemplate ? null : (
|
||||
<header className={styles.viewer.header}>
|
||||
{preview.name}
|
||||
<div className={styles.viewer.language}>{language}</div>
|
||||
</header>
|
||||
)}
|
||||
<CodeEditor
|
||||
containerStyles={styles.editorContainer}
|
||||
language={'plaintext'}
|
||||
language={language}
|
||||
showLineNumbers={false}
|
||||
showMiniMap={false}
|
||||
value={preview.text}
|
||||
@@ -133,11 +148,17 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
height: 'inherit',
|
||||
}),
|
||||
header: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
padding: theme.spacing(1, 2),
|
||||
borderBottom: `1px solid ${theme.colors.border.medium}`,
|
||||
backgroundColor: theme.colors.background.secondary,
|
||||
}),
|
||||
language: css({
|
||||
marginLeft: 'auto',
|
||||
fontStyle: 'italic',
|
||||
}),
|
||||
errorText: css({
|
||||
color: theme.colors.error.text,
|
||||
}),
|
||||
|
||||
+13
-1
@@ -1,6 +1,6 @@
|
||||
import type { Monaco } from '@grafana/ui';
|
||||
|
||||
import { AlertmanagerTemplateFunction } from './language';
|
||||
import { AlertmanagerTemplateFunction, GomplateFunctions } from './language';
|
||||
import { SuggestionDefinition } from './suggestionDefinition';
|
||||
|
||||
export function getAlertManagerSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
@@ -50,3 +50,15 @@ export function getAlertManagerSuggestions(monaco: Monaco): SuggestionDefinition
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getGomplateSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
const kind = monaco.languages.CompletionItemKind.Function;
|
||||
return Object.values(GomplateFunctions).flatMap((functionList) =>
|
||||
functionList.map((func) => ({
|
||||
label: func.keyword,
|
||||
detail: func.usage,
|
||||
documentation: `${func.definition}\n\n${func.example}`,
|
||||
kind,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { IDisposable, IRange, Position, editor, languages } from 'monaco-ed
|
||||
|
||||
import type { Monaco } from '@grafana/ui';
|
||||
|
||||
import { getAlertManagerSuggestions } from './alertManagerSuggestions';
|
||||
import { getAlertManagerSuggestions, getGomplateSuggestions } from './alertManagerSuggestions';
|
||||
import { SuggestionDefinition } from './suggestionDefinition';
|
||||
import {
|
||||
getAlertSuggestions,
|
||||
@@ -72,7 +72,10 @@ export class CompletionProvider {
|
||||
};
|
||||
|
||||
getFunctionsSuggestions = (): languages.ProviderResult<languages.CompletionList> => {
|
||||
return this.getCompletionsFromDefinitions(getAlertManagerSuggestions(this.monaco));
|
||||
return this.getCompletionsFromDefinitions(
|
||||
getAlertManagerSuggestions(this.monaco),
|
||||
getGomplateSuggestions(this.monaco)
|
||||
);
|
||||
};
|
||||
|
||||
getTemplateDataSuggestions = (wordContext: string): languages.ProviderResult<languages.CompletionList> => {
|
||||
|
||||
@@ -26,7 +26,83 @@ export enum AlertmanagerTemplateFunction {
|
||||
stringSlice = 'stringSlice',
|
||||
}
|
||||
|
||||
export const availableAlertManagerFunctions = Object.values(AlertmanagerTemplateFunction);
|
||||
// list of available Gomplate functions in Alertmanager templates
|
||||
// see https://github.com/hairyhenderson/gomplate
|
||||
export const GomplateFunctions = {
|
||||
coll: [
|
||||
{
|
||||
keyword: 'coll.Dict',
|
||||
definition:
|
||||
'Creates a map with string keys from key/value pairs. All keys are converted to strings. If an odd number of arguments is provided, the last is used as the key with an empty string value.',
|
||||
usage: 'function(key string, val any, ...)',
|
||||
example: `{{ coll.Dict "name" "Frank" "age" 42 | data.ToYAML }}`,
|
||||
},
|
||||
{
|
||||
keyword: 'coll.Slice',
|
||||
definition: 'Creates a slice (like an array or list). Useful when needing to range over a bunch of variables.',
|
||||
usage: 'function(in ...any)',
|
||||
example: `{{ range coll.Slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }}`,
|
||||
},
|
||||
{
|
||||
keyword: 'coll.Append',
|
||||
definition: 'Appends a value to the end of a list. Creates a new list rather than modifying the input.',
|
||||
usage: 'function(value any, list []any)',
|
||||
example: `{{ coll.Slice 1 1 2 3 | append 5 }}`,
|
||||
},
|
||||
],
|
||||
|
||||
data: [
|
||||
{
|
||||
keyword: 'data.JSON',
|
||||
definition: 'Converts a JSON string into an object. Works for JSON Objects and Arrays.',
|
||||
usage: 'function(json string)',
|
||||
example: `{{ ($json | data.JSON).hello }}`,
|
||||
},
|
||||
{
|
||||
keyword: 'data.ToJSON',
|
||||
definition: 'Converts an object to a JSON document.',
|
||||
usage: 'function(obj any)',
|
||||
example: `{{ (\`{"foo":{"hello":"world"}}\` | json).foo | data.ToJSON }}`,
|
||||
},
|
||||
{
|
||||
keyword: 'data.ToJSONPretty',
|
||||
definition: 'Converts an object to a pretty-printed (indented) JSON document.',
|
||||
usage: 'function(indent string, obj any)',
|
||||
example: `{{ \`{"hello":"world"}\` | data.JSON | data.ToJSONPretty " " }}`,
|
||||
},
|
||||
],
|
||||
|
||||
tmpl: [
|
||||
{
|
||||
keyword: 'tmpl.Exec',
|
||||
definition:
|
||||
'Execute (render) the named template. This is equivalent to using the `template` action, except the result is returned as a string. This allows for post-processing of templates.',
|
||||
usage: 'function(name string, [context any])',
|
||||
example: `{{ tmpl.Exec "T1" | strings.ToUpper }}`,
|
||||
},
|
||||
{
|
||||
keyword: 'tmpl.Inline',
|
||||
definition:
|
||||
'Render the given string as a template, just like a nested template. If the template is given a name, it can be re-used later with the `template` keyword. A context can be provided, otherwise the default gomplate context will be used.',
|
||||
usage: 'function(partial string, context any)',
|
||||
example: `{{ tmpl.Inline "{{print \`hello world\`}}" }}`,
|
||||
},
|
||||
],
|
||||
|
||||
time: [
|
||||
{
|
||||
keyword: 'time.Now',
|
||||
definition: "Returns the current local time, as a time.Time. This wraps Go's time.Now.",
|
||||
usage: 'function()',
|
||||
example: `{{ (time.Now).UTC.Format "Day 2 of month 1 in year 2006 (timezone MST)" }}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const availableAlertManagerFunctions = [
|
||||
...Object.values(AlertmanagerTemplateFunction),
|
||||
...Object.keys(GomplateFunctions).map((namespace) => namespace),
|
||||
];
|
||||
|
||||
// boolean functions
|
||||
const booleanFunctions = ['eq', 'ne', 'lt', 'le', 'gt', 'ge'];
|
||||
|
||||
@@ -28,6 +28,14 @@ Annotations:
|
||||
{{ end }}
|
||||
`;
|
||||
|
||||
export const jsonSnippet = `
|
||||
{{ coll.Dict
|
||||
"receiver" .Receiver
|
||||
"status" .Status
|
||||
"alerts" ( len .Alerts )
|
||||
| data.ToJSONPretty " " }}
|
||||
`;
|
||||
|
||||
export const groupLabelsLoopSnippet = getKeyValueTemplate('GroupLabels.SortedPairs');
|
||||
export const commonLabelsLoopSnippet = getKeyValueTemplate('CommonLabels.SortedPairs');
|
||||
export const commonAnnotationsLoopSnippet = getKeyValueTemplate('CommonAnnotations.SortedPairs');
|
||||
|
||||
+22
-1
@@ -7,6 +7,7 @@ import {
|
||||
commonAnnotationsLoopSnippet,
|
||||
commonLabelsLoopSnippet,
|
||||
groupLabelsLoopSnippet,
|
||||
jsonSnippet,
|
||||
labelsLoopSnippet,
|
||||
} from './snippets';
|
||||
import { SuggestionDefinition } from './suggestionDefinition';
|
||||
@@ -28,6 +29,8 @@ export function getGlobalSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
{ label: 'CommonLabels', kind, detail: '[]KeyValue' },
|
||||
{ label: 'CommonAnnotations', kind, detail: '[]KeyValue' },
|
||||
{ label: 'ExternalURL', kind, detail: 'string' },
|
||||
{ label: 'GroupKey', kind, detail: 'string' },
|
||||
{ label: 'TruncatedAlerts', kind, detail: 'integer' },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -104,6 +107,12 @@ export function getAlertSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
detail: 'string',
|
||||
documentation: 'String that contains labels and values of each reduced expression in the alert.',
|
||||
},
|
||||
{
|
||||
label: { label: 'OrgID', detail: '(Alert)' },
|
||||
kind,
|
||||
detail: 'integer',
|
||||
documentation: 'The ID of the organization that owns the alert.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -169,6 +178,11 @@ export const snippets = {
|
||||
description: 'Renders a loop through annotations',
|
||||
snippet: annotationsLoopSnippet,
|
||||
},
|
||||
json: {
|
||||
label: 'json',
|
||||
description: 'Renders a JSON object',
|
||||
snippet: jsonSnippet,
|
||||
},
|
||||
};
|
||||
|
||||
// Snippets
|
||||
@@ -176,7 +190,7 @@ export function getSnippetsSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
const snippetKind = monaco.languages.CompletionItemKind.Snippet;
|
||||
const snippetInsertRule = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
||||
|
||||
const { alerts, alertDetails, groupLabels, commonLabels, commonAnnotations, labels, annotations } = snippets;
|
||||
const { alerts, alertDetails, groupLabels, commonLabels, commonAnnotations, labels, annotations, json } = snippets;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -231,5 +245,12 @@ export function getSnippetsSuggestions(monaco: Monaco): SuggestionDefinition[] {
|
||||
insertText: annotations.snippet,
|
||||
insertTextRules: snippetInsertRule,
|
||||
},
|
||||
{
|
||||
label: json.label,
|
||||
documentation: json.description,
|
||||
kind: snippetKind,
|
||||
insertText: json.snippet,
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.KeepWhitespace,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user