Merge remote-tracking branch 'grafana/master' into proxy-slash

* grafana/master: (127 commits)
  alerting: move all notification conditions to defaultShouldNotify
  filter NULL values for column value suggestions
  imguploader: Add support for ECS credential provider for S3
  Remove .dropdown-menu-open on body click fixes #13409
  Remove option r from ln command since its not working everywhere
  fix: updated tests
  Fix spelling of your and you're
  Changed setting to be an alerting setting
  Remove non-existing css prop
  fix: Legend to the right, as table, should follow the width prop. Removing css conflicting with baron's width calculation. #13312
  rendering: Added concurrent rendering limits
  devenv: fix uid for bulk alert dashboards
  Explore: moved code to app/features/explore
  target gfdev-prometheus datasource
  devenv: adds script for creating many dashboards with alerts
  Fix goconst issues
  When stacking graphs, always include the y-offset so that tooltips can render proper values for individual points
  provisioning: changed provisioning default update interval from 3 to 10 seconds
  Fix https://github.com/grafana/grafana/issues/13387 metric segment options displays after blur
  docs: improve oauth generic azure ad instructions
  ...
This commit is contained in:
ryan
2018-09-26 20:24:08 -07:00
387 changed files with 7179 additions and 5734 deletions
+1
View File
@@ -24,6 +24,7 @@ func GetAnnotations(c *m.ReqContext) Response {
Limit: c.QueryInt64("limit"),
Tags: c.QueryStrings("tags"),
Type: c.Query("type"),
MatchAny: c.QueryBool("matchAny"),
}
repo := annotations.GetRepository()
+1 -1
View File
@@ -320,7 +320,7 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/search/", Search)
// metrics
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(QueryMetrics))
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), Wrap(hs.QueryMetrics))
apiRoute.Get("/tsdb/testdata/scenarios", Wrap(GetTestDataScenarios))
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, Wrap(GenerateSQLTestData))
apiRoute.Get("/tsdb/testdata/random-walk", Wrap(GetTestDataRandomWalk))
+7 -3
View File
@@ -22,6 +22,10 @@ import (
"github.com/grafana/grafana/pkg/util"
)
const (
anonString = "Anonymous"
)
func isDashboardStarredByUser(c *m.ReqContext, dashID int64) (bool, error) {
if !c.IsSignedIn {
return false, nil
@@ -64,7 +68,7 @@ func GetDashboard(c *m.ReqContext) Response {
}
// Finding creator and last updater of the dashboard
updater, creator := "Anonymous", "Anonymous"
updater, creator := anonString, anonString
if dash.UpdatedBy > 0 {
updater = getUserLogin(dash.UpdatedBy)
}
@@ -128,7 +132,7 @@ func getUserLogin(userID int64) string {
query := m.GetUserByIdQuery{Id: userID}
err := bus.Dispatch(&query)
if err != nil {
return "Anonymous"
return anonString
}
return query.Result.Login
}
@@ -403,7 +407,7 @@ func GetDashboardVersion(c *m.ReqContext) Response {
return Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashID), err)
}
creator := "Anonymous"
creator := anonString
if query.Result.CreatedBy > 0 {
creator = getUserLogin(query.Result.CreatedBy)
}
+5 -7
View File
@@ -13,19 +13,20 @@ import (
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
func (hs *HTTPServer) getDatasourceByID(id int64, orgID int64, nocache bool) (*m.DataSource, error) {
func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.DataSource, error) {
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
cacheKey := fmt.Sprintf("ds-%d", id)
if !nocache {
if cached, found := hs.cache.Get(cacheKey); found {
ds := cached.(*m.DataSource)
if ds.OrgId == orgID {
if ds.OrgId == c.OrgId {
return ds, nil
}
}
}
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgID}
query := m.GetDataSourceByIdQuery{Id: id, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return nil, err
}
@@ -37,10 +38,7 @@ func (hs *HTTPServer) getDatasourceByID(id int64, orgID int64, nocache bool) (*m
func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer)
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
ds, err := hs.getDatasourceByID(c.ParamsInt64(":id"), c.OrgId, nocache)
ds, err := hs.getDatasourceFromCache(c.ParamsInt64(":id"), c)
if err != nil {
c.JsonApiErr(500, "Unable to load datasource meta data", err)
return
+1 -1
View File
@@ -29,7 +29,7 @@ func TestFormatShort(t *testing.T) {
}
if parsed != tc.interval {
t.Errorf("expectes the parsed duration to equal the interval. Got %v expected: %v", parsed, tc.interval)
t.Errorf("expects the parsed duration to equal the interval. Got %v expected: %v", parsed, tc.interval)
}
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ func toFolderDto(g guardian.DashboardGuardian, folder *m.Folder) dtos.Folder {
canAdmin, _ := g.CanAdmin()
// Finding creator and last updater of the folder
updater, creator := "Anonymous", "Anonymous"
updater, creator := anonString, anonString
if folder.CreatedBy > 0 {
creator = getUserLogin(folder.CreatedBy)
}
+4
View File
@@ -233,6 +233,10 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
}
func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
if !hs.Cfg.MetricsEndpointEnabled {
return
}
if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/metrics" {
return
}
+11 -5
View File
@@ -11,6 +11,12 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
const (
// Themes
lightName = "light"
darkName = "dark"
)
func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
settings, err := getFrontendSettingsMap(c)
if err != nil {
@@ -60,7 +66,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
OrgRole: c.OrgRole,
GravatarUrl: dtos.GetGravatarUrl(c.Email),
IsGrafanaAdmin: c.IsGrafanaAdmin,
LightTheme: prefs.Theme == "light",
LightTheme: prefs.Theme == lightName,
Timezone: prefs.Timezone,
Locale: locale,
HelpFlags1: c.HelpFlags1,
@@ -88,12 +94,12 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
}
themeURLParam := c.Query("theme")
if themeURLParam == "light" {
if themeURLParam == lightName {
data.User.LightTheme = true
data.Theme = "light"
} else if themeURLParam == "dark" {
data.Theme = lightName
} else if themeURLParam == darkName {
data.User.LightTheme = false
data.Theme = "dark"
data.Theme = darkName
}
if hasEditPermissionInFoldersQuery.Result {
+7 -7
View File
@@ -13,21 +13,21 @@ import (
)
// POST /api/tsdb/query
func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
if len(reqDto.Queries) == 0 {
return Error(400, "No queries found in query", nil)
}
dsID, err := reqDto.Queries[0].Get("datasourceId").Int64()
datasourceId, err := reqDto.Queries[0].Get("datasourceId").Int64()
if err != nil {
return Error(400, "Query missing datasourceId", nil)
}
dsQuery := m.GetDataSourceByIdQuery{Id: dsID, OrgId: c.OrgId}
if err := bus.Dispatch(&dsQuery); err != nil {
return Error(500, "failed to fetch data source", err)
ds, err := hs.getDatasourceFromCache(datasourceId, c)
if err != nil {
return Error(500, "Unable to load datasource meta data", err)
}
request := &tsdb.TsdbQuery{TimeRange: timeRange}
@@ -38,11 +38,11 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
MaxDataPoints: query.Get("maxDataPoints").MustInt64(100),
IntervalMs: query.Get("intervalMs").MustInt64(1000),
Model: query,
DataSource: dsQuery.Result,
DataSource: ds,
})
}
resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request)
resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request)
if err != nil {
return Error(500, "Metric request error", err)
}
+10 -9
View File
@@ -41,15 +41,16 @@ func (hs *HTTPServer) RenderToPng(c *m.ReqContext) {
}
result, err := hs.RenderService.Render(c.Req.Context(), rendering.Opts{
Width: width,
Height: height,
Timeout: time.Duration(timeout) * time.Second,
OrgId: c.OrgId,
UserId: c.UserId,
OrgRole: c.OrgRole,
Path: c.Params("*") + queryParams,
Timezone: queryReader.Get("tz", ""),
Encoding: queryReader.Get("encoding", ""),
Width: width,
Height: height,
Timeout: time.Duration(timeout) * time.Second,
OrgId: c.OrgId,
UserId: c.UserId,
OrgRole: c.OrgRole,
Path: c.Params("*") + queryParams,
Timezone: queryReader.Get("tz", ""),
Encoding: queryReader.Get("encoding", ""),
ConcurrentLimit: 30,
})
if err != nil && err == rendering.ErrTimeout {
+6
View File
@@ -4,6 +4,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@@ -17,6 +18,11 @@ func GetTeamMembers(c *m.ReqContext) Response {
for _, member := range query.Result {
member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
member.Labels = []string{}
if setting.IsEnterprise && setting.LdapEnabled && member.External {
member.Labels = append(member.Labels, "LDAP")
}
}
return JSON(200, query.Result)
@@ -112,7 +112,7 @@ func SelectVersion(plugin m.Plugin, version string) (m.Version, error) {
}
}
return m.Version{}, errors.New("Could not find the version your looking for")
return m.Version{}, errors.New("Could not find the version you're looking for")
}
func RemoveGitBuildFromName(pluginName, filename string) string {
@@ -16,7 +16,7 @@ func upgradeCommand(c CommandLine) error {
return err
}
v, err2 := s.GetPlugin(localPlugin.Id, c.RepoDirectory())
v, err2 := s.GetPlugin(pluginName, c.RepoDirectory())
if err2 != nil {
return err2
@@ -24,9 +24,9 @@ func upgradeCommand(c CommandLine) error {
if ShouldUpgrade(localPlugin.Info.Version, v) {
s.RemoveInstalledPlugin(pluginsDir, pluginName)
return InstallPlugin(localPlugin.Id, "", c)
return InstallPlugin(pluginName, "", c)
}
logger.Infof("%s %s is up to date \n", color.GreenString("✔"), localPlugin.Id)
logger.Infof("%s %s is up to date \n", color.GreenString("✔"), pluginName)
return nil
}
+2 -2
View File
@@ -63,7 +63,7 @@ func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
var data m.PluginRepo
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error:", err)
logger.Info("Failed to unmarshal plugin repo response error:", err)
return m.PluginRepo{}, err
}
@@ -140,7 +140,7 @@ func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
var data m.Plugin
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error:", err)
logger.Info("Failed to unmarshal plugin repo response error:", err)
return m.Plugin{}, err
}
@@ -52,7 +52,7 @@ func (az *AzureBlobUploader) Upload(ctx context.Context, imageDiskPath string) (
}
randomFileName := util.GetRandomString(30) + ".png"
// upload image
az.log.Debug("Uploading image to azure_blob", "conatiner_name", az.container_name, "blob_name", randomFileName)
az.log.Debug("Uploading image to azure_blob", "container_name", az.container_name, "blob_name", randomFileName)
resp, err := blob.FileUpload(az.container_name, randomFileName, file)
if err != nil {
return "", err
@@ -274,10 +274,10 @@ func (a *Auth) canonicalizedHeaders(req *http.Request) string {
}
}
splitted := strings.Split(buffer.String(), "\n")
sort.Strings(splitted)
split := strings.Split(buffer.String(), "\n")
sort.Strings(split)
return strings.Join(splitted, "\n")
return strings.Join(split, "\n")
}
/*
@@ -313,8 +313,8 @@ func (a *Auth) canonicalizedResource(req *http.Request) string {
buffer.WriteString(fmt.Sprintf("\n%s:%s", key, strings.Join(values, ",")))
}
splitted := strings.Split(buffer.String(), "\n")
sort.Strings(splitted)
split := strings.Split(buffer.String(), "\n")
sort.Strings(split)
return strings.Join(splitted, "\n")
return strings.Join(split, "\n")
}
+28 -1
View File
@@ -2,12 +2,15 @@ package imguploader
import (
"context"
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/session"
@@ -50,7 +53,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
SecretAccessKey: u.secretKey,
}},
&credentials.EnvProvider{},
&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute},
remoteCredProvider(sess),
})
cfg := &aws.Config{
Region: aws.String(u.region),
@@ -85,3 +88,27 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
}
return image_url, nil
}
func remoteCredProvider(sess *session.Session) credentials.Provider {
ecsCredURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
if len(ecsCredURI) > 0 {
return ecsCredProvider(sess, ecsCredURI)
}
return ec2RoleProvider(sess)
}
func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
const host = `169.254.170.2`
d := defaults.Get()
return endpointcreds.NewProviderClient(
*d.Config,
d.Handlers,
fmt.Sprintf("http://%s%s", host, uri),
func(p *endpointcreds.Provider) { p.ExpiryWindow = 5 * time.Minute })
}
func ec2RoleProvider(sess *session.Session) credentials.Provider {
return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}
}
+8 -4
View File
@@ -8,6 +8,10 @@ import (
"strconv"
)
const (
nullString = "null"
)
// Float is a nullable float64.
// It does not consider zero values to be null.
// It will decode to null, not zero, if null.
@@ -68,7 +72,7 @@ func (f *Float) UnmarshalJSON(data []byte) error {
// It will return an error if the input is not an integer, blank, or "null".
func (f *Float) UnmarshalText(text []byte) error {
str := string(text)
if str == "" || str == "null" {
if str == "" || str == nullString {
f.Valid = false
return nil
}
@@ -82,7 +86,7 @@ func (f *Float) UnmarshalText(text []byte) error {
// It will encode null if this Float is null.
func (f Float) MarshalJSON() ([]byte, error) {
if !f.Valid {
return []byte("null"), nil
return []byte(nullString), nil
}
return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
}
@@ -100,7 +104,7 @@ func (f Float) MarshalText() ([]byte, error) {
// It will encode a blank string if this Float is null.
func (f Float) String() string {
if !f.Valid {
return "null"
return nullString
}
return fmt.Sprintf("%1.3f", f.Float64)
@@ -109,7 +113,7 @@ func (f Float) String() string {
// FullString returns float as string in full precision
func (f Float) FullString() string {
if !f.Valid {
return "null"
return nullString
}
return fmt.Sprintf("%f", f.Float64)
+3 -3
View File
@@ -256,7 +256,7 @@ func (j *Json) StringArray() ([]string, error) {
// MustArray guarantees the return of a `[]interface{}` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// useful when you want to iterate over array values in a succinct manner:
// for i, v := range js.Get("results").MustArray() {
// fmt.Println(i, v)
// }
@@ -281,7 +281,7 @@ func (j *Json) MustArray(args ...[]interface{}) []interface{} {
// MustMap guarantees the return of a `map[string]interface{}` (with optional default)
//
// useful when you want to interate over map values in a succinct manner:
// useful when you want to iterate over map values in a succinct manner:
// for k, v := range js.Get("dictionary").MustMap() {
// fmt.Println(k, v)
// }
@@ -329,7 +329,7 @@ func (j *Json) MustString(args ...string) string {
// MustStringArray guarantees the return of a `[]string` (with optional default)
//
// useful when you want to interate over array values in a succinct manner:
// useful when you want to iterate over array values in a succinct manner:
// for i, s := range js.Get("results").MustStringArray() {
// fmt.Println(i, s)
// }
+10 -6
View File
@@ -326,15 +326,19 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
a.log.Info("Searching for user's groups", "filter", filter)
// support old way of reading settings
groupIdAttribute := a.server.Attr.MemberOf
// but prefer dn attribute if default settings are used
if groupIdAttribute == "" || groupIdAttribute == "memberOf" {
groupIdAttribute = "dn"
}
groupSearchReq := ldap.SearchRequest{
BaseDN: groupSearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
Attributes: []string{
// Here MemberOf would be the thing that identifies the group, which is normally 'cn'
a.server.Attr.MemberOf,
},
Filter: filter,
Attributes: []string{groupIdAttribute},
Filter: filter,
}
groupSearchResult, err = a.conn.Search(&groupSearchReq)
@@ -344,7 +348,7 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) {
if len(groupSearchResult.Entries) > 0 {
for i := range groupSearchResult.Entries {
memberOf = append(memberOf, getLdapAttrN(a.server.Attr.MemberOf, groupSearchResult, i))
memberOf = append(memberOf, getLdapAttrN(groupIdAttribute, groupSearchResult, i))
}
break
}
+1 -1
View File
@@ -48,7 +48,7 @@ type LdapAttributeMap struct {
type LdapGroupToOrgRole struct {
GroupDN string `toml:"group_dn"`
OrgId int64 `toml:"org_id"`
IsGrafanaAdmin *bool `toml:"grafana_admin"` // This is a pointer to know if it was set or not (for backwards compatability)
IsGrafanaAdmin *bool `toml:"grafana_admin"` // This is a pointer to know if it was set or not (for backwards compatibility)
OrgRole m.RoleType `toml:"org_role"`
}
+58 -28
View File
@@ -61,6 +61,23 @@ var (
M_Grafana_Version *prometheus.GaugeVec
)
func newCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues ...string) *prometheus.CounterVec {
counter := prometheus.NewCounterVec(opts, labels)
for _, label := range labelValues {
counter.WithLabelValues(label).Add(0)
}
return counter
}
func newCounterStartingAtZero(opts prometheus.CounterOpts, labelValues ...string) prometheus.Counter {
counter := prometheus.NewCounter(opts)
counter.Add(0)
return counter
}
func init() {
M_Instance_Start = prometheus.NewCounter(prometheus.CounterOpts{
Name: "instance_start_total",
@@ -68,32 +85,27 @@ func init() {
Namespace: exporterName,
})
M_Page_Status = prometheus.NewCounterVec(
httpStatusCodes := []string{"200", "404", "500", "unknown"}
M_Page_Status = newCounterVecStartingAtZero(
prometheus.CounterOpts{
Name: "page_response_status_total",
Help: "page http response status",
Namespace: exporterName,
},
[]string{"code"},
)
}, []string{"code"}, httpStatusCodes...)
M_Api_Status = prometheus.NewCounterVec(
M_Api_Status = newCounterVecStartingAtZero(
prometheus.CounterOpts{
Name: "api_response_status_total",
Help: "api http response status",
Namespace: exporterName,
},
[]string{"code"},
)
}, []string{"code"}, httpStatusCodes...)
M_Proxy_Status = prometheus.NewCounterVec(
M_Proxy_Status = newCounterVecStartingAtZero(
prometheus.CounterOpts{
Name: "proxy_response_status_total",
Help: "proxy http response status",
Namespace: exporterName,
},
[]string{"code"},
)
}, []string{"code"}, httpStatusCodes...)
M_Http_Request_Total = prometheus.NewCounterVec(
prometheus.CounterOpts{
@@ -111,19 +123,19 @@ func init() {
[]string{"handler", "statuscode", "method"},
)
M_Api_User_SignUpStarted = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_User_SignUpStarted = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_user_signup_started_total",
Help: "amount of users who started the signup flow",
Namespace: exporterName,
})
M_Api_User_SignUpCompleted = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_User_SignUpCompleted = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_user_signup_completed_total",
Help: "amount of users who completed the signup flow",
Namespace: exporterName,
})
M_Api_User_SignUpInvite = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_User_SignUpInvite = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_user_signup_invite_total",
Help: "amount of users who have been invited",
Namespace: exporterName,
@@ -147,49 +159,49 @@ func init() {
Namespace: exporterName,
})
M_Api_Admin_User_Create = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Admin_User_Create = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_admin_user_created_total",
Help: "api admin user created counter",
Namespace: exporterName,
})
M_Api_Login_Post = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Login_Post = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_login_post_total",
Help: "api login post counter",
Namespace: exporterName,
})
M_Api_Login_OAuth = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Login_OAuth = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_login_oauth_total",
Help: "api login oauth counter",
Namespace: exporterName,
})
M_Api_Org_Create = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Org_Create = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_org_create_total",
Help: "api org created counter",
Namespace: exporterName,
})
M_Api_Dashboard_Snapshot_Create = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Dashboard_Snapshot_Create = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_dashboard_snapshot_create_total",
Help: "dashboard snapshots created",
Namespace: exporterName,
})
M_Api_Dashboard_Snapshot_External = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Dashboard_Snapshot_External = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_dashboard_snapshot_external_total",
Help: "external dashboard snapshots created",
Namespace: exporterName,
})
M_Api_Dashboard_Snapshot_Get = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Dashboard_Snapshot_Get = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_dashboard_snapshot_get_total",
Help: "loaded dashboards",
Namespace: exporterName,
})
M_Api_Dashboard_Insert = prometheus.NewCounter(prometheus.CounterOpts{
M_Api_Dashboard_Insert = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "api_models_dashboard_insert_total",
Help: "dashboards inserted ",
Namespace: exporterName,
@@ -207,25 +219,25 @@ func init() {
Namespace: exporterName,
}, []string{"type"})
M_Aws_CloudWatch_GetMetricStatistics = prometheus.NewCounter(prometheus.CounterOpts{
M_Aws_CloudWatch_GetMetricStatistics = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "aws_cloudwatch_get_metric_statistics_total",
Help: "counter for getting metric statistics from aws",
Namespace: exporterName,
})
M_Aws_CloudWatch_ListMetrics = prometheus.NewCounter(prometheus.CounterOpts{
M_Aws_CloudWatch_ListMetrics = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "aws_cloudwatch_list_metrics_total",
Help: "counter for getting list of metrics from aws",
Namespace: exporterName,
})
M_Aws_CloudWatch_GetMetricData = prometheus.NewCounter(prometheus.CounterOpts{
M_Aws_CloudWatch_GetMetricData = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "aws_cloudwatch_get_metric_data_total",
Help: "counter for getting metric data time series from aws",
Namespace: exporterName,
})
M_DB_DataSource_QueryById = prometheus.NewCounter(prometheus.CounterOpts{
M_DB_DataSource_QueryById = newCounterStartingAtZero(prometheus.CounterOpts{
Name: "db_datasource_query_by_id_total",
Help: "counter for getting datasource by id",
Namespace: exporterName,
@@ -350,7 +362,7 @@ func getEdition() string {
}
}
func sendUsageStats() {
func sendUsageStats(oauthProviders map[string]bool) {
if !setting.ReportingEnabled {
return
}
@@ -450,6 +462,24 @@ func sendUsageStats() {
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
}
authTypes := map[string]bool{}
authTypes["anonymous"] = setting.AnonymousEnabled
authTypes["basic_auth"] = setting.BasicAuthEnabled
authTypes["ldap"] = setting.LdapEnabled
authTypes["auth_proxy"] = setting.AuthProxyEnabled
for provider, enabled := range oauthProviders {
authTypes["oauth_"+provider] = enabled
}
for authType, enabled := range authTypes {
enabledValue := 0
if enabled {
enabledValue = 1
}
metrics["stats.auth_enabled."+authType+".count"] = enabledValue
}
out, _ := json.MarshalIndent(report, "", " ")
data := bytes.NewBuffer(out)
+26 -3
View File
@@ -147,11 +147,19 @@ func TestMetrics(t *testing.T) {
}))
usageStatsURL = ts.URL
sendUsageStats()
oauthProviders := map[string]bool{
"github": true,
"gitlab": true,
"google": true,
"generic_oauth": true,
"grafana_com": true,
}
sendUsageStats(oauthProviders)
Convey("Given reporting not enabled and sending usage stats", func() {
setting.ReportingEnabled = false
sendUsageStats()
sendUsageStats(oauthProviders)
Convey("Should not gather stats or call http endpoint", func() {
So(getSystemStatsQuery, ShouldBeNil)
@@ -164,8 +172,13 @@ func TestMetrics(t *testing.T) {
Convey("Given reporting enabled and sending usage stats", func() {
setting.ReportingEnabled = true
setting.BuildVersion = "5.0.0"
setting.AnonymousEnabled = true
setting.BasicAuthEnabled = true
setting.LdapEnabled = true
setting.AuthProxyEnabled = true
wg.Add(1)
sendUsageStats()
sendUsageStats(oauthProviders)
Convey("Should gather stats and call http endpoint", func() {
if waitTimeout(&wg, 2*time.Second) {
@@ -220,6 +233,16 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.alert_notifiers.slack.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.alert_notifiers.webhook.count").MustInt(), ShouldEqual, 2)
So(metrics.Get("stats.auth_enabled.anonymous.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.basic_auth.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.ldap.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.auth_proxy.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_github.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_gitlab.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_google.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_generic_oauth.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.auth_enabled.oauth_grafana_com.count").MustInt(), ShouldEqual, 1)
})
})
+2 -2
View File
@@ -28,9 +28,9 @@ func init() {
type InternalMetricsService struct {
Cfg *setting.Cfg `inject:""`
enabled bool
intervalSeconds int64
graphiteCfg *graphitebridge.Config
oauthProviders map[string]bool
}
func (im *InternalMetricsService) Init() error {
@@ -61,7 +61,7 @@ func (im *InternalMetricsService) Run(ctx context.Context) error {
for {
select {
case <-onceEveryDayTick.C:
sendUsageStats()
sendUsageStats(im.oauthProviders)
case <-everyMinuteTicker.C:
updateTotalStats()
case <-ctx.Done():
+4 -5
View File
@@ -5,6 +5,8 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/social"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
@@ -16,17 +18,14 @@ func (im *InternalMetricsService) readSettings() error {
return fmt.Errorf("Unable to find metrics config section %v", err)
}
im.enabled = section.Key("enabled").MustBool(false)
im.intervalSeconds = section.Key("interval_seconds").MustInt64(10)
if !im.enabled {
return nil
}
if err := im.parseGraphiteSettings(); err != nil {
return fmt.Errorf("Unable to parse metrics graphite section, %v", err)
}
im.oauthProviders = social.GetOAuthProviders(im.Cfg)
return nil
}
+1 -1
View File
@@ -98,7 +98,7 @@ type GetLatestNotificationQuery struct {
AlertId int64
NotifierId int64
Result *AlertNotificationJournal
Result []AlertNotificationJournal
}
type CleanNotificationJournalCommand struct {
+22 -17
View File
@@ -12,10 +12,11 @@ var (
// TeamMember model
type TeamMember struct {
Id int64
OrgId int64
TeamId int64
UserId int64
Id int64
OrgId int64
TeamId int64
UserId int64
External bool
Created time.Time
Updated time.Time
@@ -25,9 +26,10 @@ type TeamMember struct {
// COMMANDS
type AddTeamMemberCommand struct {
UserId int64 `json:"userId" binding:"Required"`
OrgId int64 `json:"-"`
TeamId int64 `json:"-"`
UserId int64 `json:"userId" binding:"Required"`
OrgId int64 `json:"-"`
TeamId int64 `json:"-"`
External bool `json:"-"`
}
type RemoveTeamMemberCommand struct {
@@ -40,20 +42,23 @@ type RemoveTeamMemberCommand struct {
// QUERIES
type GetTeamMembersQuery struct {
OrgId int64
TeamId int64
UserId int64
Result []*TeamMemberDTO
OrgId int64
TeamId int64
UserId int64
External bool
Result []*TeamMemberDTO
}
// ----------------------
// Projections and DTOs
type TeamMemberDTO struct {
OrgId int64 `json:"orgId"`
TeamId int64 `json:"teamId"`
UserId int64 `json:"userId"`
Email string `json:"email"`
Login string `json:"login"`
AvatarUrl string `json:"avatarUrl"`
OrgId int64 `json:"orgId"`
TeamId int64 `json:"teamId"`
UserId int64 `json:"userId"`
External bool `json:"-"`
Email string `json:"email"`
Login string `json:"login"`
AvatarUrl string `json:"avatarUrl"`
Labels []string `json:"labels"`
}
+2 -2
View File
@@ -35,7 +35,7 @@ func TestDashboardImport(t *testing.T) {
So(cmd.Result, ShouldNotBeNil)
resultStr, _ := mock.SavedDashboards[0].Dashboard.Data.EncodePretty()
expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json")
expectedBytes, _ := ioutil.ReadFile("testdata/test-app/dashboards/connections_result.json")
expectedJson, _ := simplejson.NewJson(expectedBytes)
expectedStr, _ := expectedJson.EncodePretty()
@@ -89,7 +89,7 @@ func pluginScenario(desc string, t *testing.T, fn func()) {
Convey("Given a plugin", t, func() {
setting.Raw = ini.Empty()
sec, _ := setting.Raw.NewSection("plugin.test-app")
sec.NewKey("path", "../../tests/test-app")
sec.NewKey("path", "testdata/test-app")
pm := &PluginManager{}
err := pm.Init()
+1 -1
View File
@@ -16,7 +16,7 @@ func TestPluginDashboards(t *testing.T) {
Convey("When asking plugin dashboard info", t, func() {
setting.Raw = ini.Empty()
sec, _ := setting.Raw.NewSection("plugin.test-app")
sec.NewKey("path", "../../tests/test-app")
sec.NewKey("path", "testdata/test-app")
pm := &PluginManager{}
err := pm.Init()
+1 -5
View File
@@ -48,11 +48,7 @@ func autoUpdateAppDashboard(pluginDashInfo *PluginDashboardInfoDTO, orgId int64)
Path: pluginDashInfo.Path,
}
if err := bus.Dispatch(&updateCmd); err != nil {
return err
}
return nil
return bus.Dispatch(&updateCmd)
}
func syncPluginDashboards(pluginDef *PluginBase, orgId int64) {
+1 -1
View File
@@ -30,7 +30,7 @@ func TestPluginScans(t *testing.T) {
Convey("When reading app plugin definition", t, func() {
setting.Raw = ini.Empty()
sec, _ := setting.Raw.NewSection("plugin.nginx-app")
sec.NewKey("path", "../../tests/test-app")
sec.NewKey("path", "testdata/test-app")
pm := &PluginManager{}
err := pm.Init()
+50
View File
@@ -0,0 +1,50 @@
System.register([], function (_export) {
"use strict";
return {
setters: [],
execute: function () {
function Datasource(instanceSettings, backendSrv) {
this.url = instanceSettings.url;
// this.testDatasource = function() {
// return backendSrv.datasourceRequest({
// method: 'GET',
// url: this.url + '/api/v4/search'
// });
// }
//
this.testDatasource = function() {
return backendSrv.datasourceRequest({
method: 'GET',
url: this.url + '/tokenTest'
});
}
}
function ConfigCtrl() {
}
ConfigCtrl.template = `
<div class="gf-form">
<label class="gf-form-label width-13">TenantId </label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.tenantId'></input>
</div>
<div class="gf-form">
<label class="gf-form-label width-13">ClientId </label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.clientId'></input>
</div>
<div class="gf-form">
<label class="gf-form-label width-13">Client secret</label>
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.secureJsonData.clientSecret'></input>
</div>
`;
_export('Datasource', Datasource);
_export('ConfigCtrl', ConfigCtrl);
}
};
});
+31
View File
@@ -0,0 +1,31 @@
{
"type": "datasource",
"name": "Test Datasource",
"id": "test-ds",
"routes": [
{
"path": "tokenTest",
"method": "*",
"url": "https://management.azure.com",
"tokenAuth": {
"url": "https://login.microsoftonline.com/{{.JsonData.tenantId}}/oauth2/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{.JsonData.clientId}}",
"client_secret": "{{.SecureJsonData.clientSecret}}",
"resource": "https://management.azure.com/"
}
}
},
{
"path": "api/v4/",
"method": "*",
"url": "http://localhost:3333",
"headers": [
{"name": "X-CH-Auth-API-Token", "content": "test {{.SecureJsonData.token}}"},
{"name": "X-CH-Auth-Email", "content": "test {{.JsonData.email}}"}
]
}
]
}
@@ -0,0 +1,29 @@
{
"__inputs": [
{
"name": "DS_NAME",
"type": "datasource",
"pluginId": "graphite"
}
],
"uid": "1MHHlVjzz",
"title": "Nginx Connections",
"revision": 25,
"schemaVersion": 11,
"tags": ["tag1", "tag2"],
"number_array": [1,2,3,10.33],
"boolean_true": true,
"boolean_false": false,
"rows": [
{
"panels": [
{
"type": "graph",
"datasource": "${DS_NAME}"
}
]
}
]
}
@@ -0,0 +1,21 @@
{
"revision": 25,
"tags": ["tag1", "tag2"],
"boolean_false": false,
"boolean_true": true,
"number_array": [1,2,3,10.33],
"rows": [
{
"panels": [
{
"datasource": "graphite",
"type": "graph"
}
]
}
],
"schemaVersion": 11,
"title": "Nginx Connections",
"uid": "1MHHlVjzz",
"version": 0
}
+5
View File
@@ -0,0 +1,5 @@
{
"title": "Nginx Memory",
"revision": 2,
"schemaVersion": 11
}
+55
View File
@@ -0,0 +1,55 @@
{
"type": "app",
"name": "Test App",
"id": "test-app",
"staticRoot": ".",
"pages": [
{ "name": "Live stream", "component": "StreamPageCtrl", "role": "Editor"},
{ "name": "Log view", "component": "LogsPageCtrl", "role": "Viewer"}
],
"css": {
"dark": "css/dark.css",
"light": "css/light.css"
},
"info": {
"description": "Official Grafana Test App & Dashboard bundle",
"author": {
"name": "Test Inc.",
"url": "http://test.com"
},
"keywords": ["test"],
"logos": {
"small": "img/logo_small.png",
"large": "img/logo_large.png"
},
"screenshots": [
{"name": "img1", "path": "img/screenshot1.png"},
{"name": "img2", "path": "img/screenshot2.png"}
],
"links": [
{"name": "Project site", "url": "http://project.com"},
{"name": "License & Terms", "url": "http://license.com"}
],
"version": "1.0.0",
"updated": "2015-02-10"
},
"includes": [
{"type": "dashboard", "name": "Nginx Connections", "path": "dashboards/connections.json"},
{"type": "dashboard", "name": "Nginx Memory", "path": "dashboards/memory.json"},
{"type": "panel", "name": "Nginx Panel"},
{"type": "datasource", "name": "Nginx Datasource"}
],
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": [
{"type": "datasource", "id": "graphite", "name": "Graphite", "version": "1.0.0"},
{"type": "panel", "id": "graph", "name": "Graph", "version": "1.0.0"}
]
}
}
+3 -2
View File
@@ -82,12 +82,13 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels
als, err := e.getAlertFromPanels(panel, validateAlertFunc)
alertSlice, err := e.getAlertFromPanels(panel,
validateAlertFunc)
if err != nil {
return nil, err
}
alerts = append(alerts, als...)
alerts = append(alerts, alertSlice...)
continue
}
+8 -6
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
m "github.com/grafana/grafana/pkg/models"
)
@@ -67,7 +68,7 @@ func (n *notificationService) sendNotifications(evalContext *EvalContext, notifi
// Verify that we can send the notification again
// but this time within the same transaction.
if !evalContext.IsTestRun && !not.ShouldNotify(context.Background(), evalContext) {
if !evalContext.IsTestRun && !not.ShouldNotify(ctx, evalContext) {
return nil
}
@@ -108,11 +109,12 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
}
renderOpts := rendering.Opts{
Width: 1000,
Height: 500,
Timeout: alertTimeout / 2,
OrgId: context.Rule.OrgId,
OrgRole: m.ROLE_ADMIN,
Width: 1000,
Height: 500,
Timeout: alertTimeout / 2,
OrgId: context.Rule.OrgId,
OrgRole: m.ROLE_ADMIN,
ConcurrentLimit: setting.AlertingRenderLimit,
}
ref, err := context.GetDashboardUID()
+15 -10
View File
@@ -11,6 +11,10 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
)
const (
triggMetrString = "Triggered metrics:\n\n"
)
type NotifierBase struct {
Name string
Type string
@@ -42,12 +46,21 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase {
}
}
func defaultShouldNotify(context *alerting.EvalContext, sendReminder bool, frequency time.Duration, lastNotify time.Time) bool {
func defaultShouldNotify(context *alerting.EvalContext, sendReminder bool, frequency time.Duration, journals []models.AlertNotificationJournal) bool {
// Only notify on state change.
if context.PrevAlertState == context.Rule.State && !sendReminder {
return false
}
// get last successfully sent notification
lastNotify := time.Time{}
for _, j := range journals {
if j.Success {
lastNotify = time.Unix(j.SentAt, 0)
break
}
}
// Do not notify if interval has not elapsed
if sendReminder && !lastNotify.IsZero() && lastNotify.Add(frequency).After(time.Now()) {
return false
@@ -75,20 +88,12 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, c *alerting.EvalContext
}
err := bus.DispatchCtx(ctx, cmd)
if err == models.ErrJournalingNotFound {
return true
}
if err != nil {
n.log.Error("Could not determine last time alert notifier fired", "Alert name", c.Rule.Name, "Error", err)
return false
}
if !cmd.Result.Success {
return true
}
return defaultShouldNotify(c, n.SendReminder, n.Frequency, time.Unix(cmd.Result.SentAt, 0))
return defaultShouldNotify(c, n.SendReminder, n.Frequency, cmd.Result)
}
func (n *NotifierBase) GetType() string {
+73 -29
View File
@@ -15,51 +15,105 @@ import (
)
func TestShouldSendAlertNotification(t *testing.T) {
tnow := time.Now()
tcs := []struct {
name string
prevState m.AlertStateType
newState m.AlertStateType
expected bool
sendReminder bool
frequency time.Duration
journals []m.AlertNotificationJournal
expect bool
}{
{
name: "pending -> ok should not trigger an notification",
newState: m.AlertStatePending,
prevState: m.AlertStateOK,
expected: false,
name: "pending -> ok should not trigger an notification",
newState: m.AlertStatePending,
prevState: m.AlertStateOK,
sendReminder: false,
journals: []m.AlertNotificationJournal{},
expect: false,
},
{
name: "ok -> alerting should trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
expected: true,
name: "ok -> alerting should trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
sendReminder: false,
journals: []m.AlertNotificationJournal{},
expect: true,
},
{
name: "ok -> pending should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStatePending,
expected: false,
name: "ok -> pending should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStatePending,
sendReminder: false,
journals: []m.AlertNotificationJournal{},
expect: false,
},
{
name: "ok -> ok should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
expected: false,
sendReminder: false,
journals: []m.AlertNotificationJournal{},
expect: false,
},
{
name: "ok -> alerting should not trigger an notification",
name: "ok -> alerting should trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
expected: true,
sendReminder: true,
journals: []m.AlertNotificationJournal{},
expect: true,
},
{
name: "ok -> ok with reminder should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
expected: false,
sendReminder: true,
journals: []m.AlertNotificationJournal{},
expect: false,
},
{
name: "alerting -> alerting with reminder and no journaling should trigger",
newState: m.AlertStateAlerting,
prevState: m.AlertStateAlerting,
frequency: time.Minute * 10,
sendReminder: true,
journals: []m.AlertNotificationJournal{},
expect: true,
},
{
name: "alerting -> alerting with reminder and successful recent journal event should not trigger",
newState: m.AlertStateAlerting,
prevState: m.AlertStateAlerting,
frequency: time.Minute * 10,
sendReminder: true,
journals: []m.AlertNotificationJournal{
{SentAt: tnow.Add(-time.Minute).Unix(), Success: true},
},
expect: false,
},
{
name: "alerting -> alerting with reminder and failed recent journal event should trigger",
newState: m.AlertStateAlerting,
prevState: m.AlertStateAlerting,
frequency: time.Minute * 10,
sendReminder: true,
expect: true,
journals: []m.AlertNotificationJournal{
{SentAt: tnow.Add(-time.Minute).Unix(), Success: false}, // recent failed notification
{SentAt: tnow.Add(-time.Hour).Unix(), Success: true}, // old successful notification
},
},
}
@@ -69,8 +123,8 @@ func TestShouldSendAlertNotification(t *testing.T) {
})
evalContext.Rule.State = tc.prevState
if defaultShouldNotify(evalContext, true, 0, time.Now()) != tc.expected {
t.Errorf("failed %s. expected %+v to return %v", tc.name, tc, tc.expected)
if defaultShouldNotify(evalContext, true, tc.frequency, tc.journals) != tc.expect {
t.Errorf("failed test %s.\n expected \n%+v \nto return: %v", tc.name, tc, tc.expect)
}
}
}
@@ -87,16 +141,6 @@ func TestShouldNotifyWhenNoJournalingIsFound(t *testing.T) {
})
evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{})
Convey("should notify if no journaling is found", func() {
bus.AddHandlerCtx("", func(ctx context.Context, q *m.GetLatestNotificationQuery) error {
return m.ErrJournalingNotFound
})
if !notifier.ShouldNotify(context.Background(), evalContext) {
t.Errorf("should send notifications when ErrJournalingNotFound is returned")
}
})
Convey("should not notify query returns error", func() {
bus.AddHandlerCtx("", func(ctx context.Context, q *m.GetLatestNotificationQuery) error {
return errors.New("some kind of error unknown error")
+1 -1
View File
@@ -125,7 +125,7 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
case models.AlertStateOK:
color = "green"
case models.AlertStateNoData:
color = "grey"
color = "gray"
case models.AlertStateAlerting:
color = "red"
}
+1 -1
View File
@@ -61,7 +61,7 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
state := evalContext.Rule.State
customData := "Triggered metrics:\n\n"
customData := triggMetrString
for _, evt := range evalContext.EvalMatches {
customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
}
+1 -1
View File
@@ -95,7 +95,7 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err
return err
}
customData := "Triggered metrics:\n\n"
customData := triggMetrString
for _, evt := range evalContext.EvalMatches {
customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
}
+1 -1
View File
@@ -76,7 +76,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
if evalContext.Rule.State == m.AlertStateOK {
eventType = "resolve"
}
customData := "Triggered metrics:\n\n"
customData := triggMetrString
for _, evt := range evalContext.EvalMatches {
customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
}
+1 -1
View File
@@ -74,7 +74,7 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
}
message := ""
if evalContext.Rule.State != m.AlertStateOK { //dont add message when going back to alert state ok.
if evalContext.Rule.State != m.AlertStateOK { //don't add message when going back to alert state ok.
message = evalContext.Rule.Message
}
+1 -1
View File
@@ -100,7 +100,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
}
}
}
handler.notifier.SendIfNeeded(evalContext)
handler.notifier.SendIfNeeded(evalContext)
return nil
}
+1
View File
@@ -21,6 +21,7 @@ type ItemQuery struct {
RegionId int64 `json:"regionId"`
Tags []string `json:"tags"`
Type string `json:"type"`
MatchAny bool `json:"matchAny"`
Limit int64 `json:"limit"`
}
@@ -83,7 +83,7 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
}
if dashboards[i].UpdateIntervalSeconds == 0 {
dashboards[i].UpdateIntervalSeconds = 3
dashboards[i].UpdateIntervalSeconds = 10
}
}
@@ -70,7 +70,7 @@ func validateDashboardAsConfig(t *testing.T, cfg []*DashboardsAsConfig) {
So(len(ds.Options), ShouldEqual, 1)
So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
So(ds.DisableDeletion, ShouldBeTrue)
So(ds.UpdateIntervalSeconds, ShouldEqual, 10)
So(ds.UpdateIntervalSeconds, ShouldEqual, 15)
ds2 := cfg[1]
So(ds2.Name, ShouldEqual, "default")
@@ -81,5 +81,5 @@ func validateDashboardAsConfig(t *testing.T, cfg []*DashboardsAsConfig) {
So(len(ds2.Options), ShouldEqual, 1)
So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
So(ds2.DisableDeletion, ShouldBeFalse)
So(ds2.UpdateIntervalSeconds, ShouldEqual, 3)
So(ds2.UpdateIntervalSeconds, ShouldEqual, 10)
}
@@ -6,7 +6,7 @@ providers:
folder: 'developers'
editable: true
disableDeletion: true
updateIntervalSeconds: 10
updateIntervalSeconds: 15
type: file
options:
path: /var/lib/grafana/dashboards
@@ -3,7 +3,7 @@
folder: 'developers'
editable: true
disableDeletion: true
updateIntervalSeconds: 10
updateIntervalSeconds: 15
type: file
options:
path: /var/lib/grafana/dashboards
@@ -4,7 +4,7 @@
# org_id: 1
# # list of datasources to insert/update depending
# # whats available in the datbase
# # what's available in the database
#datasources:
# # <string, required> name of the datasource. Required
# - name: Graphite
+2 -2
View File
@@ -70,7 +70,7 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, opts Opts) (*Rend
return nil, ErrTimeout
}
// if we didnt get a 200 response, something went wrong.
// if we didn't get a 200 response, something went wrong.
if resp.StatusCode != http.StatusOK {
rs.log.Error("Remote rendering request failed", "error", resp.Status)
return nil, fmt.Errorf("Remote rendering request failed. %d: %s", resp.StatusCode, resp.Status)
@@ -83,7 +83,7 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, opts Opts) (*Rend
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
// check that we didnt timeout while receiving the response.
// check that we didn't timeout while receiving the response.
if reqContext.Err() == context.DeadlineExceeded {
rs.log.Info("Rendering timed out")
return nil, ErrTimeout
+10 -9
View File
@@ -13,15 +13,16 @@ var ErrNoRenderer = errors.New("No renderer plugin found nor is an external rend
var ErrPhantomJSNotInstalled = errors.New("PhantomJS executable not found")
type Opts struct {
Width int
Height int
Timeout time.Duration
OrgId int64
UserId int64
OrgRole models.RoleType
Path string
Encoding string
Timezone string
Width int
Height int
Timeout time.Duration
OrgId int64
UserId int64
OrgRole models.RoleType
Path string
Encoding string
Timezone string
ConcurrentLimit int
}
type RenderResult struct {
+20 -7
View File
@@ -24,12 +24,13 @@ func init() {
}
type RenderingService struct {
log log.Logger
pluginClient *plugin.Client
grpcPlugin pluginModel.RendererPlugin
pluginInfo *plugins.RendererPlugin
renderAction renderFunc
domain string
log log.Logger
pluginClient *plugin.Client
grpcPlugin pluginModel.RendererPlugin
pluginInfo *plugins.RendererPlugin
renderAction renderFunc
domain string
inProgressCount int
Cfg *setting.Cfg `inject:""`
}
@@ -45,7 +46,7 @@ func (rs *RenderingService) Init() error {
// set value used for domain attribute of renderKey cookie
if rs.Cfg.RendererUrl != "" {
// RendererCallbackUrl has already been passed, it wont generate an error.
// RendererCallbackUrl has already been passed, it won't generate an error.
u, _ := url.Parse(rs.Cfg.RendererCallbackUrl)
rs.domain = u.Hostname()
} else if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
@@ -90,6 +91,18 @@ func (rs *RenderingService) Run(ctx context.Context) error {
}
func (rs *RenderingService) Render(ctx context.Context, opts Opts) (*RenderResult, error) {
if rs.inProgressCount > opts.ConcurrentLimit {
return &RenderResult{
FilePath: filepath.Join(setting.HomePath, "public/img/rendering_limit.png"),
}, nil
}
defer func() {
rs.inProgressCount -= 1
}()
rs.inProgressCount += 1
if rs.renderAction != nil {
return rs.renderAction(ctx, opts)
} else {
+3 -3
View File
@@ -40,7 +40,7 @@ func GetAlertById(query *m.GetAlertByIdQuery) error {
func GetAllAlertQueryHandler(query *m.GetAllAlertsQuery) error {
var alerts []*m.Alert
err := x.Sql("select * from alert").Find(&alerts)
err := x.SQL("select * from alert").Find(&alerts)
if err != nil {
return err
}
@@ -190,7 +190,7 @@ func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
alert.Updated = timeNow()
alert.State = alertToUpdate.State
sess.MustCols("message")
_, err := sess.Id(alert.Id).Update(alert)
_, err := sess.ID(alert.Id).Update(alert)
if err != nil {
return err
}
@@ -249,7 +249,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
return inTransaction(func(sess *DBSession) error {
alert := m.Alert{}
if has, err := sess.Id(cmd.AlertId).Get(&alert); err != nil {
if has, err := sess.ID(cmd.AlertId).Get(&alert); err != nil {
return err
} else if !has {
return fmt.Errorf("Could not find alert")
+11 -16
View File
@@ -119,7 +119,7 @@ func getAlertNotificationInternal(query *m.GetAlertNotificationsQuery, sess *DBS
}
results := make([]*m.AlertNotification, 0)
if err := sess.Sql(sql.String(), params...).Find(&results); err != nil {
if err := sess.SQL(sql.String(), params...).Find(&results); err != nil {
return err
}
@@ -230,7 +230,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
}
func RecordNotificationJournal(ctx context.Context, cmd *m.RecordNotificationJournalCommand) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
return withDbSession(ctx, func(sess *DBSession) error {
journalEntry := &m.AlertNotificationJournal{
OrgId: cmd.OrgId,
AlertId: cmd.AlertId,
@@ -239,30 +239,25 @@ func RecordNotificationJournal(ctx context.Context, cmd *m.RecordNotificationJou
Success: cmd.Success,
}
if _, err := sess.Insert(journalEntry); err != nil {
return err
}
return nil
_, err := sess.Insert(journalEntry)
return err
})
}
func GetLatestNotification(ctx context.Context, cmd *m.GetLatestNotificationQuery) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
nj := &m.AlertNotificationJournal{}
return withDbSession(ctx, func(sess *DBSession) error {
nj := []m.AlertNotificationJournal{}
_, err := sess.Desc("alert_notification_journal.sent_at").
Limit(1).
Where("alert_notification_journal.org_id = ? AND alert_notification_journal.alert_id = ? AND alert_notification_journal.notifier_id = ?", cmd.OrgId, cmd.AlertId, cmd.NotifierId).Get(nj)
err := sess.Desc("alert_notification_journal.sent_at").
Where("alert_notification_journal.org_id = ?", cmd.OrgId).
Where("alert_notification_journal.alert_id = ?", cmd.AlertId).
Where("alert_notification_journal.notifier_id = ?", cmd.NotifierId).
Find(&nj)
if err != nil {
return err
}
if nj.AlertId == 0 && nj.Id == 0 && nj.NotifierId == 0 && nj.OrgId == 0 {
return m.ErrJournalingNotFound
}
cmd.Result = nj
return nil
})
@@ -15,16 +15,21 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
InitTestDB(t)
Convey("Alert notification journal", func() {
var alertId int64 = 5
var alertId int64 = 7
var orgId int64 = 5
var notifierId int64 = 5
var notifierId int64 = 10
Convey("Getting last journal should raise error if no one exists", func() {
query := &m.GetLatestNotificationQuery{AlertId: alertId, OrgId: orgId, NotifierId: notifierId}
err := GetLatestNotification(context.Background(), query)
So(err, ShouldEqual, m.ErrJournalingNotFound)
GetLatestNotification(context.Background(), query)
So(len(query.Result), ShouldEqual, 0)
Convey("shoulbe be able to record two journaling events", func() {
// recording an journal entry in another org to make sure org filter works as expected.
journalInOtherOrg := &m.RecordNotificationJournalCommand{AlertId: alertId, NotifierId: notifierId, OrgId: 10, Success: true, SentAt: 1}
err := RecordNotificationJournal(context.Background(), journalInOtherOrg)
So(err, ShouldBeNil)
Convey("should be able to record two journaling events", func() {
createCmd := &m.RecordNotificationJournalCommand{AlertId: alertId, NotifierId: notifierId, OrgId: orgId, Success: true, SentAt: 1}
err := RecordNotificationJournal(context.Background(), createCmd)
@@ -38,17 +43,20 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
Convey("get last journaling event", func() {
err := GetLatestNotification(context.Background(), query)
So(err, ShouldBeNil)
So(query.Result.SentAt, ShouldEqual, 1001)
So(len(query.Result), ShouldEqual, 2)
last := query.Result[0]
So(last.SentAt, ShouldEqual, 1001)
Convey("be able to clear all journaling for an notifier", func() {
cmd := &m.CleanNotificationJournalCommand{AlertId: alertId, NotifierId: notifierId, OrgId: orgId}
err := CleanNotificationJournal(context.Background(), cmd)
So(err, ShouldBeNil)
Convey("querying for last junaling should raise error", func() {
Convey("querying for last journaling should return no journal entries", func() {
query := &m.GetLatestNotificationQuery{AlertId: alertId, OrgId: orgId, NotifierId: notifierId}
err := GetLatestNotification(context.Background(), query)
So(err, ShouldEqual, m.ErrJournalingNotFound)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
})
})
})
+8 -3
View File
@@ -110,7 +110,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error {
existing.Tags = item.Tags
_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing)
_, err = sess.Table("annotation").ID(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing)
return err
})
}
@@ -211,7 +211,12 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
)
`, strings.Join(keyValueFilters, " OR "))
sql.WriteString(fmt.Sprintf(" AND (%s) = %d ", tagsSubQuery, len(tags)))
if query.MatchAny {
sql.WriteString(fmt.Sprintf(" AND (%s) > 0 ", tagsSubQuery))
} else {
sql.WriteString(fmt.Sprintf(" AND (%s) = %d ", tagsSubQuery, len(tags)))
}
}
}
@@ -223,7 +228,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
items := make([]*annotations.ItemDTO, 0)
if err := x.Sql(sql.String(), params...).Find(&items); err != nil {
if err := x.SQL(sql.String(), params...).Find(&items); err != nil {
return nil, err
}
+39 -2
View File
@@ -78,7 +78,31 @@ func TestAnnotations(t *testing.T) {
So(err, ShouldBeNil)
So(annotation2.Id, ShouldBeGreaterThan, 0)
Convey("Can query for annotation", func() {
globalAnnotation1 := &annotations.Item{
OrgId: 1,
UserId: 1,
Text: "deploy",
Type: "",
Epoch: 15,
Tags: []string{"deploy"},
}
err = repo.Save(globalAnnotation1)
So(err, ShouldBeNil)
So(globalAnnotation1.Id, ShouldBeGreaterThan, 0)
globalAnnotation2 := &annotations.Item{
OrgId: 1,
UserId: 1,
Text: "rollback",
Type: "",
Epoch: 17,
Tags: []string{"rollback"},
}
err = repo.Save(globalAnnotation2)
So(err, ShouldBeNil)
So(globalAnnotation2.Id, ShouldBeGreaterThan, 0)
Convey("Can query for annotation by dashboard id", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
DashboardId: 1,
@@ -165,7 +189,7 @@ func TestAnnotations(t *testing.T) {
OrgId: 1,
DashboardId: 1,
From: 1,
To: 15,
To: 15, //this will exclude the second test annotation
Tags: []string{"outage", "error"},
})
@@ -173,6 +197,19 @@ func TestAnnotations(t *testing.T) {
So(items, ShouldHaveLength, 1)
})
Convey("Should find two annotations using partial match", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
From: 1,
To: 25,
MatchAny: true,
Tags: []string{"rollback", "deploy"},
})
So(err, ShouldBeNil)
So(items, ShouldHaveLength, 2)
})
Convey("Should find one when all key value tag filters does match", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
+4 -4
View File
@@ -225,7 +225,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
var res []DashboardSearchProjection
sql, params := sb.ToSql()
err := x.Sql(sql, params...).Find(&res)
err := x.SQL(sql, params...).Find(&res)
if err != nil {
return nil, err
}
@@ -299,7 +299,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
ORDER BY term`
query.Result = make([]*m.DashboardTagCloudItem, 0)
sess := x.Sql(sql, query.OrgId)
sess := x.SQL(sql, query.OrgId)
err := sess.Find(&query.Result)
return err
}
@@ -413,7 +413,7 @@ func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery
params = append(params, query.UserId)
params = append(params, dialect.BooleanStr(false))
err := x.Sql(sql, params...).Find(&query.Result)
err := x.SQL(sql, params...).Find(&query.Result)
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
@@ -632,7 +632,7 @@ func HasEditPermissionInFolders(query *m.HasEditPermissionInFoldersQuery) error
}
resp := make([]*folderCount, 0)
if err := x.Sql(builder.GetSqlString(), builder.params...).Find(&resp); err != nil {
if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&resp); err != nil {
return err
}
@@ -105,7 +105,7 @@ func addAnnotationMig(mg *Migrator) {
}))
//
// Convert epoch saved as seconds to miliseconds
// Convert epoch saved as seconds to milliseconds
//
updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999"
mg.AddMigration("Convert existing annotations from seconds to milliseconds", NewRawSqlMigration(updateEpochSql))
@@ -51,4 +51,7 @@ func addTeamMigrations(mg *Migrator) {
Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190,
}))
mg.AddMigration("Add column external to team_member table", NewAddColumnMigration(teamMemberV1, &Column{
Name: "external", Type: DB_Bool, Nullable: true,
}))
}
+1 -1
View File
@@ -134,7 +134,7 @@ type TempUserDTO struct {
func (m *AddMissingUserSaltAndRandsMigration) Exec(sess *xorm.Session, mg *Migrator) error {
users := make([]*TempUserDTO, 0)
err := sess.Sql(fmt.Sprintf("SELECT id, login from %s WHERE rands = ''", mg.Dialect.Quote("user"))).Find(&users)
err := sess.SQL(fmt.Sprintf("SELECT id, login from %s WHERE rands = ''", mg.Dialect.Quote("user"))).Find(&users)
if err != nil {
return err
}
+2 -2
View File
@@ -133,7 +133,7 @@ func UpdateOrg(cmd *m.UpdateOrgCommand) error {
Updated: time.Now(),
}
affectedRows, err := sess.Id(cmd.OrgId).Update(&org)
affectedRows, err := sess.ID(cmd.OrgId).Update(&org)
if err != nil {
return err
@@ -166,7 +166,7 @@ func UpdateOrgAddress(cmd *m.UpdateOrgAddressCommand) error {
Updated: time.Now(),
}
if _, err := sess.Id(cmd.OrgId).Update(&org); err != nil {
if _, err := sess.ID(cmd.OrgId).Update(&org); err != nil {
return err
}
+3 -3
View File
@@ -21,7 +21,7 @@ func AddOrgUser(cmd *m.AddOrgUserCommand) error {
return inTransaction(func(sess *DBSession) error {
// check if user exists
var user m.User
if exists, err := sess.Id(cmd.UserId).Get(&user); err != nil {
if exists, err := sess.ID(cmd.UserId).Get(&user); err != nil {
return err
} else if !exists {
return m.ErrUserNotFound
@@ -85,7 +85,7 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
orgUser.Role = cmd.Role
orgUser.Updated = time.Now()
_, err = sess.Id(orgUser.Id).Update(&orgUser)
_, err = sess.ID(orgUser.Id).Update(&orgUser)
if err != nil {
return err
}
@@ -138,7 +138,7 @@ func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
return inTransaction(func(sess *DBSession) error {
// check if user exists
var user m.User
if exists, err := sess.Id(cmd.UserId).Get(&user); err != nil {
if exists, err := sess.ID(cmd.UserId).Get(&user); err != nil {
return err
} else if !exists {
return m.ErrUserNotFound
+2 -2
View File
@@ -26,7 +26,7 @@ func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
params = append(params, query.OrgId)
}
sess := x.Sql(sql, params...)
sess := x.SQL(sql, params...)
query.Result = make([]*m.PluginSettingInfoDTO, 0)
return sess.Find(&query.Result)
}
@@ -100,7 +100,7 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
pluginSetting.Pinned = cmd.Pinned
pluginSetting.PluginVersion = cmd.PluginVersion
_, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
_, err = sess.ID(pluginSetting.Id).Update(&pluginSetting)
return err
})
}
+1 -1
View File
@@ -94,7 +94,7 @@ func SavePreferences(cmd *m.SavePreferencesCommand) error {
prefs.Theme = cmd.Theme
prefs.Updated = time.Now()
prefs.Version += 1
_, err = sess.Id(prefs.Id).AllCols().Update(&prefs)
_, err = sess.ID(prefs.Id).AllCols().Update(&prefs)
return err
})
}
+7 -7
View File
@@ -38,7 +38,7 @@ func GetOrgQuotaByTarget(query *m.GetOrgQuotaByTargetQuery) error {
//get quota used.
rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target))
resp := make([]*targetCount, 0)
if err := x.Sql(rawSql, query.OrgId).Find(&resp); err != nil {
if err := x.SQL(rawSql, query.OrgId).Find(&resp); err != nil {
return err
}
@@ -81,7 +81,7 @@ func GetOrgQuotas(query *m.GetOrgQuotasQuery) error {
//get quota used.
rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target))
resp := make([]*targetCount, 0)
if err := x.Sql(rawSql, q.OrgId).Find(&resp); err != nil {
if err := x.SQL(rawSql, q.OrgId).Find(&resp); err != nil {
return err
}
result[i] = &m.OrgQuotaDTO{
@@ -116,7 +116,7 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
}
} else {
//update existing quota entry in the DB.
if _, err := sess.Id(quota.Id).Update(&quota); err != nil {
if _, err := sess.ID(quota.Id).Update(&quota); err != nil {
return err
}
}
@@ -140,7 +140,7 @@ func GetUserQuotaByTarget(query *m.GetUserQuotaByTargetQuery) error {
//get quota used.
rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target))
resp := make([]*targetCount, 0)
if err := x.Sql(rawSql, query.UserId).Find(&resp); err != nil {
if err := x.SQL(rawSql, query.UserId).Find(&resp); err != nil {
return err
}
@@ -183,7 +183,7 @@ func GetUserQuotas(query *m.GetUserQuotasQuery) error {
//get quota used.
rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target))
resp := make([]*targetCount, 0)
if err := x.Sql(rawSql, q.UserId).Find(&resp); err != nil {
if err := x.SQL(rawSql, q.UserId).Find(&resp); err != nil {
return err
}
result[i] = &m.UserQuotaDTO{
@@ -218,7 +218,7 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
}
} else {
//update existing quota entry in the DB.
if _, err := sess.Id(quota.Id).Update(&quota); err != nil {
if _, err := sess.ID(quota.Id).Update(&quota); err != nil {
return err
}
}
@@ -231,7 +231,7 @@ func GetGlobalQuotaByTarget(query *m.GetGlobalQuotaByTargetQuery) error {
//get quota used.
rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target))
resp := make([]*targetCount, 0)
if err := x.Sql(rawSql).Find(&resp); err != nil {
if err := x.SQL(rawSql).Find(&resp); err != nil {
return err
}
+14 -10
View File
@@ -74,7 +74,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
sess.MustCols("email")
affectedRows, err := sess.Id(cmd.Id).Update(&team)
affectedRows, err := sess.ID(cmd.Id).Update(&team)
if err != nil {
return err
@@ -169,7 +169,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
sql.WriteString(dialect.LimitOffset(int64(query.Limit), int64(offset)))
}
if err := x.Sql(sql.String(), params...).Find(&query.Result.Teams); err != nil {
if err := x.SQL(sql.String(), params...).Find(&query.Result.Teams); err != nil {
return err
}
@@ -196,7 +196,7 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
var team m.TeamDTO
exists, err := x.Sql(sql.String(), query.OrgId, query.Id).Get(&team)
exists, err := x.SQL(sql.String(), query.OrgId, query.Id).Get(&team)
if err != nil {
return err
@@ -220,7 +220,7 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
sql.WriteString(` INNER JOIN team_member on team.id = team_member.team_id`)
sql.WriteString(` WHERE team.org_id = ? and team_member.user_id = ?`)
err := x.Sql(sql.String(), query.OrgId, query.UserId).Find(&query.Result)
err := x.SQL(sql.String(), query.OrgId, query.UserId).Find(&query.Result)
return err
}
@@ -240,11 +240,12 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
}
entity := m.TeamMember{
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
UserId: cmd.UserId,
Created: time.Now(),
Updated: time.Now(),
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
UserId: cmd.UserId,
External: cmd.External,
Created: time.Now(),
Updated: time.Now(),
}
_, err := sess.Insert(&entity)
@@ -289,7 +290,10 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
if query.UserId != 0 {
sess.Where("team_member.user_id=?", query.UserId)
}
sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login")
if query.External {
sess.Where("team_member.external=?", dialect.BooleanStr(true))
}
sess.Cols("team_member.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login", "team_member.external")
sess.Asc("user.login", "user.email")
err := sess.Find(&query.Result)
+16
View File
@@ -50,13 +50,29 @@ func TestTeamCommandsAndQueries(t *testing.T) {
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[1], External: true})
So(err, ShouldBeNil)
q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
err = GetTeamMembers(q1)
So(err, ShouldBeNil)
So(q1.Result, ShouldHaveLength, 2)
So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
So(q1.Result[0].Login, ShouldEqual, "loginuser0")
So(q1.Result[0].OrgId, ShouldEqual, testOrgId)
So(q1.Result[1].TeamId, ShouldEqual, team1.Id)
So(q1.Result[1].Login, ShouldEqual, "loginuser1")
So(q1.Result[1].OrgId, ShouldEqual, testOrgId)
So(q1.Result[1].External, ShouldEqual, true)
q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
err = GetTeamMembers(q2)
So(err, ShouldBeNil)
So(q2.Result, ShouldHaveLength, 1)
So(q2.Result[0].TeamId, ShouldEqual, team1.Id)
So(q2.Result[0].Login, ShouldEqual, "loginuser1")
So(q2.Result[0].OrgId, ShouldEqual, testOrgId)
So(q2.Result[0].External, ShouldEqual, true)
})
Convey("Should be able to search for teams", func() {
+2 -2
View File
@@ -96,7 +96,7 @@ func GetTempUsersQuery(query *m.GetTempUsersQuery) error {
rawSql += " ORDER BY tu.created desc"
query.Result = make([]*m.TempUserDTO, 0)
sess := x.Sql(rawSql, params...)
sess := x.SQL(rawSql, params...)
err := sess.Find(&query.Result)
return err
}
@@ -121,7 +121,7 @@ func GetTempUserByCode(query *m.GetTempUserByCodeQuery) error {
WHERE tu.code=?`
var tempUser m.TempUserDTO
sess := x.Sql(rawSql, query.Code)
sess := x.SQL(rawSql, query.Code)
has, err := sess.Get(&tempUser)
if err != nil {
+1 -1
View File
@@ -39,7 +39,7 @@ func TestTransaction(t *testing.T) {
So(err, ShouldEqual, models.ErrInvalidApiKey)
})
Convey("wont update if one handler fails", func() {
Convey("won't update if one handler fails", func() {
err := ss.InTransaction(context.Background(), func(ctx context.Context) error {
err := DeleteApiKeyCtx(ctx, deleteApiKeyCmd)
if err != nil {
+10 -13
View File
@@ -240,7 +240,7 @@ func UpdateUser(cmd *m.UpdateUserCommand) error {
Updated: time.Now(),
}
if _, err := sess.Id(cmd.UserId).Update(&user); err != nil {
if _, err := sess.ID(cmd.UserId).Update(&user); err != nil {
return err
}
@@ -264,22 +264,19 @@ func ChangeUserPassword(cmd *m.ChangeUserPasswordCommand) error {
Updated: time.Now(),
}
_, err := sess.Id(cmd.UserId).Update(&user)
_, err := sess.ID(cmd.UserId).Update(&user)
return err
})
}
func UpdateUserLastSeenAt(cmd *m.UpdateUserLastSeenAtCommand) error {
return inTransaction(func(sess *DBSession) error {
if cmd.UserId <= 0 {
}
user := m.User{
Id: cmd.UserId,
LastSeenAt: time.Now(),
}
_, err := sess.Id(cmd.UserId).Update(&user)
_, err := sess.ID(cmd.UserId).Update(&user)
return err
})
}
@@ -310,7 +307,7 @@ func setUsingOrgInTransaction(sess *DBSession, userID int64, orgID int64) error
OrgId: orgID,
}
_, err := sess.Id(userID).Update(&user)
_, err := sess.ID(userID).Update(&user)
return err
}
@@ -372,11 +369,11 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
sess := x.Table("user")
if query.UserId > 0 {
sess.Sql(rawSql+"WHERE u.id=?", query.UserId)
sess.SQL(rawSql+"WHERE u.id=?", query.UserId)
} else if query.Login != "" {
sess.Sql(rawSql+"WHERE u.login=?", query.Login)
sess.SQL(rawSql+"WHERE u.login=?", query.Login)
} else if query.Email != "" {
sess.Sql(rawSql+"WHERE u.email=?", query.Email)
sess.SQL(rawSql+"WHERE u.email=?", query.Email)
}
var user m.SignedInUser
@@ -472,11 +469,11 @@ func DeleteUser(cmd *m.DeleteUserCommand) error {
func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
return inTransaction(func(sess *DBSession) error {
user := m.User{}
sess.Id(cmd.UserId).Get(&user)
sess.ID(cmd.UserId).Get(&user)
user.IsAdmin = cmd.IsGrafanaAdmin
sess.UseBool("is_admin")
_, err := sess.Id(user.Id).Update(&user)
_, err := sess.ID(user.Id).Update(&user)
return err
})
}
@@ -490,7 +487,7 @@ func SetUserHelpFlag(cmd *m.SetUserHelpFlagCommand) error {
Updated: time.Now(),
}
_, err := sess.Id(cmd.UserId).Cols("help_flags1").Update(&user)
_, err := sess.ID(cmd.UserId).Cols("help_flags1").Update(&user)
return err
})
}
+12 -4
View File
@@ -166,6 +166,7 @@ var (
// Alerting
AlertingEnabled bool
ExecuteAlerts bool
AlertingRenderLimit int
AlertingErrorOrTimeout string
AlertingNoDataOrNullValues string
@@ -196,13 +197,18 @@ type Cfg struct {
Smtp SmtpSettings
// Rendering
ImagesDir string
PhantomDir string
RendererUrl string
RendererCallbackUrl string
ImagesDir string
PhantomDir string
RendererUrl string
RendererCallbackUrl string
RendererLimit int
RendererLimitAlerting int
DisableBruteForceLoginProtection bool
TempDataLifetime time.Duration
MetricsEndpointEnabled bool
}
type CommandLineArgs struct {
@@ -659,6 +665,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
cfg.ImagesDir = filepath.Join(DataPath, "png")
cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
analytics := iniFile.Section("analytics")
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
@@ -674,6 +681,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
alerting := iniFile.Section("alerting")
AlertingEnabled = alerting.Key("enabled").MustBool(true)
ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true)
AlertingRenderLimit = alerting.Key("concurrent_render_limit").MustInt(5)
AlertingErrorOrTimeout = alerting.Key("error_or_timeout").MustString("alerting")
AlertingNoDataOrNullValues = alerting.Key("nodata_or_nullvalues").MustString("no_data")
+5 -5
View File
@@ -97,7 +97,7 @@ func TestLoadingSettings(t *testing.T) {
Args: []string{
"cfg:default.server.domain=test2",
},
Config: filepath.Join(HomePath, "tests/config-files/override.ini"),
Config: filepath.Join(HomePath, "pkg/setting/testdata/override.ini"),
})
So(Domain, ShouldEqual, "test2")
@@ -108,7 +108,7 @@ func TestLoadingSettings(t *testing.T) {
cfg := NewCfg()
cfg.Load(&CommandLineArgs{
HomePath: "../../",
Config: filepath.Join(HomePath, "tests/config-files/override_windows.ini"),
Config: filepath.Join(HomePath, "pkg/setting/testdata/override_windows.ini"),
Args: []string{`cfg:default.paths.data=c:\tmp\data`},
})
@@ -117,7 +117,7 @@ func TestLoadingSettings(t *testing.T) {
cfg := NewCfg()
cfg.Load(&CommandLineArgs{
HomePath: "../../",
Config: filepath.Join(HomePath, "tests/config-files/override.ini"),
Config: filepath.Join(HomePath, "pkg/setting/testdata/override.ini"),
Args: []string{"cfg:default.paths.data=/tmp/data"},
})
@@ -130,7 +130,7 @@ func TestLoadingSettings(t *testing.T) {
cfg := NewCfg()
cfg.Load(&CommandLineArgs{
HomePath: "../../",
Config: filepath.Join(HomePath, "tests/config-files/override_windows.ini"),
Config: filepath.Join(HomePath, "pkg/setting/testdata/override_windows.ini"),
Args: []string{`cfg:paths.data=c:\tmp\data`},
})
@@ -139,7 +139,7 @@ func TestLoadingSettings(t *testing.T) {
cfg := NewCfg()
cfg.Load(&CommandLineArgs{
HomePath: "../../",
Config: filepath.Join(HomePath, "tests/config-files/override.ini"),
Config: filepath.Join(HomePath, "pkg/setting/testdata/override.ini"),
Args: []string{"cfg:paths.data=/tmp/data"},
})
+3
View File
@@ -0,0 +1,3 @@
[paths]
data = /tmp/override
+3
View File
@@ -0,0 +1,3 @@
[paths]
data = c:\tmp\override
+31 -5
View File
@@ -46,17 +46,20 @@ func (e *Error) Error() string {
return e.s
}
const (
grafanaCom = "grafana_com"
)
var (
SocialBaseUrl = "/login/"
SocialMap = make(map[string]SocialConnector)
allOauthes = []string{"github", "gitlab", "google", "generic_oauth", "grafananet", grafanaCom}
)
func NewOAuthService() {
setting.OAuthService = &setting.OAuther{}
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
allOauthes := []string{"github", "gitlab", "google", "generic_oauth", "grafananet", "grafana_com"}
for _, name := range allOauthes {
sec := setting.Raw.Section("auth." + name)
info := &setting.OAuthInfo{
@@ -83,7 +86,7 @@ func NewOAuthService() {
}
if name == "grafananet" {
name = "grafana_com"
name = grafanaCom
}
setting.OAuthService.OAuthInfos[name] = info
@@ -160,7 +163,7 @@ func NewOAuthService() {
}
}
if name == "grafana_com" {
if name == grafanaCom {
config = oauth2.Config{
ClientID: info.ClientId,
ClientSecret: info.ClientSecret,
@@ -172,7 +175,7 @@ func NewOAuthService() {
Scopes: info.Scopes,
}
SocialMap["grafana_com"] = &SocialGrafanaCom{
SocialMap[grafanaCom] = &SocialGrafanaCom{
SocialBase: &SocialBase{
Config: &config,
log: logger,
@@ -184,3 +187,26 @@ func NewOAuthService() {
}
}
}
// GetOAuthProviders returns available oauth providers and if they're enabled or not
var GetOAuthProviders = func(cfg *setting.Cfg) map[string]bool {
result := map[string]bool{}
if cfg == nil || cfg.Raw == nil {
return result
}
for _, name := range allOauthes {
if name == "grafananet" {
name = grafanaCom
}
sec := cfg.Raw.Section("auth." + name)
if sec == nil {
continue
}
result[name] = sec.Key("enabled").MustBool()
}
return result
}
+3 -2
View File
@@ -58,7 +58,8 @@ func (ts *TracingService) parseSettings() {
func (ts *TracingService) initGlobalTracer() error {
cfg := jaegercfg.Configuration{
Disabled: !ts.enabled,
ServiceName: "grafana",
Disabled: !ts.enabled,
Sampler: &jaegercfg.SamplerConfig{
Type: ts.samplerType,
Param: ts.samplerParam,
@@ -78,7 +79,7 @@ func (ts *TracingService) initGlobalTracer() error {
options = append(options, jaegercfg.Tag(tag, value))
}
tracer, closer, err := cfg.New("grafana", options...)
tracer, closer, err := cfg.NewTracer(options...)
if err != nil {
return err
}
+2 -2
View File
@@ -196,7 +196,7 @@ func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatch
params.ExtendedStatistics = query.ExtendedStatistics
}
// 1 minutes resolutin metrics is stored for 15 days, 15 * 24 * 60 = 21600
// 1 minutes resolution metrics is stored for 15 days, 15 * 24 * 60 = 21600
if query.HighResolution && (((endTime.Unix() - startTime.Unix()) / int64(query.Period)) > 21600) {
return nil, errors.New("too long query period")
}
@@ -267,7 +267,7 @@ func (e *CloudWatchExecutor) executeGetMetricDataQuery(ctx context.Context, regi
ScanBy: aws.String("TimestampAscending"),
}
for _, query := range queries {
// 1 minutes resolutin metrics is stored for 15 days, 15 * 24 * 60 = 21600
// 1 minutes resolution metrics is stored for 15 days, 15 * 24 * 60 = 21600
if query.HighResolution && (((endTime.Unix() - startTime.Unix()) / int64(query.Period)) > 21600) {
return nil, errors.New("too long query period")
}
+4 -4
View File
@@ -138,13 +138,13 @@ func (c *baseClientImpl) encodeBatchRequests(requests []*multiRequest) ([]byte,
}
body := string(reqBody)
body = strings.Replace(body, "$__interval_ms", strconv.FormatInt(r.interval.Value.Nanoseconds()/int64(time.Millisecond), 10), -1)
body = strings.Replace(body, "$__interval_ms", strconv.FormatInt(r.interval.Milliseconds(), 10), -1)
body = strings.Replace(body, "$__interval", r.interval.Text, -1)
payload.WriteString(body + "\n")
}
elapsed := time.Now().Sub(start)
elapsed := time.Since(start)
clientLog.Debug("Encoded batch requests to json", "took", elapsed)
return payload.Bytes(), nil
@@ -187,7 +187,7 @@ func (c *baseClientImpl) executeRequest(method, uriPath string, body []byte) (*h
start := time.Now()
defer func() {
elapsed := time.Now().Sub(start)
elapsed := time.Since(start)
clientLog.Debug("Executed request", "took", elapsed)
}()
return ctxhttp.Do(c.ctx, httpClient, req)
@@ -215,7 +215,7 @@ func (c *baseClientImpl) ExecuteMultisearch(r *MultiSearchRequest) (*MultiSearch
return nil, err
}
elapsed := time.Now().Sub(start)
elapsed := time.Since(start)
clientLog.Debug("Decoded multisearch json response", "took", elapsed)
msr.Status = res.StatusCode
+1 -1
View File
@@ -40,7 +40,7 @@ func TestClient(t *testing.T) {
So(err, ShouldNotBeNil)
})
Convey("When unspported version set should return error", func() {
Convey("When unsupported version set should return error", func() {
ds := &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
"esVersion": 6,
@@ -56,9 +56,7 @@ func (b *SearchRequestBuilder) Build() (*SearchRequest, error) {
if err != nil {
return nil, err
}
for _, agg := range aggArray {
sr.Aggs = append(sr.Aggs, agg)
}
sr.Aggs = append(sr.Aggs, aggArray...)
}
}
@@ -112,7 +110,7 @@ func (b *SearchRequestBuilder) Query() *QueryBuilder {
return b.queryBuilder
}
// Agg initaite and returns a new aggregation builder
// Agg initiate and returns a new aggregation builder
func (b *SearchRequestBuilder) Agg() AggBuilder {
aggBuilder := newAggBuilder()
b.aggBuilders = append(b.aggBuilders, aggBuilder)
@@ -300,9 +298,7 @@ func (b *aggBuilderImpl) Build() (AggArray, error) {
return nil, err
}
for _, childAgg := range childAggs {
agg.Aggregation.Aggs = append(agg.Aggregation.Aggs, childAgg)
}
agg.Aggregation.Aggs = append(agg.Aggregation.Aggs, childAggs...)
}
aggs = append(aggs, agg)
+23 -12
View File
@@ -13,6 +13,19 @@ import (
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
)
const (
// Metric types
countType = "count"
percentilesType = "percentiles"
extendedStatsType = "extended_stats"
// Bucket types
dateHistType = "date_histogram"
histogramType = "histogram"
filtersType = "filters"
termsType = "terms"
geohashGridType = "geohash_grid"
)
type responseParser struct {
Responses []*es.SearchResponse
Targets []*Query
@@ -81,7 +94,7 @@ func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Qu
}
if depth == maxDepth {
if aggDef.Type == "date_histogram" {
if aggDef.Type == dateHistType {
err = rp.processMetrics(esAgg, target, series, props)
} else {
err = rp.processAggregationDocs(esAgg, aggDef, target, table, props)
@@ -92,7 +105,7 @@ func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Qu
} else {
for _, b := range esAgg.Get("buckets").MustArray() {
bucket := simplejson.NewFromAny(b)
newProps := make(map[string]string, 0)
newProps := make(map[string]string)
for k, v := range props {
newProps[k] = v
@@ -122,7 +135,7 @@ func (rp *responseParser) processBuckets(aggs map[string]interface{}, target *Qu
for _, bucketKey := range bucketKeys {
bucket := simplejson.NewFromAny(buckets[bucketKey])
newProps := make(map[string]string, 0)
newProps := make(map[string]string)
for k, v := range props {
newProps[k] = v
@@ -149,7 +162,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
}
switch metric.Type {
case "count":
case countType:
newSeries := tsdb.TimeSeries{
Tags: make(map[string]string),
}
@@ -164,10 +177,10 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
for k, v := range props {
newSeries.Tags[k] = v
}
newSeries.Tags["metric"] = "count"
newSeries.Tags["metric"] = countType
*series = append(*series, &newSeries)
case "percentiles":
case percentilesType:
buckets := esAgg.Get("buckets").MustArray()
if len(buckets) == 0 {
break
@@ -198,7 +211,7 @@ func (rp *responseParser) processMetrics(esAgg *simplejson.Json, target *Query,
}
*series = append(*series, &newSeries)
}
case "extended_stats":
case extendedStatsType:
buckets := esAgg.Get("buckets").MustArray()
metaKeys := make([]string, 0)
@@ -312,10 +325,9 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
for _, metric := range target.Metrics {
switch metric.Type {
case "count":
case countType:
addMetricValue(&values, rp.getMetricName(metric.Type), castToNullFloat(bucket.Get("doc_count")))
break
case "extended_stats":
case extendedStatsType:
metaKeys := make([]string, 0)
meta := metric.Meta.MustMap()
for k := range meta {
@@ -355,7 +367,6 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
}
addMetricValue(&values, metricName, castToNullFloat(bucket.GetPath(metric.ID, "value")))
break
}
}
@@ -368,7 +379,7 @@ func (rp *responseParser) processAggregationDocs(esAgg *simplejson.Json, aggDef
func (rp *responseParser) trimDatapoints(series *tsdb.TimeSeriesSlice, target *Query) {
var histogram *BucketAgg
for _, bucketAgg := range target.BucketAggs {
if bucketAgg.Type == "date_histogram" {
if bucketAgg.Type == dateHistType {
histogram = bucketAgg
break
}
+5 -5
View File
@@ -75,15 +75,15 @@ func (e *timeSeriesQuery) execute() (*tsdb.Response, error) {
// iterate backwards to create aggregations bottom-down
for _, bucketAgg := range q.BucketAggs {
switch bucketAgg.Type {
case "date_histogram":
case dateHistType:
aggBuilder = addDateHistogramAgg(aggBuilder, bucketAgg, from, to)
case "histogram":
case histogramType:
aggBuilder = addHistogramAgg(aggBuilder, bucketAgg)
case "filters":
case filtersType:
aggBuilder = addFiltersAgg(aggBuilder, bucketAgg)
case "terms":
case termsType:
aggBuilder = addTermsAgg(aggBuilder, bucketAgg, q.Metrics)
case "geohash_grid":
case geohashGridType:
aggBuilder = addGeoHashGridAgg(aggBuilder, bucketAgg)
}
}
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"strconv"
"strings"
"time"
"regexp"
@@ -34,7 +33,7 @@ func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) {
res = strings.Replace(res, "$timeFilter", query.renderTimeFilter(queryContext), -1)
res = strings.Replace(res, "$interval", interval.Text, -1)
res = strings.Replace(res, "$__interval_ms", strconv.FormatInt(interval.Value.Nanoseconds()/int64(time.Millisecond), 10), -1)
res = strings.Replace(res, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10), -1)
res = strings.Replace(res, "$__interval", interval.Text, -1)
return res, nil
}
+1 -1
View File
@@ -158,7 +158,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" < 10001`)
})
Convey("can render number greather then condition tags", func() {
Convey("can render number greater then condition tags", func() {
query := &Query{Tags: []*Tag{{Operator: ">", Value: "10001", Key: "key"}}}
So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" > 10001`)
+4
View File
@@ -49,6 +49,10 @@ func NewIntervalCalculator(opt *IntervalOptions) *intervalCalculator {
return calc
}
func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond)
}
func (ic *intervalCalculator) Calculate(timerange *TimeRange, minInterval time.Duration) Interval {
to := timerange.MustGetTo().UnixNano()
from := timerange.MustGetFrom().UnixNano()
+3 -19
View File
@@ -13,12 +13,13 @@ const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type msSqlMacroEngine struct {
*tsdb.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
}
func newMssqlMacroEngine() tsdb.SqlMacroEngine {
return &msSqlMacroEngine{}
return &msSqlMacroEngine{SqlMacroEngineBase: tsdb.NewSqlMacroEngineBase()}
}
func (m *msSqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
@@ -27,7 +28,7 @@ func (m *msSqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
rExp, _ := regexp.Compile(sExpr)
var macroError error
sql = replaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
sql = m.ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
args := strings.Split(groups[2], ",")
for i, arg := range args {
args[i] = strings.Trim(arg, " ")
@@ -47,23 +48,6 @@ func (m *msSqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
return sql, nil
}
func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
groups := []string{}
for i := 0; i < len(v); i += 2 {
groups = append(groups, str[v[i]:v[i+1]])
}
result += str[lastIndex:v[0]] + repl(groups)
lastIndex = v[1]
}
return result + str[lastIndex:]
}
func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
case "__time":
+40
View File
@@ -35,6 +35,11 @@ func TestMSSQL(t *testing.T) {
return x, nil
}
origInterpolate := tsdb.Interpolate
tsdb.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
return sql, nil
}
endpoint, err := newMssqlQueryEndpoint(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
@@ -47,6 +52,7 @@ func TestMSSQL(t *testing.T) {
Reset(func() {
sess.Close()
tsdb.NewXormEngine = origXormEngine
tsdb.Interpolate = origInterpolate
})
Convey("Given a table with different native data types", func() {
@@ -295,6 +301,40 @@ func TestMSSQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup and $__interval", func() {
mockInterpolate := tsdb.Interpolate
tsdb.Interpolate = origInterpolate
Reset(func() {
tsdb.Interpolate = mockInterpolate
})
Convey("Should replace $__interval", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, $__interval) ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
},
},
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(nil, nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 ORDER BY 1")
})
})
Convey("When doing a metric query using timeGroup with float fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
+3 -20
View File
@@ -9,17 +9,17 @@ import (
"github.com/grafana/grafana/pkg/tsdb"
)
//const rsString = `(?:"([^"]*)")`;
const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type mySqlMacroEngine struct {
*tsdb.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
}
func newMysqlMacroEngine() tsdb.SqlMacroEngine {
return &mySqlMacroEngine{}
return &mySqlMacroEngine{SqlMacroEngineBase: tsdb.NewSqlMacroEngineBase()}
}
func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
@@ -28,7 +28,7 @@ func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
rExp, _ := regexp.Compile(sExpr)
var macroError error
sql = replaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
sql = m.ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
args := strings.Split(groups[2], ",")
for i, arg := range args {
args[i] = strings.Trim(arg, " ")
@@ -48,23 +48,6 @@ func (m *mySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRa
return sql, nil
}
func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
groups := []string{}
for i := 0; i < len(v); i += 2 {
groups = append(groups, str[v[i]:v[i+1]])
}
result += str[lastIndex:v[0]] + repl(groups)
lastIndex = v[1]
}
return result + str[lastIndex:]
}
func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
case "__timeEpoch", "__time":
+40
View File
@@ -42,6 +42,11 @@ func TestMySQL(t *testing.T) {
return x, nil
}
origInterpolate := tsdb.Interpolate
tsdb.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
return sql, nil
}
endpoint, err := newMysqlQueryEndpoint(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
@@ -54,6 +59,7 @@ func TestMySQL(t *testing.T) {
Reset(func() {
sess.Close()
tsdb.NewXormEngine = origXormEngine
tsdb.Interpolate = origInterpolate
})
Convey("Given a table with different native data types", func() {
@@ -295,6 +301,40 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup and $__interval", func() {
mockInterpolate := tsdb.Interpolate
tsdb.Interpolate = origInterpolate
Reset(func() {
tsdb.Interpolate = mockInterpolate
})
Convey("Should replace $__interval", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
},
},
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(nil, nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT UNIX_TIMESTAMP(time) DIV 60 * 60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
})
})
Convey("When doing a metric query using timeGroup with value fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
+6 -20
View File
@@ -9,18 +9,21 @@ import (
"github.com/grafana/grafana/pkg/tsdb"
)
//const rsString = `(?:"([^"]*)")`;
const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type postgresMacroEngine struct {
*tsdb.SqlMacroEngineBase
timeRange *tsdb.TimeRange
query *tsdb.Query
timescaledb bool
}
func newPostgresMacroEngine(timescaledb bool) tsdb.SqlMacroEngine {
return &postgresMacroEngine{timescaledb: timescaledb}
return &postgresMacroEngine{
SqlMacroEngineBase: tsdb.NewSqlMacroEngineBase(),
timescaledb: timescaledb,
}
}
func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
@@ -29,7 +32,7 @@ func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.Tim
rExp, _ := regexp.Compile(sExpr)
var macroError error
sql = replaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
sql = m.ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
// detect if $__timeGroup is supposed to add AS time for pre 5.3 compatibility
// if there is a ',' directly after the macro call $__timeGroup is probably used
@@ -66,23 +69,6 @@ func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.Tim
return sql, nil
}
func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
groups := []string{}
for i := 0; i < len(v); i += 2 {
groups = append(groups, str[v[i]:v[i+1]])
}
result += str[lastIndex:v[0]] + repl(groups)
lastIndex = v[1]
}
return result + str[lastIndex:]
}
func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
case "__time":
+40
View File
@@ -43,6 +43,11 @@ func TestPostgres(t *testing.T) {
return x, nil
}
origInterpolate := tsdb.Interpolate
tsdb.Interpolate = func(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
return sql, nil
}
endpoint, err := newPostgresQueryEndpoint(&models.DataSource{
JsonData: simplejson.New(),
SecureJsonData: securejsondata.SecureJsonData{},
@@ -55,6 +60,7 @@ func TestPostgres(t *testing.T) {
Reset(func() {
sess.Close()
tsdb.NewXormEngine = origXormEngine
tsdb.Interpolate = origInterpolate
})
Convey("Given a table with different native data types", func() {
@@ -222,6 +228,40 @@ func TestPostgres(t *testing.T) {
}
})
Convey("When doing a metric query using timeGroup and $__interval", func() {
mockInterpolate := tsdb.Interpolate
tsdb.Interpolate = origInterpolate
Reset(func() {
tsdb.Interpolate = mockInterpolate
})
Convey("Should replace $__interval", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
DataSource: &models.DataSource{},
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
},
},
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(30*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(nil, nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT floor(extract(epoch from time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
})
})
Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
+2 -2
View File
@@ -92,12 +92,12 @@ func (e *PrometheusExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
return nil, err
}
querys, err := parseQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
queries, err := parseQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
if err != nil {
return nil, err
}
for _, query := range querys {
for _, query := range queries {
timeRange := apiv1.Range{
Start: query.Start,
End: query.End,
+49 -1
View File
@@ -6,6 +6,7 @@ import (
"database/sql"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"sync"
@@ -43,6 +44,8 @@ var engineCache = engineCacheType{
versions: make(map[int64]int),
}
var sqlIntervalCalculator = NewIntervalCalculator(nil)
var NewXormEngine = func(driverName string, connectionString string) (*xorm.Engine, error) {
return xorm.NewEngine(driverName, connectionString)
}
@@ -126,7 +129,15 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
queryResult := &QueryResult{Meta: simplejson.New(), RefId: query.RefId}
result.Results[query.RefId] = queryResult
rawSQL, err := e.macroEngine.Interpolate(query, tsdbQuery.TimeRange, rawSQL)
// global substitutions
rawSQL, err := Interpolate(query, tsdbQuery.TimeRange, rawSQL)
if err != nil {
queryResult.Error = err
continue
}
// datasource specific substitutions
rawSQL, err = e.macroEngine.Interpolate(query, tsdbQuery.TimeRange, rawSQL)
if err != nil {
queryResult.Error = err
continue
@@ -163,6 +174,20 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
return result, nil
}
// global macros/substitutions for all sql datasources
var Interpolate = func(query *Query, timeRange *TimeRange, sql string) (string, error) {
minInterval, err := GetIntervalFrom(query.DataSource, query.Model, time.Second*60)
if err != nil {
return sql, nil
}
interval := sqlIntervalCalculator.Calculate(timeRange, minInterval)
sql = strings.Replace(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10), -1)
sql = strings.Replace(sql, "$__interval", interval.Text, -1)
return sql, nil
}
func (e *sqlQueryEndpoint) transformToTable(query *Query, rows *core.Rows, result *QueryResult, tsdbQuery *TsdbQuery) error {
columnNames, err := rows.Columns()
columnCount := len(columnNames)
@@ -589,3 +614,26 @@ func SetupFillmode(query *Query, interval time.Duration, fillmode string) error
return nil
}
type SqlMacroEngineBase struct{}
func NewSqlMacroEngineBase() *SqlMacroEngineBase {
return &SqlMacroEngineBase{}
}
func (m *SqlMacroEngineBase) ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
result := ""
lastIndex := 0
for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
groups := []string{}
for i := 0; i < len(v); i += 2 {
groups = append(groups, str[v[i]:v[i+1]])
}
result += str[lastIndex:v[0]] + repl(groups)
lastIndex = v[1]
}
return result + str[lastIndex:]
}
+31
View File
@@ -5,6 +5,8 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
@@ -14,6 +16,35 @@ func TestSqlEngine(t *testing.T) {
dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC)
earlyDt := time.Date(1970, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC)
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := NewFakeTimeRange("5m", "now", to)
query := &Query{DataSource: &models.DataSource{}, Model: simplejson.New()}
Convey("interpolate $__interval", func() {
sql, err := Interpolate(query, timeRange, "select $__interval ")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select 1m ")
})
Convey("interpolate $__interval in $__timeGroup", func() {
sql, err := Interpolate(query, timeRange, "select $__timeGroupAlias(time,$__interval)")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select $__timeGroupAlias(time,1m)")
})
Convey("interpolate $__interval_ms", func() {
sql, err := Interpolate(query, timeRange, "select $__interval_ms ")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "select 60000 ")
})
})
Convey("Given row values with time.Time as time columns", func() {
var nilPointer *time.Time
+2 -2
View File
@@ -3,14 +3,14 @@ package util
import "testing"
func TestMd5Sum(t *testing.T) {
input := "dont hash passwords with md5"
input := "don't hash passwords with md5"
have, err := Md5SumString(input)
if err != nil {
t.Fatal("expected err to be nil")
}
want := "2d6a56c82d09d374643b926d3417afba"
want := "dd1f7fdb3466c0d09c2e839d1f1530f8"
if have != want {
t.Fatalf("expected: %s got: %s", want, have)
}