Merge branch 'master' into react-panels-step1

This commit is contained in:
Torkel Ödegaard
2018-10-19 09:15:37 +02:00
99 changed files with 1500 additions and 2252 deletions
+3 -3
View File
@@ -234,13 +234,13 @@ func (hs *HTTPServer) registerRoutes() {
datasourceRoute.Get("/", Wrap(GetDataSources))
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), Wrap(AddDataSource))
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), Wrap(UpdateDataSource))
datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceByID))
datasourceRoute.Delete("/:id", Wrap(DeleteDataSourceById))
datasourceRoute.Delete("/name/:name", Wrap(DeleteDataSourceByName))
datasourceRoute.Get("/:id", Wrap(GetDataSourceByID))
datasourceRoute.Get("/:id", Wrap(GetDataSourceById))
datasourceRoute.Get("/name/:name", Wrap(GetDataSourceByName))
}, reqOrgAdmin)
apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIDByName), reqSignedIn)
apiRoute.Get("/datasources/id/:name", Wrap(GetDataSourceIdByName), reqSignedIn)
apiRoute.Get("/plugins", Wrap(GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID))
+3 -2
View File
@@ -6,6 +6,7 @@ import (
"os"
"path"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/api/dtos"
@@ -251,8 +252,8 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
return Error(403, err.Error(), err)
}
if err == m.ErrDashboardContainsInvalidAlertData {
return Error(500, "Invalid alert data. Cannot save dashboard", err)
if validationErr, ok := err.(alerting.ValidationError); ok {
return Error(422, validationErr.Error(), nil)
}
if err != nil {
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting"
@@ -725,7 +726,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
{SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardContainsInvalidAlertData, ExpectedStatusCode: 500},
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
+18 -1
View File
@@ -2,6 +2,7 @@ package api
import (
"fmt"
"github.com/pkg/errors"
"time"
"github.com/grafana/grafana/pkg/api/pluginproxy"
@@ -14,6 +15,20 @@ import (
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.DataSource, error) {
userPermissionsQuery := m.GetDataSourcePermissionsForUserQuery{
User: c.SignedInUser,
}
if err := bus.Dispatch(&userPermissionsQuery); err != nil {
if err != bus.ErrHandlerNotFound {
return nil, err
}
} else {
permissionType, exists := userPermissionsQuery.Result[id]
if exists && permissionType != m.DsPermissionQuery {
return nil, errors.New("User not allowed to access datasource")
}
}
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
cacheKey := fmt.Sprintf("ds-%d", id)
@@ -38,7 +53,9 @@ func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.Data
func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer)
ds, err := hs.getDatasourceFromCache(c.ParamsInt64(":id"), c)
dsId := c.ParamsInt64(":id")
ds, err := hs.getDatasourceFromCache(dsId, c)
if err != nil {
c.JsonApiErr(500, "Unable to load datasource meta data", err)
return
+7 -7
View File
@@ -20,8 +20,8 @@ func GetDataSources(c *m.ReqContext) Response {
result := make(dtos.DataSourceList, 0)
for _, ds := range query.Result {
dsItem := dtos.DataSourceListItemDTO{
Id: ds.Id,
OrgId: ds.OrgId,
Id: ds.Id,
Name: ds.Name,
Url: ds.Url,
Type: ds.Type,
@@ -49,7 +49,7 @@ func GetDataSources(c *m.ReqContext) Response {
return JSON(200, &result)
}
func GetDataSourceByID(c *m.ReqContext) Response {
func GetDataSourceById(c *m.ReqContext) Response {
query := m.GetDataSourceByIdQuery{
Id: c.ParamsInt64(":id"),
OrgId: c.OrgId,
@@ -68,14 +68,14 @@ func GetDataSourceByID(c *m.ReqContext) Response {
return JSON(200, &dtos)
}
func DeleteDataSourceByID(c *m.ReqContext) Response {
func DeleteDataSourceById(c *m.ReqContext) Response {
id := c.ParamsInt64(":id")
if id <= 0 {
return Error(400, "Missing valid datasource id", nil)
}
ds, err := getRawDataSourceByID(id, c.OrgId)
ds, err := getRawDataSourceById(id, c.OrgId)
if err != nil {
return Error(400, "Failed to delete datasource", nil)
}
@@ -186,7 +186,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
return nil
}
ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId)
ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId)
if err != nil {
return err
}
@@ -206,7 +206,7 @@ func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
return nil
}
func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) {
func getRawDataSourceById(id int64, orgID int64) (*m.DataSource, error) {
query := m.GetDataSourceByIdQuery{
Id: id,
OrgId: orgID,
@@ -236,7 +236,7 @@ func GetDataSourceByName(c *m.ReqContext) Response {
}
// Get /api/datasources/id/:name
func GetDataSourceIDByName(c *m.ReqContext) Response {
func GetDataSourceIdByName(c *m.ReqContext) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
+26 -23
View File
@@ -49,28 +49,30 @@ func formatShort(interval time.Duration) string {
func NewAlertNotification(notification *models.AlertNotification) *AlertNotification {
return &AlertNotification{
Id: notification.Id,
Name: notification.Name,
Type: notification.Type,
IsDefault: notification.IsDefault,
Created: notification.Created,
Updated: notification.Updated,
Frequency: formatShort(notification.Frequency),
SendReminder: notification.SendReminder,
Settings: notification.Settings,
Id: notification.Id,
Name: notification.Name,
Type: notification.Type,
IsDefault: notification.IsDefault,
Created: notification.Created,
Updated: notification.Updated,
Frequency: formatShort(notification.Frequency),
SendReminder: notification.SendReminder,
DisableResolveMessage: notification.DisableResolveMessage,
Settings: notification.Settings,
}
}
type AlertNotification struct {
Id int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Settings *simplejson.Json `json:"settings"`
Id int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Settings *simplejson.Json `json:"settings"`
}
type AlertTestCommand struct {
@@ -100,11 +102,12 @@ type EvalMatch struct {
}
type NotificationTestCommand struct {
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
Settings *simplejson.Json `json:"settings"`
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"`
Settings *simplejson.Json `json:"settings"`
}
type PauseAlertCommand struct {
+14 -1
View File
@@ -22,7 +22,20 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
return nil, err
}
orgDataSources = query.Result
dsFilterQuery := m.DatasourcesPermissionFilterQuery{
User: c.SignedInUser,
Datasources: query.Result,
}
if err := bus.Dispatch(&dsFilterQuery); err != nil {
if err != bus.ErrHandlerNotFound {
return nil, err
}
orgDataSources = query.Result
} else {
orgDataSources = dsFilterQuery.Result
}
}
datasources := make(map[string]interface{})
+1 -1
View File
@@ -100,7 +100,7 @@ func listenToSystemSignals(server *GrafanaServerImpl) {
sighupChan := make(chan os.Signal, 1)
signal.Notify(sighupChan, syscall.SIGHUP)
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
for {
select {
+3 -1
View File
@@ -185,7 +185,9 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo
if ldapUser.isMemberOf(group.GroupDN) {
extUser.OrgRoles[group.OrgId] = group.OrgRole
extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
if extUser.IsGrafanaAdmin == nil || *extUser.IsGrafanaAdmin == false {
extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
}
}
}
+26 -23
View File
@@ -23,38 +23,41 @@ var (
)
type AlertNotification struct {
Id int64 `json:"id"`
OrgId int64 `json:"-"`
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
Frequency time.Duration `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Id int64 `json:"id"`
OrgId int64 `json:"-"`
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency time.Duration `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type CreateAlertNotificationCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
OrgId int64 `json:"-"`
Result *AlertNotification
}
type UpdateAlertNotificationCommand struct {
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"`
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"`
OrgId int64 `json:"-"`
Result *AlertNotification
-1
View File
@@ -21,7 +21,6 @@ var (
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
+29 -2
View File
@@ -30,6 +30,7 @@ var (
ErrDataSourceNameExists = errors.New("Data source with same name already exists")
ErrDataSourceUpdatingOldVersion = errors.New("Trying to update old version of datasource")
ErrDatasourceIsReadOnly = errors.New("Data source is readonly. Can only be updated from configuration.")
ErrDataSourceAccessDenied = errors.New("Data source access denied")
)
type DsAccess string
@@ -167,6 +168,7 @@ type DeleteDataSourceByNameCommand struct {
type GetDataSourcesQuery struct {
OrgId int64
User *SignedInUser
Result []*DataSource
}
@@ -187,6 +189,31 @@ type GetDataSourceByNameQuery struct {
}
// ---------------------
// EVENTS
type DataSourceCreatedEvent struct {
// Permissions
// ---------------------
type DsPermissionType int
const (
DsPermissionNoAccess DsPermissionType = iota
DsPermissionQuery
)
func (p DsPermissionType) String() string {
names := map[int]string{
int(DsPermissionQuery): "Query",
int(DsPermissionNoAccess): "No Access",
}
return names[int(p)]
}
type GetDataSourcePermissionsForUserQuery struct {
User *SignedInUser
Result map[int64]DsPermissionType
}
type DatasourcesPermissionFilterQuery struct {
User *SignedInUser
Datasources []*DataSource
Result []*DataSource
}
@@ -2,6 +2,7 @@ package conditions
import (
"encoding/json"
"fmt"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -31,12 +32,12 @@ type ThresholdEvaluator struct {
func newThresholdEvaluator(typ string, model *simplejson.Json) (*ThresholdEvaluator, error) {
params := model.Get("params").MustArray()
if len(params) == 0 {
return nil, alerting.ValidationError{Reason: "Evaluator missing threshold parameter"}
return nil, fmt.Errorf("Evaluator missing threshold parameter")
}
firstParam, ok := params[0].(json.Number)
if !ok {
return nil, alerting.ValidationError{Reason: "Evaluator has invalid parameter"}
return nil, fmt.Errorf("Evaluator has invalid parameter")
}
defaultEval := &ThresholdEvaluator{Type: typ}
@@ -107,7 +108,7 @@ func (e *RangedEvaluator) Eval(reducedValue null.Float) bool {
func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) {
typ := model.Get("type").MustString()
if typ == "" {
return nil, alerting.ValidationError{Reason: "Evaluator missing type property"}
return nil, fmt.Errorf("Evaluator missing type property")
}
if inSlice(typ, defaultTypes) {
@@ -122,7 +123,7 @@ func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) {
return &NoValueEvaluator{}, nil
}
return nil, alerting.ValidationError{Reason: "Evaluator invalid evaluator type: " + typ}
return nil, fmt.Errorf("Evaluator invalid evaluator type: %s", typ)
}
func inSlice(a string, list []string) bool {
+5 -6
View File
@@ -82,8 +82,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels
alertSlice, err := e.getAlertFromPanels(panel,
validateAlertFunc)
alertSlice, err := e.getAlertFromPanels(panel, validateAlertFunc)
if err != nil {
return nil, err
}
@@ -100,7 +99,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
panelID, err := panel.Get("id").Int64()
if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err)
return nil, ValidationError{Reason: "A numeric panel id property is missing"}
}
// backward compatibility check, can be removed later
@@ -146,7 +145,8 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
datasource, err := e.lookupDatasourceID(dsName)
if err != nil {
return nil, err
e.log.Debug("Error looking up datasource", "error", err)
return nil, ValidationError{Reason: fmt.Sprintf("Data source used by alert rule not found, alertName=%v, datasource=%s", alert.Name, dsName)}
}
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
@@ -167,8 +167,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
}
if !validateAlertFunc(alert) {
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId)
return nil, m.ErrDashboardContainsInvalidAlertData
return nil, ValidationError{Reason: fmt.Sprintf("Panel id is not correct, alertName=%v, panelId=%v", alert.Name, alert.PanelId)}
}
alerts = append(alerts, alert)
+1 -1
View File
@@ -258,7 +258,7 @@ func TestAlertRuleExtraction(t *testing.T) {
Convey("Should fail on save", func() {
_, err := extractor.GetAlerts()
So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData)
So(err.Error(), ShouldEqual, "Alert validation error: Panel id is not correct, alertName=Influxdb, panelId=1")
})
})
})
+1
View File
@@ -27,6 +27,7 @@ type Notifier interface {
GetNotifierId() int64
GetIsDefault() bool
GetSendReminder() bool
GetDisableResolveMessage() bool
GetFrequency() time.Duration
}
+26 -16
View File
@@ -6,7 +6,6 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
)
@@ -15,13 +14,14 @@ const (
)
type NotifierBase struct {
Name string
Type string
Id int64
IsDeault bool
UploadImage bool
SendReminder bool
Frequency time.Duration
Name string
Type string
Id int64
IsDeault bool
UploadImage bool
SendReminder bool
DisableResolveMessage bool
Frequency time.Duration
log log.Logger
}
@@ -34,14 +34,15 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase {
}
return NotifierBase{
Id: model.Id,
Name: model.Name,
IsDeault: model.IsDefault,
Type: model.Type,
UploadImage: uploadImage,
SendReminder: model.SendReminder,
Frequency: model.Frequency,
log: log.New("alerting.notifier." + model.Name),
Id: model.Id,
Name: model.Name,
IsDeault: model.IsDefault,
Type: model.Type,
UploadImage: uploadImage,
SendReminder: model.SendReminder,
DisableResolveMessage: model.DisableResolveMessage,
Frequency: model.Frequency,
log: log.New("alerting.notifier." + model.Name),
}
}
@@ -83,6 +84,11 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC
}
}
// Do not notify when state is OK if DisableResolveMessage is set to true
if context.Rule.State == models.AlertStateOK && n.DisableResolveMessage {
return false
}
return true
}
@@ -106,6 +112,10 @@ func (n *NotifierBase) GetSendReminder() bool {
return n.SendReminder
}
func (n *NotifierBase) GetDisableResolveMessage() bool {
return n.DisableResolveMessage
}
func (n *NotifierBase) GetFrequency() time.Duration {
return n.Frequency
}
@@ -179,5 +179,10 @@ func TestBaseNotifier(t *testing.T) {
base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeTrue)
})
Convey("default value should be false for backwards compatibility", func() {
base := NewNotifierBase(model)
So(base.DisableResolveMessage, ShouldBeFalse)
})
})
}
+5 -5
View File
@@ -36,13 +36,13 @@ type ValidationError struct {
}
func (e ValidationError) Error() string {
extraInfo := ""
extraInfo := e.Reason
if e.Alertid != 0 {
extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid)
}
if e.PanelId != 0 {
extraInfo = fmt.Sprintf("%s PanelId: %v ", extraInfo, e.PanelId)
extraInfo = fmt.Sprintf("%s PanelId: %v", extraInfo, e.PanelId)
}
if e.DashboardId != 0 {
@@ -50,10 +50,10 @@ func (e ValidationError) Error() string {
}
if e.Err != nil {
return fmt.Sprintf("%s %s%s", e.Err.Error(), e.Reason, extraInfo)
return fmt.Sprintf("Alert validation error: %s%s", e.Err.Error(), extraInfo)
}
return fmt.Sprintf("Failed to extract alert.Reason: %s %s", e.Reason, extraInfo)
return fmt.Sprintf("Alert validation error: %s", extraInfo)
}
var (
@@ -128,7 +128,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
}
if len(model.Conditions) == 0 {
return nil, fmt.Errorf("Alert is missing conditions")
return nil, ValidationError{Reason: "Alert is missing conditions"}
}
return model, nil
+1 -1
View File
@@ -73,7 +73,7 @@ func (srv *CleanUpService) cleanUpTmpFiles() {
}
}
srv.log.Debug("Found old rendered image to delete", "deleted", len(toDelete), "keept", len(files))
srv.log.Debug("Found old rendered image to delete", "deleted", len(toDelete), "kept", len(files))
}
func (srv *CleanUpService) shouldCleanupTempFile(filemtime time.Time, now time.Time) bool {
+6 -2
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util"
@@ -25,7 +26,9 @@ type DashboardProvisioningService interface {
// NewService factory for creating a new dashboard service
var NewService = func() DashboardService {
return &dashboardServiceImpl{}
return &dashboardServiceImpl{
log: log.New("dashboard-service"),
}
}
// NewProvisioningService factory for creating a new dashboard provisioning service
@@ -45,6 +48,7 @@ type SaveDashboardDTO struct {
type dashboardServiceImpl struct {
orgId int64
user *models.SignedInUser
log log.Logger
}
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
@@ -89,7 +93,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData
return nil, err
}
}
@@ -117,12 +117,12 @@ func TestDashboardService(t *testing.T) {
})
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return errors.New("error")
return errors.New("Alert validation error")
})
dto.Dashboard = models.NewDashboard("Dash")
_, err := service.SaveDashboard(dto)
So(err, ShouldEqual, models.ErrDashboardContainsInvalidAlertData)
So(err.Error(), ShouldEqual, "Alert validation error")
})
})
+14 -10
View File
@@ -66,6 +66,7 @@ func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) erro
alert_notification.updated,
alert_notification.settings,
alert_notification.is_default,
alert_notification.disable_resolve_message,
alert_notification.send_reminder,
alert_notification.frequency
FROM alert_notification
@@ -106,6 +107,7 @@ func getAlertNotificationInternal(query *m.GetAlertNotificationsQuery, sess *DBS
alert_notification.updated,
alert_notification.settings,
alert_notification.is_default,
alert_notification.disable_resolve_message,
alert_notification.send_reminder,
alert_notification.frequency
FROM alert_notification
@@ -166,15 +168,16 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error
}
alertNotification := &m.AlertNotification{
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Settings: cmd.Settings,
SendReminder: cmd.SendReminder,
Frequency: frequency,
Created: time.Now(),
Updated: time.Now(),
IsDefault: cmd.IsDefault,
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Settings: cmd.Settings,
SendReminder: cmd.SendReminder,
DisableResolveMessage: cmd.DisableResolveMessage,
Frequency: frequency,
Created: time.Now(),
Updated: time.Now(),
IsDefault: cmd.IsDefault,
}
if _, err = sess.MustCols("send_reminder").Insert(alertNotification); err != nil {
@@ -210,6 +213,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
current.Type = cmd.Type
current.IsDefault = cmd.IsDefault
current.SendReminder = cmd.SendReminder
current.DisableResolveMessage = cmd.DisableResolveMessage
if current.SendReminder {
if cmd.Frequency == "" {
@@ -224,7 +228,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
current.Frequency = frequency
}
sess.UseBool("is_default", "send_reminder")
sess.UseBool("is_default", "send_reminder", "disable_resolve_message")
if affected, err := sess.ID(cmd.Id).Update(current); err != nil {
return err
@@ -44,8 +44,8 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
s := *query.Result
cmd := models.SetAlertNotificationStateToPendingCommand{
Id: s.Id,
Version: s.Version,
Id: s.Id,
Version: s.Version,
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
}
@@ -100,8 +100,8 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
s := *query.Result
s.Version = 1000
cmd := models.SetAlertNotificationStateToPendingCommand{
Id: s.NotifierId,
Version: s.Version,
Id: s.NotifierId,
Version: s.Version,
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
}
err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
@@ -111,8 +111,8 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
Convey("Updating existing state to pending with incorrect version since alert rule state update version is higher", func() {
s := *query.Result
cmd := models.SetAlertNotificationStateToPendingCommand{
Id: s.Id,
Version: s.Version,
Id: s.Id,
Version: s.Version,
AlertRuleStateUpdatedVersion: 1000,
}
err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
@@ -125,8 +125,8 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
s := *query.Result
s.Version = 1000
cmd := models.SetAlertNotificationStateToPendingCommand{
Id: s.Id,
Version: s.Version,
Id: s.Id,
Version: s.Version,
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
}
err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
@@ -219,6 +219,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
So(cmd.Result.OrgId, ShouldNotEqual, 0)
So(cmd.Result.Type, ShouldEqual, "email")
So(cmd.Result.Frequency, ShouldEqual, 10*time.Second)
So(cmd.Result.DisableResolveMessage, ShouldBeFalse)
Convey("Cannot save Alert Notification with the same name", func() {
err = CreateAlertNotificationCommand(cmd)
@@ -227,18 +228,20 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
Convey("Can update alert notification", func() {
newCmd := &models.UpdateAlertNotificationCommand{
Name: "NewName",
Type: "webhook",
OrgId: cmd.Result.OrgId,
SendReminder: true,
Frequency: "60s",
Settings: simplejson.New(),
Id: cmd.Result.Id,
Name: "NewName",
Type: "webhook",
OrgId: cmd.Result.OrgId,
SendReminder: true,
DisableResolveMessage: true,
Frequency: "60s",
Settings: simplejson.New(),
Id: cmd.Result.Id,
}
err := UpdateAlertNotification(newCmd)
So(err, ShouldBeNil)
So(newCmd.Result.Name, ShouldEqual, "NewName")
So(newCmd.Result.Frequency, ShouldEqual, 60*time.Second)
So(newCmd.Result.DisableResolveMessage, ShouldBeTrue)
})
Convey("Can update alert notification to disable sending of reminders", func() {
+1
View File
@@ -27,6 +27,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id}
has, err := x.Get(&datasource)
if err != nil {
return err
}
@@ -71,6 +71,9 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("Add column send_reminder", NewAddColumnMigration(alert_notification, &Column{
Name: "send_reminder", Type: DB_Bool, Nullable: true, Default: "0",
}))
mg.AddMigration("Add column disable_resolve_message", NewAddColumnMigration(alert_notification, &Column{
Name: "disable_resolve_message", Type: DB_Bool, Nullable: false, Default: "0",
}))
mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
+9 -2
View File
@@ -53,6 +53,7 @@ type SqlStore struct {
dbCfg DatabaseConfig
engine *xorm.Engine
log log.Logger
Dialect migrator.Dialect
skipEnsureAdmin bool
}
@@ -125,10 +126,12 @@ func (ss *SqlStore) Init() error {
}
ss.engine = engine
ss.Dialect = migrator.NewDialect(ss.engine)
// temporarily still set global var
x = engine
dialect = migrator.NewDialect(x)
dialect = ss.Dialect
migrator := migrator.NewMigrator(x)
migrations.AddMigrations(migrator)
@@ -347,7 +350,11 @@ func InitTestDB(t *testing.T) *SqlStore {
t.Fatalf("Failed to init test database: %v", err)
}
dialect = migrator.NewDialect(engine)
sqlstore.Dialect = migrator.NewDialect(engine)
// temp global var until we get rid of global vars
dialect = sqlstore.Dialect
if err := dialect.CleanDB(); err != nil {
t.Fatalf("Failed to clean test db %v", err)
}
+16 -10
View File
@@ -86,9 +86,10 @@ func (e *CloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
}
func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{
results := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult),
}
resultChan := make(chan *tsdb.QueryResult, len(queryContext.Queries))
eg, ectx := errgroup.WithContext(ctx)
@@ -102,10 +103,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
RefId := queryContext.Queries[i].RefId
query, err := parseQuery(queryContext.Queries[i].Model)
if err != nil {
result.Results[RefId] = &tsdb.QueryResult{
results.Results[RefId] = &tsdb.QueryResult{
Error: err,
}
return result, nil
return results, nil
}
query.RefId = RefId
@@ -118,10 +119,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
}
if query.Id == "" && query.Expression != "" {
result.Results[query.RefId] = &tsdb.QueryResult{
results.Results[query.RefId] = &tsdb.QueryResult{
Error: fmt.Errorf("Invalid query: id should be set if using expression"),
}
return result, nil
return results, nil
}
eg.Go(func() error {
@@ -130,12 +131,13 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
return err
}
if err != nil {
result.Results[query.RefId] = &tsdb.QueryResult{
resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
Error: err,
}
return nil
}
result.Results[queryRes.RefId] = queryRes
resultChan <- queryRes
return nil
})
}
@@ -149,10 +151,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
return err
}
for _, queryRes := range queryResponses {
result.Results[queryRes.RefId] = queryRes
if err != nil {
result.Results[queryRes.RefId].Error = err
queryRes.Error = err
}
resultChan <- queryRes
}
return nil
})
@@ -162,8 +164,12 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
if err := eg.Wait(); err != nil {
return nil, err
}
close(resultChan)
for result := range resultChan {
results.Results[result.RefId] = result
}
return result, nil
return results, nil
}
func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatchQuery, queryContext *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
+29 -2
View File
@@ -234,10 +234,37 @@ func parseMultiSelectValue(input string) []string {
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
func (e *CloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
regions := []string{
"ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-south-1", "ca-central-1", "cn-north-1", "cn-northwest-1",
"eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-gov-west-1", "us-west-1", "us-west-2", "us-isob-east-1", "us-iso-east-1",
"ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1",
"eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "me-south-1", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2",
"cn-north-1", "cn-northwest-1", "us-gov-east-1", "us-gov-west-1", "us-isob-east-1", "us-iso-east-1",
}
err := e.ensureClientSession("us-east-1")
if err != nil {
return nil, err
}
r, err := e.ec2Svc.DescribeRegions(&ec2.DescribeRegionsInput{})
if err != nil {
// ignore error for backward compatibility
plog.Error("Failed to get regions", "error", err)
} else {
for _, region := range r.Regions {
exists := false
for _, existingRegion := range regions {
if existingRegion == *region.RegionName {
exists = true
break
}
}
if !exists {
regions = append(regions, *region.RegionName)
}
}
}
sort.Strings(regions)
result := make([]suggestData, 0)
for _, region := range regions {
result = append(result, suggestData{Text: region, Value: region})
+6 -1
View File
@@ -52,13 +52,18 @@ func generateConnectionString(datasource *models.DataSource) string {
}
server, port := hostParts[0], hostParts[1]
return fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s;",
encrypt := datasource.JsonData.Get("encrypt").MustString("false")
connStr := fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s;",
server,
port,
datasource.Database,
datasource.User,
password,
)
if encrypt != "false" {
connStr += fmt.Sprintf("encrypt=%s;", encrypt)
}
return connStr
}
type mssqlRowTransformer struct {
+6 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
@@ -20,10 +21,14 @@ func init() {
func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
logger := log.New("tsdb.mysql")
protocol := "tcp"
if strings.HasPrefix(datasource.Url, "/") {
protocol = "unix"
}
cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC&allowNativePasswords=true",
datasource.User,
datasource.Password,
"tcp",
protocol,
datasource.Url,
datasource.Database,
)