Merge branch 'master' into react-panels-step1
This commit is contained in:
+3
-3
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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{})
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ type Notifier interface {
|
||||
GetNotifierId() int64
|
||||
GetIsDefault() bool
|
||||
GetSendReminder() bool
|
||||
GetDisableResolveMessage() bool
|
||||
GetFrequency() time.Duration
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user