Alerting: Update forking request handlers to use the same errors (#52965)

* generalize error handling in forking request handlers
* remove MatchesBackend and change test to test Can
* add 404 to route specs
* change backendTypeByUID to getDatasourceByUID of expected type
* use common errors in api testing
* handle 401 in errorToResponse
* replace backend type error with "unexpected datasource type"
* update swagger spec
pull/53138/head
Yuriy Tseretyan 3 years ago committed by GitHub
parent 28e27e1365
commit 718620c197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      pkg/services/ngalert/api/api_testing.go
  2. 38
      pkg/services/ngalert/api/errors.go
  3. 50
      pkg/services/ngalert/api/forking_alertmanager.go
  4. 34
      pkg/services/ngalert/api/forking_prometheus.go
  5. 85
      pkg/services/ngalert/api/forking_ruler.go
  6. 488
      pkg/services/ngalert/api/tooling/api.json
  7. 41
      pkg/services/ngalert/api/tooling/definitions/alertmanager.go
  8. 38
      pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go
  9. 6
      pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go
  10. 2
      pkg/services/ngalert/api/tooling/definitions/prom.go
  11. 1
      pkg/services/ngalert/api/tooling/definitions/testing.go
  12. 628
      pkg/services/ngalert/api/tooling/post.json
  13. 615
      pkg/services/ngalert/api/tooling/spec.json
  14. 26
      pkg/services/ngalert/api/util.go
  15. 1727
      public/api-merged.json

@ -1,8 +1,6 @@
package api
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
@ -31,13 +29,13 @@ type TestingApiSrv struct {
func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response {
if body.Type() != apimodels.GrafanaBackend || body.GrafanaManagedCondition == nil {
return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "")
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.GrafanaBackend, body.Type().String()))
}
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: body.GrafanaManagedCondition.Data}, func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
}) {
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "")
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
}
evalCond := ngmodels.Condition{
@ -65,22 +63,21 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body a
func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload, datasourceUID string) response.Response {
if body.Type() != apimodels.LoTexRulerBackend {
return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "")
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.LoTexRulerBackend, body.Type().String()))
}
var path string
ds, err := srv.DatasourceCache.GetDatasourceByUID(context.Background(), datasourceUID, c.SignedInUser, c.SkipCache)
ds, err := getDatasourceByUID(c, srv.DatasourceCache, apimodels.LoTexRulerBackend)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get datasource")
return errorToResponse(err)
}
var path string
switch ds.Type {
case "loki":
path = "loki/api/v1/query"
case "prometheus":
path = "api/v1/query"
default:
return ErrResp(http.StatusBadRequest, fmt.Errorf("unexpected datasource type %s", ds.Type), "")
// this should not happen because getDatasourceByUID would not return the data source
return errorToResponse(unexpectedDatasourceTypeError(ds.Type, "loki, prometheus"))
}
t := timeNow()

@ -0,0 +1,38 @@
package api
import (
"errors"
"fmt"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/services/datasources"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
)
var (
errUnexpectedDatasourceType = errors.New("unexpected datasource type")
)
func unexpectedDatasourceTypeError(actual string, expected string) error {
return fmt.Errorf("%w '%s', expected %s", errUnexpectedDatasourceType, actual, expected)
}
func backendTypeDoesNotMatchPayloadTypeError(backendType apimodels.Backend, payloadType string) error {
return fmt.Errorf("unexpected backend type (%s) for payload type (%s)",
backendType.String(),
payloadType,
)
}
func errorToResponse(err error) response.Response {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return ErrResp(404, err, "")
}
if errors.Is(err, errUnexpectedDatasourceType) {
return ErrResp(400, err, "")
}
if errors.Is(err, ErrAuthorization) {
return ErrResp(401, err, "")
}
return ErrResp(500, err, "")
}

