mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 11:44:26 -06:00
240 lines
5.9 KiB
Go
240 lines
5.9 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
)
|
|
|
|
const defaultMaxDataPoints float64 = 43200 // 12 hours at 1sec interval
|
|
const defaultIntervalMS float64 = 1000
|
|
|
|
// Duration is a type used for marshalling durations.
|
|
type Duration time.Duration
|
|
|
|
func (d Duration) String() string {
|
|
return time.Duration(d).String()
|
|
}
|
|
|
|
func (d Duration) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(time.Duration(d).Seconds())
|
|
}
|
|
|
|
func (d *Duration) UnmarshalJSON(b []byte) error {
|
|
var v interface{}
|
|
if err := json.Unmarshal(b, &v); err != nil {
|
|
return err
|
|
}
|
|
switch value := v.(type) {
|
|
case float64:
|
|
*d = Duration(time.Duration(value) * time.Second)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("invalid duration %v", v)
|
|
}
|
|
}
|
|
|
|
// RelativeTimeRange is the per query start and end time
|
|
// for requests.
|
|
type RelativeTimeRange struct {
|
|
From Duration `json:"from"`
|
|
To Duration `json:"to"`
|
|
}
|
|
|
|
// isValid checks that From duration is greater than To duration.
|
|
func (rtr *RelativeTimeRange) isValid() bool {
|
|
return rtr.From > rtr.To
|
|
}
|
|
|
|
func (rtr *RelativeTimeRange) ToTimeRange(now time.Time) backend.TimeRange {
|
|
return backend.TimeRange{
|
|
From: now.Add(-time.Duration(rtr.From)),
|
|
To: now.Add(-time.Duration(rtr.To)),
|
|
}
|
|
}
|
|
|
|
// AlertQuery represents a single query associated with an alert definition.
|
|
type AlertQuery struct {
|
|
// RefID is the unique identifier of the query, set by the frontend call.
|
|
RefID string `json:"refId"`
|
|
|
|
// QueryType is an optional identifier for the type of query.
|
|
// It can be used to distinguish different types of queries.
|
|
QueryType string `json:"queryType"`
|
|
|
|
// RelativeTimeRange is the relative Start and End of the query as sent by the frontend.
|
|
RelativeTimeRange RelativeTimeRange `json:"relativeTimeRange"`
|
|
|
|
// Grafana data source unique identifier; it should be '-100' for a Server Side Expression operation.
|
|
DatasourceUID string `json:"datasourceUid"`
|
|
|
|
// JSON is the raw JSON query and includes the above properties as well as custom properties.
|
|
Model json.RawMessage `json:"model"`
|
|
|
|
modelProps map[string]interface{}
|
|
}
|
|
|
|
func (aq *AlertQuery) setModelProps() error {
|
|
aq.modelProps = make(map[string]interface{})
|
|
err := json.Unmarshal(aq.Model, &aq.modelProps)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal query model: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsExpression returns true if the alert query is an expression.
|
|
func (aq *AlertQuery) IsExpression() (bool, error) {
|
|
return expr.IsDataSource(aq.DatasourceUID), nil
|
|
}
|
|
|
|
// setMaxDatapoints sets the model maxDataPoints if it's missing or invalid
|
|
func (aq *AlertQuery) setMaxDatapoints() error {
|
|
if aq.modelProps == nil {
|
|
err := aq.setModelProps()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
i, ok := aq.modelProps["maxDataPoints"] // GEL requires maxDataPoints inside the query JSON
|
|
if !ok {
|
|
aq.modelProps["maxDataPoints"] = defaultMaxDataPoints
|
|
}
|
|
maxDataPoints, ok := i.(float64)
|
|
if !ok || maxDataPoints == 0 {
|
|
aq.modelProps["maxDataPoints"] = defaultMaxDataPoints
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (aq *AlertQuery) GetMaxDatapoints() (int64, error) {
|
|
err := aq.setMaxDatapoints()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
maxDataPoints, ok := aq.modelProps["maxDataPoints"].(float64)
|
|
if !ok {
|
|
return 0, fmt.Errorf("failed to cast maxDataPoints to float64: %v", aq.modelProps["maxDataPoints"])
|
|
}
|
|
return int64(maxDataPoints), nil
|
|
}
|
|
|
|
// setIntervalMS sets the model IntervalMs if it's missing or invalid
|
|
func (aq *AlertQuery) setIntervalMS() error {
|
|
if aq.modelProps == nil {
|
|
err := aq.setModelProps()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
i, ok := aq.modelProps["intervalMs"] // GEL requires intervalMs inside the query JSON
|
|
if !ok {
|
|
aq.modelProps["intervalMs"] = defaultIntervalMS
|
|
}
|
|
intervalMs, ok := i.(float64)
|
|
if !ok || intervalMs == 0 {
|
|
aq.modelProps["intervalMs"] = defaultIntervalMS
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (aq *AlertQuery) getIntervalMS() (int64, error) {
|
|
err := aq.setIntervalMS()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
intervalMs, ok := aq.modelProps["intervalMs"].(float64)
|
|
if !ok {
|
|
return 0, fmt.Errorf("failed to cast intervalMs to float64: %v", aq.modelProps["intervalMs"])
|
|
}
|
|
return int64(intervalMs), nil
|
|
}
|
|
|
|
func (aq *AlertQuery) GetIntervalDuration() (time.Duration, error) {
|
|
err := aq.setIntervalMS()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
intervalMs, ok := aq.modelProps["intervalMs"].(float64)
|
|
if !ok {
|
|
return 0, fmt.Errorf("failed to cast intervalMs to float64: %v", aq.modelProps["intervalMs"])
|
|
}
|
|
return time.Duration(intervalMs) * time.Millisecond, nil
|
|
}
|
|
|
|
// GetDatasource returns the query datasource identifier.
|
|
func (aq *AlertQuery) GetDatasource() (string, error) {
|
|
return aq.DatasourceUID, nil
|
|
}
|
|
|
|
func (aq *AlertQuery) GetModel() ([]byte, error) {
|
|
err := aq.setMaxDatapoints()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = aq.setIntervalMS()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
model, err := json.Marshal(aq.modelProps)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to marshal query model: %w", err)
|
|
}
|
|
return model, nil
|
|
}
|
|
|
|
func (aq *AlertQuery) setQueryType() error {
|
|
if aq.modelProps == nil {
|
|
err := aq.setModelProps()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
i, ok := aq.modelProps["queryType"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
queryType, ok := i.(string)
|
|
if !ok {
|
|
return fmt.Errorf("failed to get queryType from query model: %v", i)
|
|
}
|
|
|
|
aq.QueryType = queryType
|
|
return nil
|
|
}
|
|
|
|
// PreSave sets query's properties.
|
|
// It should be called before being saved.
|
|
func (aq *AlertQuery) PreSave() error {
|
|
if err := aq.setQueryType(); err != nil {
|
|
return fmt.Errorf("failed to set query type to query model: %w", err)
|
|
}
|
|
|
|
// override model
|
|
model, err := aq.GetModel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
aq.Model = model
|
|
|
|
isExpression, err := aq.IsExpression()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok := isExpression || aq.RelativeTimeRange.isValid(); !ok {
|
|
return fmt.Errorf("invalid relative time range: %+v", aq.RelativeTimeRange)
|
|
}
|
|
return nil
|
|
}
|