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:
@@ -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
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ type GetLatestNotificationQuery struct {
|
||||
AlertId int64
|
||||
NotifierId int64
|
||||
|
||||
Result *AlertNotificationJournal
|
||||
Result []AlertNotificationJournal
|
||||
}
|
||||
|
||||
type CleanNotificationJournalCommand struct {
|
||||
|
||||
+22
-17
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "Nginx Memory",
|
||||
"revision": 2,
|
||||
"schemaVersion": 11
|
||||
}
|
||||
+55
@@ -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"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
handler.notifier.SendIfNeeded(evalContext)
|
||||
|
||||
handler.notifier.SendIfNeeded(evalContext)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ providers:
|
||||
folder: 'developers'
|
||||
editable: true
|
||||
disableDeletion: true
|
||||
updateIntervalSeconds: 10
|
||||
updateIntervalSeconds: 15
|
||||
type: file
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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("a); err != nil {
|
||||
if _, err := sess.ID(quota.Id).Update("a); 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("a); err != nil {
|
||||
if _, err := sess.ID(quota.Id).Update("a); 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
|
||||
@@ -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"},
|
||||
})
|
||||
|
||||
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
[paths]
|
||||
data = /tmp/override
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
[paths]
|
||||
data = c:\tmp\override
|
||||
|
||||
+31
-5
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
@@ -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:]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user