@ -1,8 +1,6 @@
package api
import (
"fmt"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
@ -25,23 +23,17 @@ func NewForkingAM(datasourceCache datasources.CacheService, proxy *LotexAM, graf
}
func (f *AlertmanagerApiHandler) getService(ctx *models.ReqContext) (*LotexAM, error) {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
_, err := getDatasourceByUID(ctx, f.DatasourceCache, apimodels.AlertmanagerBackend)
if err != nil {
return nil, err
}
switch t {
case apimodels.AlertmanagerBackend:
return f.AMSvc, nil
default:
return nil, fmt.Errorf("unexpected backend type (%v)", t)
}
return f.AMSvc, nil
}
func (f *AlertmanagerApiHandler) handleRouteGetAMStatus(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
return errorToResponse(err)
}
return s.RouteGetAMStatus(ctx)
@ -50,7 +42,7 @@ func (f *AlertmanagerApiHandler) handleRouteGetAMStatus(ctx *models.ReqContext,
func (f *AlertmanagerApiHandler) handleRouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteCreateSilence(ctx, body)
@ -59,7 +51,7 @@ func (f *AlertmanagerApiHandler) handleRouteCreateSilence(ctx *models.ReqContext
func (f *AlertmanagerApiHandler) handleRouteDeleteAlertingConfig(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteDeleteAlertingConfig(ctx)
@ -68,7 +60,7 @@ func (f *AlertmanagerApiHandler) handleRouteDeleteAlertingConfig(ctx *models.Req
func (f *AlertmanagerApiHandler) handleRouteDeleteSilence(ctx *models.ReqContext, silenceID string, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteDeleteSilence(ctx, silenceID)
@ -77,7 +69,7 @@ func (f *AlertmanagerApiHandler) handleRouteDeleteSilence(ctx *models.ReqContext
func (f *AlertmanagerApiHandler) handleRouteGetAlertingConfig(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteGetAlertingConfig(ctx)
@ -86,7 +78,7 @@ func (f *AlertmanagerApiHandler) handleRouteGetAlertingConfig(ctx *models.ReqCon
func (f *AlertmanagerApiHandler) handleRouteGetAMAlertGroups(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteGetAMAlertGroups(ctx)
@ -95,7 +87,7 @@ func (f *AlertmanagerApiHandler) handleRouteGetAMAlertGroups(ctx *models.ReqCont
func (f *AlertmanagerApiHandler) handleRouteGetAMAlerts(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteGetAMAlerts(ctx)
@ -104,7 +96,7 @@ func (f *AlertmanagerApiHandler) handleRouteGetAMAlerts(ctx *models.ReqContext,
func (f *AlertmanagerApiHandler) handleRouteGetSilence(ctx *models.ReqContext, silenceID string, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteGetSilence(ctx, silenceID)
@ -113,7 +105,7 @@ func (f *AlertmanagerApiHandler) handleRouteGetSilence(ctx *models.ReqContext, s
func (f *AlertmanagerApiHandler) handleRouteGetSilences(ctx *models.ReqContext, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RouteGetSilences(ctx)
@ -122,25 +114,18 @@ func (f *AlertmanagerApiHandler) handleRouteGetSilences(ctx *models.ReqContext,
func (f *AlertmanagerApiHandler) handleRoutePostAlertingConfig(ctx *models.ReqContext, body apimodels.PostableUserConfig, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
b, err := backendTypeByUID(ctx, f.DatasourceCache)
if err != nil {
return ErrResp(400, err, "")
if !body.AlertmanagerConfig.ReceiverType().Can(apimodels.AlertmanagerReceiverType) {
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.AlertmanagerBackend, body.AlertmanagerConfig.ReceiverType().String()))
}
if err := body.AlertmanagerConfig.ReceiverType().MatchesBackend(b); err != nil {
return ErrResp(400, err, "bad match")
}
return s.RoutePostAlertingConfig(ctx, body)
}
func (f *AlertmanagerApiHandler) handleRoutePostAMAlerts(ctx *models.ReqContext, body apimodels.PostableAlerts, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RoutePostAMAlerts(ctx, body)
@ -149,7 +134,7 @@ func (f *AlertmanagerApiHandler) handleRoutePostAMAlerts(ctx *models.ReqContext,
func (f *AlertmanagerApiHandler) handleRoutePostTestReceivers(ctx *models.ReqContext, body apimodels.TestReceiversConfigBodyParams, dsUID string) response.Response {
s, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
return errorToResponse(err)
}
return s.RoutePostTestReceivers(ctx, body)
@ -196,6 +181,9 @@ func (f *AlertmanagerApiHandler) handleRoutePostGrafanaAMAlerts(ctx *models.ReqC
}
func (f *AlertmanagerApiHandler) handleRoutePostGrafanaAlertingConfig(ctx *models.ReqContext, conf apimodels.PostableUserConfig) response.Response {
if !conf.AlertmanagerConfig.ReceiverType().Can(apimodels.GrafanaReceiverType) {
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.GrafanaBackend, conf.AlertmanagerConfig.ReceiverType().String()))
}
return f.GrafanaSvc.RoutePostAlertingConfig(ctx, conf)
}

@ -1,8 +1,6 @@
package api
import (
"fmt"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
@ -25,31 +23,19 @@ func NewForkingProm(datasourceCache datasources.CacheService, proxy *LotexProm,
}
func (f *PrometheusApiHandler) handleRouteGetAlertStatuses(ctx *models.ReqContext, dsUID string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.ProxySvc.RouteGetAlertStatuses(ctx)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteGetAlertStatuses(ctx)
}
func (f *PrometheusApiHandler) handleRouteGetRuleStatuses(ctx *models.ReqContext, dsUID string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.ProxySvc.RouteGetRuleStatuses(ctx)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteGetRuleStatuses(ctx)
}
func (f *PrometheusApiHandler) handleRouteGetGrafanaAlertStatuses(ctx *models.ReqContext) response.Response {
@ -59,3 +45,11 @@ func (f *PrometheusApiHandler) handleRouteGetGrafanaAlertStatuses(ctx *models.Re
func (f *PrometheusApiHandler) handleRouteGetGrafanaRuleStatuses(ctx *models.ReqContext) response.Response {
return f.GrafanaSvc.RouteGetRuleStatuses(ctx)
}
func (f *PrometheusApiHandler) getService(ctx *models.ReqContext) (*LotexProm, error) {
_, err := getDatasourceByUID(ctx, f.DatasourceCache, apimodels.LoTexRulerBackend)
if err != nil {
return nil, err
}
return f.ProxySvc, nil
}

@ -1,8 +1,6 @@
package api
import (
"fmt"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
@ -25,87 +23,54 @@ func NewForkingRuler(datasourceCache datasources.CacheService, lotex *LotexRuler
}
func (f *RulerApiHandler) handleRouteDeleteNamespaceRulesConfig(ctx *models.ReqContext, dsUID, namespace string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RouteDeleteNamespaceRulesConfig(ctx, namespace)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteDeleteNamespaceRulesConfig(ctx, namespace)
}
func (f *RulerApiHandler) handleRouteDeleteRuleGroupConfig(ctx *models.ReqContext, dsUID, namespace, group string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RouteDeleteRuleGroupConfig(ctx, namespace, group)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteDeleteRuleGroupConfig(ctx, namespace, group)
}
func (f *RulerApiHandler) handleRouteGetNamespaceRulesConfig(ctx *models.ReqContext, dsUID, namespace string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RouteGetNamespaceRulesConfig(ctx, namespace)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteGetNamespaceRulesConfig(ctx, namespace)
}
func (f *RulerApiHandler) handleRouteGetRulegGroupConfig(ctx *models.ReqContext, dsUID, namespace, group string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RouteGetRulegGroupConfig(ctx, namespace, group)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteGetRulegGroupConfig(ctx, namespace, group)
}
func (f *RulerApiHandler) handleRouteGetRulesConfig(ctx *models.ReqContext, dsUID string) response.Response {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
switch t {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RouteGetRulesConfig(ctx)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", t), "")
return errorToResponse(err)
}
return t.RouteGetRulesConfig(ctx)
}
func (f *RulerApiHandler) handleRoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig, dsUID, namespace string) response.Response {
backendType, err := backendTypeByUID(ctx, f.DatasourceCache)
t, err := f.getService(ctx)
if err != nil {
return ErrResp(400, err, "")
}
payloadType := conf.Type()
if backendType != payloadType {
return ErrResp(400, fmt.Errorf("unexpected backend type (%v) vs payload type (%v)", backendType, payloadType), "")
return errorToResponse(err)
}
switch backendType {
case apimodels.LoTexRulerBackend:
return f.LotexRuler.RoutePostNameRulesConfig(ctx, conf, namespace)
default:
return ErrResp(400, fmt.Errorf("unexpected backend type (%v)", backendType), "")
if conf.Type() != apimodels.LoTexRulerBackend {
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.LoTexRulerBackend, conf.Type().String()))
}
return t.RoutePostNameRulesConfig(ctx, conf, namespace)
}
func (f *RulerApiHandler) handleRouteDeleteNamespaceGrafanaRulesConfig(ctx *models.ReqContext, namespace string) response.Response {
@ -131,7 +96,15 @@ func (f *RulerApiHandler) handleRouteGetGrafanaRulesConfig(ctx *models.ReqContex
func (f *RulerApiHandler) handleRoutePostNameGrafanaRulesConfig(ctx *models.ReqContext, conf apimodels.PostableRuleGroupConfig, namespace string) response.Response {
payloadType := conf.Type()
if payloadType != apimodels.GrafanaBackend {
return ErrResp(400, fmt.Errorf("unexpected backend type (%v) vs payload type (%v)", apimodels.GrafanaBackend, payloadType), "")
return errorToResponse(backendTypeDoesNotMatchPayloadTypeError(apimodels.GrafanaBackend, conf.Type().String()))
}
return f.GrafanaRuler.RoutePostNameRulesConfig(ctx, conf, namespace)
}
func (f *RulerApiHandler) getService(ctx *models.ReqContext) (*LotexRuler, error) {
_, err := getDatasourceByUID(ctx, f.DatasourceCache, apimodels.LoTexRulerBackend)
if err != nil {
return nil, err
}
return f.LotexRuler, nil
}

@ -96,9 +96,6 @@
"title": "AlertManager models a configured Alert Manager.",
"type": "object"
},
"AlertManagerNotFound": {
"type": "object"
},
"AlertManagerNotReady": {
"type": "object"
},
@ -284,6 +281,9 @@
},
"type": "object"
},
"AlertStateType": {
"type": "string"
},
"AlertingRule": {
"description": "adapted from cortex",
"properties": {
@ -401,6 +401,11 @@
"title": "BasicAuth contains basic HTTP authentication credentials.",
"type": "object"
},
"ConfFloat64": {
"description": "ConfFloat64 is a float64. It Marshals float64 values of NaN of Inf\nto null.",
"format": "double",
"type": "number"
},
"Config": {
"properties": {
"global": {
@ -437,6 +442,39 @@
},
"type": "array"
},
"DataLink": {
"description": "DataLink define what",
"properties": {
"targetBlank": {
"type": "boolean"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"DataResponse": {
"description": "A map of RefIDs (unique query identifiers) to this type makes up the Responses property of a QueryDataResponse.\nThe Error property is used to allow for partial success responses from the containing QueryDataResponse.",
"properties": {
"Error": {
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
"type": "string"
},
"Frames": {
"$ref": "#/definitions/Frames"
}
},
"title": "DataResponse contains the results from a DataQuery.",
"type": "object"
},
"DataTopic": {
"title": "DataTopic is used to identify which topic the frame should be assigned to.",
"type": "string"
},
"DateTime": {
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
"format": "date-time",
@ -664,6 +702,197 @@
"Failure": {
"$ref": "#/definitions/ResponseDetails"
},
"Field": {
"description": "A Field is essentially a slice of various types with extra properties and methods.\nSee NewField() for supported types.\n\nThe slice data in the Field is a not exported, so methods on the Field are used to to manipulate its data.",
"properties": {
"config": {
"$ref": "#/definitions/FieldConfig"
},
"labels": {
"$ref": "#/definitions/FrameLabels"
},
"name": {
"description": "Name is default identifier of the field. The name does not have to be unique, but the combination\nof name and Labels should be unique for proper behavior in all situations.",
"type": "string"
}
},
"title": "Field represents a typed column of data within a Frame.",
"type": "object"
},
"FieldConfig": {
"properties": {
"color": {
"additionalProperties": {
"type": "object"
},
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object"
},
"custom": {
"additionalProperties": {
"type": "object"
},
"description": "Panel Specific Values",
"type": "object"
},
"decimals": {
"format": "uint16",
"type": "integer"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"format": "double",
"type": "number"
},
"links": {
"description": "The behavior when clicking on a result",
"items": {
"$ref": "#/definitions/DataLink"
},
"type": "array"
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
},
"title": "FieldConfig represents the display properties for a Field.",
"type": "object"
},
"Frame": {
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
"properties": {
"Fields": {
"description": "Fields are the columns of a frame.\nAll Fields must be of the same the length when marshalling the Frame for transmission.",
"items": {
"$ref": "#/definitions/Field"
},
"type": "array"
},
"Meta": {
"$ref": "#/definitions/FrameMeta"
},
"Name": {
"description": "Name is used in some Grafana visualizations.",
"type": "string"
},
"RefID": {
"description": "RefID is a property that can be set to match a Frame to its originating query.",
"type": "string"
}
},
"title": "Frame is a columnar data structure where each column is a Field.",
"type": "object"
},
"FrameLabels": {
"additionalProperties": {
"type": "string"
},
"description": "Labels are used to add metadata to an object. The JSON will always be sorted keys",
"type": "object"
},
"FrameMeta": {
"description": "https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L11\nNOTE -- in javascript this can accept any `[key: string]: any;` however\nthis interface only exposes the values we want to be exposed",
"properties": {
"channel": {
"description": "Channel is the path to a stream in grafana live that has real-time updates for this data.",
"type": "string"
},
"custom": {
"description": "Custom datasource specific values.",
"type": "object"
},
"dataTopic": {
"$ref": "#/definitions/DataTopic"
},
"executedQueryString": {
"description": "ExecutedQueryString is the raw query sent to the underlying system. All macros and templating\nhave been applied. When metadata contains this value, it will be shown in the query inspector.",
"type": "string"
},
"notices": {
"description": "Notices provide additional information about the data in the Frame that\nGrafana can display to the user in the user interface.",
"items": {
"$ref": "#/definitions/Notice"
},
"type": "array"
},
"path": {
"description": "Path is a browsable path on the datasource.",
"type": "string"
},
"pathSeparator": {
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
"type": "string"
},
"preferredVisualisationType": {
"$ref": "#/definitions/VisType"
},
"stats": {
"description": "Stats is an array of query result statistics.",
"items": {
"$ref": "#/definitions/QueryStat"
},
"type": "array"
},
"type": {
"$ref": "#/definitions/FrameType"
}
},
"title": "FrameMeta matches:",
"type": "object"
},
"FrameType": {
"description": "A FrameType string, when present in a frame's metadata, asserts that the\nframe's structure conforms to the FrameType's specification.\nThis property is currently optional, so FrameType may be FrameTypeUnknown even if the properties of\nthe Frame correspond to a defined FrameType.",
"type": "string"
},
"Frames": {
"description": "It is the main data container within a backend.DataResponse.",
"items": {
"$ref": "#/definitions/Frame"
},
"title": "Frames is a slice of Frame pointers.",
"type": "array"
},
"GettableAlertmanagers": {
"properties": {
"data": {
@ -1176,6 +1405,11 @@
},
"type": "object"
},
"InspectType": {
"format": "int64",
"title": "InspectType is a type for the Inspect property of a Notice.",
"type": "integer"
},
"Json": {
"type": "object"
},
@ -1217,6 +1451,82 @@
},
"type": "array"
},
"LegacyAlert": {
"properties": {
"Created": {
"format": "date-time",
"type": "string"
},
"DashboardId": {
"format": "int64",
"type": "integer"
},
"EvalData": {
"$ref": "#/definitions/Json"
},
"ExecutionError": {
"type": "string"
},
"For": {
"$ref": "#/definitions/Duration"
},
"Frequency": {
"format": "int64",
"type": "integer"
},
"Handler": {
"format": "int64",
"type": "integer"
},
"Id": {
"format": "int64",
"type": "integer"
},
"Message": {
"type": "string"
},
"Name": {
"type": "string"
},
"NewStateDate": {
"format": "date-time",
"type": "string"
},
"OrgId": {
"format": "int64",
"type": "integer"
},
"PanelId": {
"format": "int64",
"type": "integer"
},
"Settings": {
"$ref": "#/definitions/Json"
},
"Severity": {
"type": "string"
},
"Silenced": {
"type": "boolean"
},
"State": {
"$ref": "#/definitions/AlertStateType"
},
"StateChanges": {
"format": "int64",
"type": "integer"
},
"Updated": {
"format": "date-time",
"type": "string"
},
"Version": {
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"MatchRegexps": {
"additionalProperties": {
"$ref": "#/definitions/Regexp"
@ -1329,6 +1639,31 @@
"NotFound": {
"type": "object"
},
"Notice": {
"properties": {
"inspect": {
"$ref": "#/definitions/InspectType"
},
"link": {
"description": "Link is an optional link for display in the user interface and can be an\nabsolute URL or a path relative to Grafana's root url.",
"type": "string"
},
"severity": {
"$ref": "#/definitions/NoticeSeverity"
},
"text": {
"description": "Text is freeform descriptive text for the notice.",
"type": "string"
}
},
"title": "Notice provides a structure for presenting notifications in Grafana's user interface.",
"type": "object"
},
"NoticeSeverity": {
"format": "int64",
"title": "NoticeSeverity is a type for the Severity property of a Notice.",
"type": "integer"
},
"NotifierConfig": {
"properties": {
"send_resolved": {
@ -2008,6 +2343,91 @@
},
"type": "object"
},
"QueryStat": {
"description": "The embedded FieldConfig's display name must be set.\nIt corresponds to the QueryResultMetaStat on the frontend (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L53).",
"properties": {
"color": {
"additionalProperties": {
"type": "object"
},
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object"
},
"custom": {
"additionalProperties": {
"type": "object"
},
"description": "Panel Specific Values",
"type": "object"
},
"decimals": {
"format": "uint16",
"type": "integer"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"format": "double",
"type": "number"
},
"links": {
"description": "The behavior when clicking on a result",
"items": {
"$ref": "#/definitions/DataLink"
},
"type": "array"
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"value": {
"format": "double",
"type": "number"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
},
"title": "QueryStat is used for storing arbitrary statistics metadata related to a query and its result, e.g. total request time, data processing time.",
"type": "object"
},
"Receiver": {
"properties": {
"email_configs": {
@ -2097,6 +2517,14 @@
},
"type": "object"
},
"Responses": {
"additionalProperties": {
"$ref": "#/definitions/DataResponse"
},
"description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataResponses' frames based on these RefIDs.",
"title": "Responses is a map of RefIDs (Unique Query ID) to DataResponses.",
"type": "object"
},
"Route": {
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
"properties": {
@ -2650,6 +3078,41 @@
},
"type": "object"
},
"Threshold": {
"description": "Threshold a single step on the threshold list",
"properties": {
"color": {
"type": "string"
},
"state": {
"type": "string"
},
"value": {
"$ref": "#/definitions/ConfFloat64"
}
},
"type": "object"
},
"ThresholdsConfig": {
"description": "ThresholdsConfig setup thresholds",
"properties": {
"mode": {
"$ref": "#/definitions/ThresholdsMode"
},
"steps": {
"description": "Must be sorted by 'value', first value is always -Infinity",
"items": {
"$ref": "#/definitions/Threshold"
},
"type": "array"
}
},
"type": "object"
},
"ThresholdsMode": {
"description": "ThresholdsMode absolute or percentage",
"type": "string"
},
"TimeInterval": {
"description": "TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained\nwithin the interval.",
"properties": {
@ -2751,6 +3214,16 @@
},
"type": "object"
},
"ValueMapping": {
"description": "ValueMapping allows mapping input values to text and color",
"type": "object"
},
"ValueMappings": {
"items": {
"$ref": "#/definitions/ValueMapping"
},
"type": "array"
},
"Vector": {
"description": "Vector is basically only an alias for model.Samples, but the\ncontract is that in a Vector, all Samples have the same timestamp.",
"items": {
@ -2800,6 +3273,10 @@
"title": "VictorOpsConfig configures notifications via VictorOps.",
"type": "object"
},
"VisType": {
"title": "VisType is used to indicate how the data should be visualized in explore.",
"type": "string"
},
"WebhookConfig": {
"properties": {
"http_config": {
@ -2929,7 +3406,6 @@
"type": "object"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"items": {
"$ref": "#/definitions/alertGroup"
},
@ -3037,6 +3513,7 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"properties": {
"annotations": {
"$ref": "#/definitions/labelSet"
@ -3099,7 +3576,6 @@
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@ -3148,6 +3624,7 @@
"type": "object"
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
"items": {
"$ref": "#/definitions/gettableSilence"
},
@ -3296,7 +3773,6 @@
"type": "object"
},
"receiver": {
"description": "Receiver receiver",
"properties": {
"name": {
"description": "name",

@ -38,6 +38,7 @@ import (
// Responses:
// 201: Ack
// 400: ValidationError
// 404: NotFound
// swagger:route GET /api/alertmanager/grafana/config/api/v1/alerts alertmanager RouteGetGrafanaAlertingConfig
//
@ -54,6 +55,7 @@ import (
// Responses:
// 200: GettableUserConfig
// 400: ValidationError
// 404: NotFound
// swagger:route DELETE /api/alertmanager/grafana/config/api/v1/alerts alertmanager RouteDeleteGrafanaAlertingConfig
//
@ -70,6 +72,7 @@ import (
// Responses:
// 200: Ack
// 400: ValidationError
// 404: NotFound
// swagger:route GET /api/alertmanager/grafana/api/v2/status alertmanager RouteGetGrafanaAMStatus
//
@ -86,6 +89,7 @@ import (
// Responses:
// 200: GettableStatus
// 400: ValidationError
// 404: NotFound
// swagger:route GET /api/alertmanager/grafana/api/v2/alerts alertmanager RouteGetGrafanaAMAlerts
//
@ -102,6 +106,7 @@ import (
// Responses:
// 200: gettableAlerts
// 400: ValidationError
// 404: NotFound
// swagger:route POST /api/alertmanager/grafana/api/v2/alerts alertmanager RoutePostGrafanaAMAlerts
//
@ -118,6 +123,7 @@ import (
// Responses:
// 200: Ack
// 400: ValidationError
// 404: NotFound
// swagger:route GET /api/alertmanager/grafana/api/v2/alerts/groups alertmanager RouteGetGrafanaAMAlertGroups
//
@ -134,6 +140,7 @@ import (
// Responses:
// 200: alertGroups
// 400: ValidationError
// 404: NotFound
// swagger:route POST /api/alertmanager/grafana/config/api/v1/receivers/test alertmanager RoutePostTestGrafanaReceivers
//
@ -145,7 +152,7 @@ import (
// 207: MultiStatus
// 400: ValidationError
// 403: PermissionDenied
// 404: AlertManagerNotFound
// 404: NotFound
// 408: Failure
// 409: AlertManagerNotReady
@ -159,7 +166,7 @@ import (
// 207: MultiStatus
// 400: ValidationError
// 403: PermissionDenied
// 404: AlertManagerNotFound
// 404: NotFound
// 408: Failure
// 409: AlertManagerNotReady
@ -178,6 +185,7 @@ import (
// Responses:
// 200: gettableSilences
// 400: ValidationError
// 404: NotFound
// swagger:route POST /api/alertmanager/grafana/api/v2/silences alertmanager RouteCreateGrafanaSilence
//
@ -194,6 +202,7 @@ import (
// Responses:
// 201: gettableSilence
// 400: ValidationError
// 404: NotFound
// swagger:route GET /api/alertmanager/grafana/api/v2/silence/{SilenceId} alertmanager RouteGetGrafanaSilence
//
@ -210,6 +219,7 @@ import (
// Responses:
// 200: gettableSilence
// 400: ValidationError
// 404: NotFound
// swagger:route DELETE /api/alertmanager/grafana/api/v2/silence/{SilenceId} alertmanager RouteDeleteGrafanaSilence
//
@ -226,13 +236,11 @@ import (
// Responses:
// 200: Ack
// 400: ValidationError
// 404: NotFound
// swagger:model
type PermissionDenied struct{}
// swagger:model
type AlertManagerNotFound struct{}
// swagger:model
type AlertManagerNotReady struct{}
@ -1001,29 +1009,6 @@ func (r ReceiverType) String() string {
// are valid in all backends.
func (r ReceiverType) Can(other ReceiverType) bool { return r&other != 0 }
// MatchesBackend determines if a config payload can be sent to a particular backend type
func (r ReceiverType) MatchesBackend(backend Backend) error {
msg := func(backend Backend, receiver ReceiverType) error {
return fmt.Errorf(
"unexpected backend type (%s) for receiver type (%s)",
backend.String(),
receiver.String(),
)
}
var ok bool
switch backend {
case GrafanaBackend:
ok = r.Can(GrafanaReceiverType)
case AlertmanagerBackend:
ok = r.Can(AlertmanagerReceiverType)
default:
}
if !ok {
return msg(backend, r)
}
return nil
}
type GettableApiReceiver struct {
config.Receiver `yaml:",inline"`
GettableGrafanaReceivers `yaml:",inline"`

@ -888,53 +888,43 @@ func Test_ReceiverMatchesBackend(t *testing.T) {
for _, tc := range []struct {
desc string
rec ReceiverType
b Backend
err bool
b ReceiverType
ok bool
}{
{
desc: "graf=graf",
rec: GrafanaReceiverType,
b: GrafanaBackend,
err: false,
b: GrafanaReceiverType,
ok: true,
},
{
desc: "empty=graf",
rec: EmptyReceiverType,
b: GrafanaBackend,
err: false,
b: GrafanaReceiverType,
ok: true,
},
{
desc: "am=am",
rec: AlertmanagerReceiverType,
b: AlertmanagerBackend,
err: false,
b: AlertmanagerReceiverType,
ok: true,
},
{
desc: "empty=am",
rec: EmptyReceiverType,
b: AlertmanagerBackend,
err: false,
b: AlertmanagerReceiverType,
ok: true,
},
{
desc: "graf!=am",
rec: GrafanaReceiverType,
b: AlertmanagerBackend,
err: true,
},
{
desc: "am!=ruler",
rec: GrafanaReceiverType,
b: LoTexRulerBackend,
err: true,
b: AlertmanagerReceiverType,
ok: false,
},
} {
t.Run(tc.desc, func(t *testing.T) {
err := tc.rec.MatchesBackend(tc.b)
if tc.err {
require.NotNil(t, err)
} else {
require.Nil(t, err)
}
ok := tc.rec.Can(tc.b)
require.Equal(t, tc.ok, ok)
})
}
}

@ -30,6 +30,7 @@ import (
//
// Responses:
// 202: NamespaceConfigResponse
// 404: NotFound
// swagger:route POST /api/ruler/grafana/api/v1/rules/{Namespace} ruler RoutePostNameGrafanaRulesConfig
//
@ -53,6 +54,7 @@ import (
//
// Responses:
// 202: Ack
// 404: NotFound
// swagger:route Get /api/ruler/grafana/api/v1/rules/{Namespace} ruler RouteGetNamespaceGrafanaRulesConfig
//
@ -73,6 +75,7 @@ import (
//
// Responses:
// 202: NamespaceConfigResponse
// 404: NotFound
// swagger:route Delete /api/ruler/grafana/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceGrafanaRulesConfig
//
@ -87,6 +90,7 @@ import (
//
// Responses:
// 202: Ack
// 404: NotFound
// swagger:route Get /api/ruler/grafana/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetGrafanaRuleGroupConfig
//
@ -107,6 +111,7 @@ import (
//
// Responses:
// 202: RuleGroupConfigResponse
// 404: NotFound
// swagger:route Delete /api/ruler/grafana/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteGrafanaRuleGroupConfig
//
@ -121,6 +126,7 @@ import (
//
// Responses:
// 202: Ack
// 404: NotFound
// swagger:parameters RoutePostNameRulesConfig RoutePostNameGrafanaRulesConfig
type NamespaceConfig struct {

@ -19,6 +19,7 @@ import (
//
// Responses:
// 200: RuleResponse
// 404: NotFound
// swagger:route GET /api/prometheus/grafana/api/v1/alerts prometheus RouteGetGrafanaAlertStatuses
//
@ -33,6 +34,7 @@ import (
//
// Responses:
// 200: AlertResponse
// 404: NotFound
// swagger:model
type RuleResponse struct {

@ -38,6 +38,7 @@ import (
//
// Responses:
// 200: TestRuleResponse
// 404: NotFound
// swagger:route Post /api/v1/eval testing RouteEvalQueries
//

@ -96,9 +96,6 @@
"title": "AlertManager models a configured Alert Manager.",
"type": "object"
},
"AlertManagerNotFound": {
"type": "object"
},
"AlertManagerNotReady": {
"type": "object"
},
@ -284,6 +281,9 @@
},
"type": "object"
},
"AlertStateType": {
"type": "string"
},
"AlertingRule": {
"description": "adapted from cortex",
"properties": {
@ -401,6 +401,11 @@
"title": "BasicAuth contains basic HTTP authentication credentials.",
"type": "object"
},
"ConfFloat64": {
"description": "ConfFloat64 is a float64. It Marshals float64 values of NaN of Inf\nto null.",
"format": "double",
"type": "number"
},
"Config": {
"properties": {
"global": {
@ -437,6 +442,39 @@
},
"type": "array"
},
"DataLink": {
"description": "DataLink define what",
"properties": {
"targetBlank": {
"type": "boolean"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
}
},
"type": "object"
},
"DataResponse": {
"description": "A map of RefIDs (unique query identifiers) to this type makes up the Responses property of a QueryDataResponse.\nThe Error property is used to allow for partial success responses from the containing QueryDataResponse.",
"properties": {
"Error": {
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
"type": "string"
},
"Frames": {
"$ref": "#/definitions/Frames"
}
},
"title": "DataResponse contains the results from a DataQuery.",
"type": "object"
},
"DataTopic": {
"title": "DataTopic is used to identify which topic the frame should be assigned to.",
"type": "string"
},
"DateTime": {
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
"format": "date-time",
@ -664,6 +702,197 @@
"Failure": {
"$ref": "#/definitions/ResponseDetails"
},
"Field": {
"description": "A Field is essentially a slice of various types with extra properties and methods.\nSee NewField() for supported types.\n\nThe slice data in the Field is a not exported, so methods on the Field are used to to manipulate its data.",
"properties": {
"config": {
"$ref": "#/definitions/FieldConfig"
},
"labels": {
"$ref": "#/definitions/FrameLabels"
},
"name": {
"description": "Name is default identifier of the field. The name does not have to be unique, but the combination\nof name and Labels should be unique for proper behavior in all situations.",
"type": "string"
}
},
"title": "Field represents a typed column of data within a Frame.",
"type": "object"
},
"FieldConfig": {
"properties": {
"color": {
"additionalProperties": {
"type": "object"
},
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object"
},
"custom": {
"additionalProperties": {
"type": "object"
},
"description": "Panel Specific Values",
"type": "object"
},
"decimals": {
"format": "uint16",
"type": "integer"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"format": "double",
"type": "number"
},
"links": {
"description": "The behavior when clicking on a result",
"items": {
"$ref": "#/definitions/DataLink"
},
"type": "array"
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
},
"title": "FieldConfig represents the display properties for a Field.",
"type": "object"
},
"Frame": {
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
"properties": {
"Fields": {
"description": "Fields are the columns of a frame.\nAll Fields must be of the same the length when marshalling the Frame for transmission.",
"items": {
"$ref": "#/definitions/Field"
},
"type": "array"
},
"Meta": {
"$ref": "#/definitions/FrameMeta"
},
"Name": {
"description": "Name is used in some Grafana visualizations.",
"type": "string"
},
"RefID": {
"description": "RefID is a property that can be set to match a Frame to its originating query.",
"type": "string"
}
},
"title": "Frame is a columnar data structure where each column is a Field.",
"type": "object"
},
"FrameLabels": {
"additionalProperties": {
"type": "string"
},
"description": "Labels are used to add metadata to an object. The JSON will always be sorted keys",
"type": "object"
},
"FrameMeta": {
"description": "https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L11\nNOTE -- in javascript this can accept any `[key: string]: any;` however\nthis interface only exposes the values we want to be exposed",
"properties": {
"channel": {
"description": "Channel is the path to a stream in grafana live that has real-time updates for this data.",
"type": "string"
},
"custom": {
"description": "Custom datasource specific values.",
"type": "object"
},
"dataTopic": {
"$ref": "#/definitions/DataTopic"
},
"executedQueryString": {
"description": "ExecutedQueryString is the raw query sent to the underlying system. All macros and templating\nhave been applied. When metadata contains this value, it will be shown in the query inspector.",
"type": "string"
},
"notices": {
"description": "Notices provide additional information about the data in the Frame that\nGrafana can display to the user in the user interface.",
"items": {
"$ref": "#/definitions/Notice"
},
"type": "array"
},
"path": {
"description": "Path is a browsable path on the datasource.",
"type": "string"
},
"pathSeparator": {
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
"type": "string"
},
"preferredVisualisationType": {
"$ref": "#/definitions/VisType"
},
"stats": {
"description": "Stats is an array of query result statistics.",
"items": {
"$ref": "#/definitions/QueryStat"
},
"type": "array"
},
"type": {
"$ref": "#/definitions/FrameType"
}
},
"title": "FrameMeta matches:",
"type": "object"
},
"FrameType": {
"description": "A FrameType string, when present in a frame's metadata, asserts that the\nframe's structure conforms to the FrameType's specification.\nThis property is currently optional, so FrameType may be FrameTypeUnknown even if the properties of\nthe Frame correspond to a defined FrameType.",
"type": "string"
},
"Frames": {
"description": "It is the main data container within a backend.DataResponse.",
"items": {
"$ref": "#/definitions/Frame"
},
"title": "Frames is a slice of Frame pointers.",
"type": "array"
},
"GettableAlertmanagers": {
"properties": {
"data": {
@ -1176,6 +1405,11 @@
},
"type": "object"
},
"InspectType": {
"format": "int64",
"title": "InspectType is a type for the Inspect property of a Notice.",
"type": "integer"
},
"Json": {
"type": "object"
},
@ -1217,6 +1451,82 @@
},
"type": "array"
},
"LegacyAlert": {
"properties": {
"Created": {
"format": "date-time",
"type": "string"
},
"DashboardId": {
"format": "int64",
"type": "integer"
},
"EvalData": {
"$ref": "#/definitions/Json"
},
"ExecutionError": {
"type": "string"
},
"For": {
"$ref": "#/definitions/Duration"
},
"Frequency": {
"format": "int64",
"type": "integer"
},
"Handler": {
"format": "int64",
"type": "integer"
},
"Id": {
"format": "int64",
"type": "integer"
},
"Message": {
"type": "string"
},
"Name": {
"type": "string"
},
"NewStateDate": {
"format": "date-time",
"type": "string"
},
"OrgId": {
"format": "int64",
"type": "integer"
},
"PanelId": {
"format": "int64",
"type": "integer"
},
"Settings": {
"$ref": "#/definitions/Json"
},
"Severity": {
"type": "string"
},
"Silenced": {
"type": "boolean"
},
"State": {
"$ref": "#/definitions/AlertStateType"
},
"StateChanges": {
"format": "int64",
"type": "integer"
},
"Updated": {
"format": "date-time",
"type": "string"
},
"Version": {
"format": "int64",
"type": "integer"
}
},
"type": "object"
},
"MatchRegexps": {
"additionalProperties": {
"$ref": "#/definitions/Regexp"
@ -1329,6 +1639,31 @@
"NotFound": {
"type": "object"
},
"Notice": {
"properties": {
"inspect": {
"$ref": "#/definitions/InspectType"
},
"link": {
"description": "Link is an optional link for display in the user interface and can be an\nabsolute URL or a path relative to Grafana's root url.",
"type": "string"
},
"severity": {
"$ref": "#/definitions/NoticeSeverity"
},
"text": {
"description": "Text is freeform descriptive text for the notice.",
"type": "string"
}
},
"title": "Notice provides a structure for presenting notifications in Grafana's user interface.",
"type": "object"
},
"NoticeSeverity": {
"format": "int64",
"title": "NoticeSeverity is a type for the Severity property of a Notice.",
"type": "integer"
},
"NotifierConfig": {
"properties": {
"send_resolved": {
@ -2008,6 +2343,91 @@
},
"type": "object"
},
"QueryStat": {
"description": "The embedded FieldConfig's display name must be set.\nIt corresponds to the QueryResultMetaStat on the frontend (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L53).",
"properties": {
"color": {
"additionalProperties": {
"type": "object"
},
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object"
},
"custom": {
"additionalProperties": {
"type": "object"
},
"description": "Panel Specific Values",
"type": "object"
},
"decimals": {
"format": "uint16",
"type": "integer"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"format": "double",
"type": "number"
},
"links": {
"description": "The behavior when clicking on a result",
"items": {
"$ref": "#/definitions/DataLink"
},
"type": "array"
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"value": {
"format": "double",
"type": "number"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
},
"title": "QueryStat is used for storing arbitrary statistics metadata related to a query and its result, e.g. total request time, data processing time.",
"type": "object"
},
"Receiver": {
"properties": {
"email_configs": {
@ -2097,6 +2517,14 @@
},
"type": "object"
},
"Responses": {
"additionalProperties": {
"$ref": "#/definitions/DataResponse"
},
"description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataResponses' frames based on these RefIDs.",
"title": "Responses is a map of RefIDs (Unique Query ID) to DataResponses.",
"type": "object"
},
"Route": {
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
"properties": {
@ -2650,6 +3078,41 @@
},
"type": "object"
},
"Threshold": {
"description": "Threshold a single step on the threshold list",
"properties": {
"color": {
"type": "string"
},
"state": {
"type": "string"
},
"value": {
"$ref": "#/definitions/ConfFloat64"
}
},
"type": "object"
},
"ThresholdsConfig": {
"description": "ThresholdsConfig setup thresholds",
"properties": {
"mode": {
"$ref": "#/definitions/ThresholdsMode"
},
"steps": {
"description": "Must be sorted by 'value', first value is always -Infinity",
"items": {
"$ref": "#/definitions/Threshold"
},
"type": "array"
}
},
"type": "object"
},
"ThresholdsMode": {
"description": "ThresholdsMode absolute or percentage",
"type": "string"
},
"TimeInterval": {
"description": "TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained\nwithin the interval.",
"properties": {
@ -2750,6 +3213,16 @@
},
"type": "object"
},
"ValueMapping": {
"description": "ValueMapping allows mapping input values to text and color",
"type": "object"
},
"ValueMappings": {
"items": {
"$ref": "#/definitions/ValueMapping"
},
"type": "array"
},
"Vector": {
"description": "Vector is basically only an alias for model.Samples, but the\ncontract is that in a Vector, all Samples have the same timestamp.",
"items": {
@ -2799,6 +3272,10 @@
"title": "VictorOpsConfig configures notifications via VictorOps.",
"type": "object"
},
"VisType": {
"title": "VisType is used to indicate how the data should be visualized in explore.",
"type": "string"
},
"WebhookConfig": {
"properties": {
"http_config": {
@ -2927,6 +3404,7 @@
"type": "object"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"items": {
"$ref": "#/definitions/alertGroup"
},
@ -3090,12 +3568,14 @@
"type": "object"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
"items": {
"$ref": "#/definitions/gettableAlert"
},
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@ -3255,6 +3735,7 @@
"type": "array"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@ -3292,7 +3773,6 @@
"type": "object"
},
"receiver": {
"description": "Receiver receiver",
"properties": {
"name": {
"description": "name",
@ -3812,9 +4292,9 @@
}
},
"404": {
"description": "AlertManagerNotFound",
"description": "NotFound",
"schema": {
"$ref": "#/definitions/AlertManagerNotFound"
"$ref": "#/definitions/NotFound"
}
},
"408": {
@ -3897,6 +4377,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -3937,6 +4423,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4005,6 +4497,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4043,6 +4541,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4079,6 +4583,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4119,6 +4629,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4156,6 +4672,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4188,6 +4710,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4220,6 +4748,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4250,6 +4784,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4287,6 +4827,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4339,9 +4885,9 @@
}
},
"404": {
"description": "AlertManagerNotFound",
"description": "NotFound",
"schema": {
"$ref": "#/definitions/AlertManagerNotFound"
"$ref": "#/definitions/NotFound"
}
},
"408": {
@ -4447,6 +4993,12 @@
"schema": {
"$ref": "#/definitions/AlertResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4473,6 +5025,12 @@
"schema": {
"$ref": "#/definitions/RuleResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4694,6 +5252,12 @@
"schema": {
"$ref": "#/definitions/NamespaceConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4726,6 +5290,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4759,6 +5329,12 @@
"schema": {
"$ref": "#/definitions/NamespaceConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4800,6 +5376,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4838,6 +5420,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -4877,6 +5465,12 @@
"schema": {
"$ref": "#/definitions/RuleGroupConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [
@ -5042,9 +5636,9 @@
],
"responses": {
"201": {
"description": "AlertRule",
"description": "ProvisionedAlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
"$ref": "#/definitions/ProvisionedAlertRule"
}
},
"400": {
@ -5095,9 +5689,9 @@
],
"responses": {
"200": {
"description": "AlertRule",
"description": "ProvisionedAlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
"$ref": "#/definitions/ProvisionedAlertRule"
}
},
"404": {
@ -5132,9 +5726,9 @@
],
"responses": {
"200": {
"description": "AlertRule",
"description": "ProvisionedAlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
"$ref": "#/definitions/ProvisionedAlertRule"
}
},
"400": {
@ -5736,6 +6330,12 @@
"schema": {
"$ref": "#/definitions/TestRuleResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
},
"tags": [

@ -434,9 +434,9 @@
}
},
"404": {
"description": "AlertManagerNotFound",
"description": "NotFound",
"schema": {
"$ref": "#/definitions/AlertManagerNotFound"
"$ref": "#/definitions/NotFound"
}
},
"408": {
@ -518,6 +518,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -558,6 +564,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -626,6 +638,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -664,6 +682,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -700,6 +724,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -740,6 +770,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -777,6 +813,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -809,6 +851,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -841,6 +889,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -878,6 +932,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -908,6 +968,12 @@
"schema": {
"$ref": "#/definitions/ValidationError"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -961,9 +1027,9 @@
}
},
"404": {
"description": "AlertManagerNotFound",
"description": "NotFound",
"schema": {
"$ref": "#/definitions/AlertManagerNotFound"
"$ref": "#/definitions/NotFound"
}
},
"408": {
@ -1068,6 +1134,12 @@
"schema": {
"$ref": "#/definitions/AlertResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -1094,6 +1166,12 @@
"schema": {
"$ref": "#/definitions/RuleResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -1315,6 +1393,12 @@
"schema": {
"$ref": "#/definitions/NamespaceConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -1350,6 +1434,12 @@
"schema": {
"$ref": "#/definitions/NamespaceConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -1391,6 +1481,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -1421,6 +1517,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -1462,6 +1564,12 @@
"schema": {
"$ref": "#/definitions/RuleGroupConfigResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
},
@ -1498,6 +1606,12 @@
"schema": {
"$ref": "#/definitions/Ack"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -2379,6 +2493,12 @@
"schema": {
"$ref": "#/definitions/TestRuleResponse"
}
},
"404": {
"description": "NotFound",
"schema": {
"$ref": "#/definitions/NotFound"
}
}
}
}
@ -2477,9 +2597,6 @@
}
}
},
"AlertManagerNotFound": {
"type": "object"
},
"AlertManagerNotReady": {
"type": "object"
},
@ -2665,6 +2782,9 @@
}
}
},
"AlertStateType": {
"type": "string"
},
"AlertingRule": {
"description": "adapted from cortex",
"type": "object",
@ -2782,6 +2902,11 @@
}
}
},
"ConfFloat64": {
"description": "ConfFloat64 is a float64. It Marshals float64 values of NaN of Inf\nto null.",
"type": "number",
"format": "double"
},
"Config": {
"type": "object",
"title": "Config is the top-level configuration for Alertmanager's config files.",
@ -2818,6 +2943,39 @@
"$ref": "#/definitions/EmbeddedContactPoint"
}
},
"DataLink": {
"description": "DataLink define what",
"type": "object",
"properties": {
"targetBlank": {
"type": "boolean"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
}
}
},
"DataResponse": {
"description": "A map of RefIDs (unique query identifiers) to this type makes up the Responses property of a QueryDataResponse.\nThe Error property is used to allow for partial success responses from the containing QueryDataResponse.",
"type": "object",
"title": "DataResponse contains the results from a DataQuery.",
"properties": {
"Error": {
"description": "Error is a property to be set if the the corresponding DataQuery has an error.",
"type": "string"
},
"Frames": {
"$ref": "#/definitions/Frames"
}
}
},
"DataTopic": {
"type": "string",
"title": "DataTopic is used to identify which topic the frame should be assigned to."
},
"DateTime": {
"description": "DateTime is a time but it serializes to ISO8601 format with millis\nIt knows how to read 3 different variations of a RFC3339 date time.\nMost APIs we encounter want either millisecond or second precision times.\nThis just tries to make it worry-free.",
"type": "string",
@ -3048,6 +3206,197 @@
"Failure": {
"$ref": "#/definitions/ResponseDetails"
},
"Field": {
"description": "A Field is essentially a slice of various types with extra properties and methods.\nSee NewField() for supported types.\n\nThe slice data in the Field is a not exported, so methods on the Field are used to to manipulate its data.",
"type": "object",
"title": "Field represents a typed column of data within a Frame.",
"properties": {
"config": {
"$ref": "#/definitions/FieldConfig"
},
"labels": {
"$ref": "#/definitions/FrameLabels"
},
"name": {
"description": "Name is default identifier of the field. The name does not have to be unique, but the combination\nof name and Labels should be unique for proper behavior in all situations.",
"type": "string"
}
}
},
"FieldConfig": {
"type": "object",
"title": "FieldConfig represents the display properties for a Field.",
"properties": {
"color": {
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"custom": {
"description": "Panel Specific Values",
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"decimals": {
"type": "integer",
"format": "uint16"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"type": "number",
"format": "double"
},
"links": {
"description": "The behavior when clicking on a result",
"type": "array",
"items": {
"$ref": "#/definitions/DataLink"
}
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
}
},
"Frame": {
"description": "Each Field is well typed by its FieldType and supports optional Labels.\n\nA Frame is a general data container for Grafana. A Frame can be table data\nor time series data depending on its content and field types.",
"type": "object",
"title": "Frame is a columnar data structure where each column is a Field.",
"properties": {
"Fields": {
"description": "Fields are the columns of a frame.\nAll Fields must be of the same the length when marshalling the Frame for transmission.",
"type": "array",
"items": {
"$ref": "#/definitions/Field"
}
},
"Meta": {
"$ref": "#/definitions/FrameMeta"
},
"Name": {
"description": "Name is used in some Grafana visualizations.",
"type": "string"
},
"RefID": {
"description": "RefID is a property that can be set to match a Frame to its originating query.",
"type": "string"
}
}
},
"FrameLabels": {
"description": "Labels are used to add metadata to an object. The JSON will always be sorted keys",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"FrameMeta": {
"description": "https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L11\nNOTE -- in javascript this can accept any `[key: string]: any;` however\nthis interface only exposes the values we want to be exposed",
"type": "object",
"title": "FrameMeta matches:",
"properties": {
"channel": {
"description": "Channel is the path to a stream in grafana live that has real-time updates for this data.",
"type": "string"
},
"custom": {
"description": "Custom datasource specific values.",
"type": "object"
},
"dataTopic": {
"$ref": "#/definitions/DataTopic"
},
"executedQueryString": {
"description": "ExecutedQueryString is the raw query sent to the underlying system. All macros and templating\nhave been applied. When metadata contains this value, it will be shown in the query inspector.",
"type": "string"
},
"notices": {
"description": "Notices provide additional information about the data in the Frame that\nGrafana can display to the user in the user interface.",
"type": "array",
"items": {
"$ref": "#/definitions/Notice"
}
},
"path": {
"description": "Path is a browsable path on the datasource.",
"type": "string"
},
"pathSeparator": {
"description": "PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'.",
"type": "string"
},
"preferredVisualisationType": {
"$ref": "#/definitions/VisType"
},
"stats": {
"description": "Stats is an array of query result statistics.",
"type": "array",
"items": {
"$ref": "#/definitions/QueryStat"
}
},
"type": {
"$ref": "#/definitions/FrameType"
}
}
},
"FrameType": {
"description": "A FrameType string, when present in a frame's metadata, asserts that the\nframe's structure conforms to the FrameType's specification.\nThis property is currently optional, so FrameType may be FrameTypeUnknown even if the properties of\nthe Frame correspond to a defined FrameType.",
"type": "string"
},
"Frames": {
"description": "It is the main data container within a backend.DataResponse.",
"type": "array",
"title": "Frames is a slice of Frame pointers.",
"items": {
"$ref": "#/definitions/Frame"
}
},
"GettableAlertmanagers": {
"type": "object",
"properties": {
@ -3560,6 +3909,11 @@
}
}
},
"InspectType": {
"type": "integer",
"format": "int64",
"title": "InspectType is a type for the Inspect property of a Notice."
},
"Json": {
"type": "object"
},
@ -3601,6 +3955,82 @@
"$ref": "#/definitions/Label"
}
},
"LegacyAlert": {
"type": "object",
"properties": {
"Created": {
"type": "string",
"format": "date-time"
},
"DashboardId": {
"type": "integer",
"format": "int64"
},
"EvalData": {
"$ref": "#/definitions/Json"
},
"ExecutionError": {
"type": "string"
},
"For": {
"$ref": "#/definitions/Duration"
},
"Frequency": {
"type": "integer",
"format": "int64"
},
"Handler": {
"type": "integer",
"format": "int64"
},
"Id": {
"type": "integer",
"format": "int64"
},
"Message": {
"type": "string"
},
"Name": {
"type": "string"
},
"NewStateDate": {
"type": "string",
"format": "date-time"
},
"OrgId": {
"type": "integer",
"format": "int64"
},
"PanelId": {
"type": "integer",
"format": "int64"
},
"Settings": {
"$ref": "#/definitions/Json"
},
"Severity": {
"type": "string"
},
"Silenced": {
"type": "boolean"
},
"State": {
"$ref": "#/definitions/AlertStateType"
},
"StateChanges": {
"type": "integer",
"format": "int64"
},
"Updated": {
"type": "string",
"format": "date-time"
},
"Version": {
"type": "integer",
"format": "int64"
}
}
},
"MatchRegexps": {
"type": "object",
"title": "MatchRegexps represents a map of Regexp.",
@ -3714,6 +4144,31 @@
"NotFound": {
"type": "object"
},
"Notice": {
"type": "object",
"title": "Notice provides a structure for presenting notifications in Grafana's user interface.",
"properties": {
"inspect": {
"$ref": "#/definitions/InspectType"
},
"link": {
"description": "Link is an optional link for display in the user interface and can be an\nabsolute URL or a path relative to Grafana's root url.",
"type": "string"
},
"severity": {
"$ref": "#/definitions/NoticeSeverity"
},
"text": {
"description": "Text is freeform descriptive text for the notice.",
"type": "string"
}
}
},
"NoticeSeverity": {
"type": "integer",
"format": "int64",
"title": "NoticeSeverity is a type for the Severity property of a Notice."
},
"NotifierConfig": {
"type": "object",
"title": "NotifierConfig contains base options common across all notifier configurations.",
@ -4393,6 +4848,91 @@
}
}
},
"QueryStat": {
"description": "The embedded FieldConfig's display name must be set.\nIt corresponds to the QueryResultMetaStat on the frontend (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/types/data.ts#L53).",
"type": "object",
"title": "QueryStat is used for storing arbitrary statistics metadata related to a query and its result, e.g. total request time, data processing time.",
"properties": {
"color": {
"description": "Map values to a display color\nNOTE: this interface is under development in the frontend... so simple map for now",
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"custom": {
"description": "Panel Specific Values",
"type": "object",
"additionalProperties": {
"type": "object"
}
},
"decimals": {
"type": "integer",
"format": "uint16"
},
"description": {
"description": "Description is human readable field metadata",
"type": "string"
},
"displayName": {
"description": "DisplayName overrides Grafana default naming, should not be used from a data source",
"type": "string"
},
"displayNameFromDS": {
"description": "DisplayNameFromDS overrides Grafana default naming in a better way that allows users to override it easily.",
"type": "string"
},
"filterable": {
"description": "Filterable indicates if the Field's data can be filtered by additional calls.",
"type": "boolean"
},
"interval": {
"description": "Interval indicates the expected regular step between values in the series.\nWhen an interval exists, consumers can identify \"missing\" values when the expected value is not present.\nThe grafana timeseries visualization will render disconnected values when missing values are found it the time field.\nThe interval uses the same units as the values. For time.Time, this is defined in milliseconds.",
"type": "number",
"format": "double"
},
"links": {
"description": "The behavior when clicking on a result",
"type": "array",
"items": {
"$ref": "#/definitions/DataLink"
}
},
"mappings": {
"$ref": "#/definitions/ValueMappings"
},
"max": {
"$ref": "#/definitions/ConfFloat64"
},
"min": {
"$ref": "#/definitions/ConfFloat64"
},
"noValue": {
"description": "Alternative to empty string",
"type": "string"
},
"path": {
"description": "Path is an explicit path to the field in the datasource. When the frame meta includes a path,\nthis will default to `${frame.meta.path}/${field.name}\n\nWhen defined, this value can be used as an identifier within the datasource scope, and\nmay be used as an identifier to update values in a subsequent request",
"type": "string"
},
"thresholds": {
"$ref": "#/definitions/ThresholdsConfig"
},
"unit": {
"description": "Numeric Options",
"type": "string"
},
"value": {
"type": "number",
"format": "double"
},
"writeable": {
"description": "Writeable indicates that the datasource knows how to update this value",
"type": "boolean"
}
}
},
"Receiver": {
"type": "object",
"title": "Receiver configuration provides configuration on how to contact a receiver.",
@ -4482,6 +5022,14 @@
}
}
},
"Responses": {
"description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataResponses' frames based on these RefIDs.",
"type": "object",
"title": "Responses is a map of RefIDs (Unique Query ID) to DataResponses.",
"additionalProperties": {
"$ref": "#/definitions/DataResponse"
}
},
"Route": {
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
"type": "object",
@ -5035,6 +5583,41 @@
}
}
},
"Threshold": {
"description": "Threshold a single step on the threshold list",
"type": "object",
"properties": {
"color": {
"type": "string"
},
"state": {
"type": "string"
},
"value": {
"$ref": "#/definitions/ConfFloat64"
}
}
},
"ThresholdsConfig": {
"description": "ThresholdsConfig setup thresholds",
"type": "object",
"properties": {
"mode": {
"$ref": "#/definitions/ThresholdsMode"
},
"steps": {
"description": "Must be sorted by 'value', first value is always -Infinity",
"type": "array",
"items": {
"$ref": "#/definitions/Threshold"
}
}
}
},
"ThresholdsMode": {
"description": "ThresholdsMode absolute or percentage",
"type": "string"
},
"TimeInterval": {
"description": "TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained\nwithin the interval.",
"type": "object",
@ -5135,6 +5718,16 @@
}
}
},
"ValueMapping": {
"description": "ValueMapping allows mapping input values to text and color",
"type": "object"
},
"ValueMappings": {
"type": "array",
"items": {
"$ref": "#/definitions/ValueMapping"
}
},
"Vector": {
"description": "Vector is basically only an alias for model.Samples, but the\ncontract is that in a Vector, all Samples have the same timestamp.",
"type": "array",
@ -5184,6 +5777,10 @@
}
}
},
"VisType": {
"type": "string",
"title": "VisType is used to indicate how the data should be visualized in explore."
},
"WebhookConfig": {
"type": "object",
"title": "WebhookConfig configures notifications via a generic webhook.",
@ -5289,7 +5886,6 @@
}
},
"alertGroup": {
"description": "AlertGroup alert group",
"type": "object",
"required": [
"alerts",
@ -5488,6 +6084,7 @@
"$ref": "#/definitions/gettableAlerts"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"type": "object",
"required": [
"comment",
@ -5537,6 +6134,7 @@
"$ref": "#/definitions/gettableSilence"
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
"type": "array",
"items": {
"$ref": "#/definitions/gettableSilence"
@ -5687,7 +6285,6 @@
"$ref": "#/definitions/postableSilence"
},
"receiver": {
"description": "Receiver receiver",
"type": "object",
"required": [
"name"

@ -35,19 +35,25 @@ func toMacaronPath(path string) string {
}))
}
func backendTypeByUID(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) {
func getDatasourceByUID(ctx *models.ReqContext, cache datasources.CacheService, expectedType apimodels.Backend) (*datasources.DataSource, error) {
datasourceUID := web.Params(ctx.Req)[":DatasourceUID"]
if ds, err := cache.GetDatasourceByUID(ctx.Req.Context(), datasourceUID, ctx.SignedInUser, ctx.SkipCache); err == nil {
switch ds.Type {
case "loki", "prometheus":
return apimodels.LoTexRulerBackend, nil
case "alertmanager":
return apimodels.AlertmanagerBackend, nil
default:
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
ds, err := cache.GetDatasourceByUID(ctx.Req.Context(), datasourceUID, ctx.SignedInUser, ctx.SkipCache)
if err != nil {
return nil, err
}
switch expectedType {
case apimodels.AlertmanagerBackend:
if ds.Type != "alertmanager" {
return nil, unexpectedDatasourceTypeError(ds.Type, "alertmanager")
}
case apimodels.LoTexRulerBackend:
if ds.Type != "loki" && ds.Type != "prometheus" {
return nil, unexpectedDatasourceTypeError(ds.Type, "loki, prometheus")
}
default:
return nil, unexpectedDatasourceTypeError(ds.Type, expectedType.String())
}
return 0, fmt.Errorf("unexpected backend type (%v)", datasourceUID)
return ds, nil
}
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save