diff --git a/CHANGELOG.md b/CHANGELOG.md index acec4e8cdf9..eef674b4bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,19 @@ ## Enhancements * **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk) * **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller) +* **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo) -# 4.3.2 (upcoming patch release) +# 4.3.2 (2017-05-31) ## Bug fixes * **InfluxDB**: Fixed issue with query editor not showing ALIAS BY input field when in text editor mode [#8459](https://github.com/grafana/grafana/issues/8459) * **Graph Log Scale**: Fixed issue with log scale going below x-axis [#8244](https://github.com/grafana/grafana/issues/8244) * **Playlist**: Fixed dashboard play order issue [#7688](https://github.com/grafana/grafana/issues/7688) +* **Elasticsearch**: Fixed table query issue with ES 2.x [#8467](https://github.com/grafana/grafana/issues/8467), thx [@goldeelox](https://github.com/goldeelox) + +## Changes +* **Lazy Loading Of Panels**: Panels are no longer loaded as they are scrolled into view, this was reverted due to Chrome bug, might be reintroduced when Chrome fixes it's JS blocking behavior on scroll. [#8500](https://github.com/grafana/grafana/issues/8500) # 4.3.1 (2017-05-23) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index a2688de3100..c4a3a012c46 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -52,12 +52,22 @@ Here you can specify the name of the alert rule and how often the scheduler shou ### Conditions Currently the only condition type that exists is a `Query` condition that allows you to -specify a query letter, time range and an aggregation function. The letter refers to -a query you already have added in the **Metrics** tab. The result from the query and the aggregation function is -a single value that is then used in the threshold check. The query used in an alert rule cannot -contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. +specify a query letter, time range and an aggregation function. + + +### Query condition example + +```sql +avg() OF query(A, 5m, now) IS BELOW 14 +``` + +- `avg()` Controls how the values for **each** serie should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function. +- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters defines the time range, `5m, now` means 5 minutes from now to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes from now to 2 minutes from now. This is useful if you want to ignore the last 2 minutes of data. +- `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold. + +The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. For example, we have 3 conditions in the following order: -`condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)` +*condition:A(evaluates to: TRUE) OR condition:B(evaluates to: FALSE) AND condition:C(evaluates to: TRUE)* so the result will be calculated as ((TRUE OR FALSE) AND TRUE) = TRUE. We plan to add other condition types in the future, like `Other Alert`, where you can include the state diff --git a/docs/sources/features/datasources/elasticsearch.md b/docs/sources/features/datasources/elasticsearch.md index 6462d026366..25cdb98c8c5 100644 --- a/docs/sources/features/datasources/elasticsearch.md +++ b/docs/sources/features/datasources/elasticsearch.md @@ -92,9 +92,10 @@ The Elasticsearch data source supports two types of queries you can use in the * Query | Description ------------ | ------------- *{"find": "fields", "type": "keyword"} | Returns a list of field names with the index type `keyword`. -*{"find": "terms", "field": "@hostname"}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query. +*{"find": "terms", "field": "@hostname", "size": 1000}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query. *{"find": "terms", "field": "@hostname", "query": ''}* | Returns a list of values for a field using term aggregation & and a specified lucene query filter. Query will use current dashboard time range as time range for query. +There is a default size limit of 500 on terms queries. Set the size property in your query to set a custom limit. You can use other variables inside the query. Example query definition for a variable named `$host`. ``` diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 595a10e5081..3bbecfcef4d 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -65,7 +65,7 @@ Each field in the dashboard JSON is explained below with its usage: | **timezone** | timezone of dashboard, i.e. `utc` or `browser` | | **editable** | whether a dashboard is editable or not | | **hideControls** | whether row controls on the left in green are hidden or not | -| **graphTooltip** | TODO | +| **graphTooltip** | 0 for no shared crosshair or tooltip (default), 1 for shared crosshair, 2 for shared crosshair AND shared tooltip | | **rows** | row metadata, see [rows section](#rows) for details | | **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc | | **timepicker** | timepicker metadata, see [timepicker section](#timepicker) for details | diff --git a/pkg/api/api.go b/pkg/api/api.go index 46a636cb8eb..b7ed096c649 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -261,6 +261,7 @@ func (hs *HttpServer) registerRoutes() { r.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics)) r.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios)) r.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData)) + r.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk)) // metrics r.Get("/metrics", wrap(GetInternalMetrics)) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index f32cf8ca23a..3ad63ceec75 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -90,6 +90,7 @@ func init() { "CacheHitPercent", "CachePercentUsed", "CachePercentDirty", "ReadBytes", "ReadTime", "WriteBytes", "WriteTime", "QueuedWrites"}, "AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut", "ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"}, + "AWS/VPN": {"TunnelState", "TunnelDataIn", "TunnelDataOut"}, "AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"}, "AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"}, "KMS": {"SecondsUntilKeyMaterialExpiration"}, @@ -131,6 +132,7 @@ func init() { "AWS/SQS": {"QueueName"}, "AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"}, "AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"}, + "AWS/VPN": {"VpnId", "TunnelIpAddress"}, "AWS/WAF": {"Rule", "WebACL"}, "AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"}, "KMS": {"KeyId"}, diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 5fbd9fea709..0d9ab83282c 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -28,6 +28,7 @@ var ( ErrEmailNotAllowed = errors.New("Required email domain not fulfilled") ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter") ErrUsersQuotaReached = errors.New("Users quota reached") + ErrNoEmail = errors.New("Login provider didn't return an email address") ) func GenStateString() string { @@ -63,7 +64,7 @@ func OAuthLogin(ctx *middleware.Context) { if setting.OAuthService.OAuthInfos[name].HostedDomain == "" { ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline)) } else { - ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline)) + ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline)) } return } @@ -134,6 +135,12 @@ func OAuthLogin(ctx *middleware.Context) { ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo) + // validate that we got at least an email address + if userInfo.Email == "" { + redirectWithError(ctx, ErrNoEmail) + return + } + // validate that the email is allowed to login to grafana if !connect.IsEmailAllowed(userInfo.Email) { redirectWithError(ctx, ErrEmailNotAllowed) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index abd6527431a..e35e35cdab6 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" @@ -144,3 +145,29 @@ func GenerateSqlTestData(c *middleware.Context) Response { return Json(200, &util.DynMap{"message": "OK"}) } + +// GET /api/tsdb/testdata/random-walk +func GetTestDataRandomWalk(c *middleware.Context) Response { + from := c.Query("from") + to := c.Query("to") + intervalMs := c.QueryInt64("intervalMs") + + timeRange := tsdb.NewTimeRange(from, to) + request := &tsdb.Request{TimeRange: timeRange} + + request.Queries = append(request.Queries, &tsdb.Query{ + RefId: "A", + IntervalMs: intervalMs, + Model: simplejson.NewFromAny(&util.DynMap{ + "scenario": "random_walk", + }), + DataSource: &models.DataSource{Type: "grafana-testdata-datasource"}, + }) + + resp, err := tsdb.HandleRequest(context.Background(), request) + if err != nil { + return ApiError(500, "Metric request error", err) + } + + return Json(200, &resp) +} diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 7b6558656f1..2a364d5f464 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -16,7 +16,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "frequency", Type: DB_BigInt, Nullable: false}, {Name: "handler", Type: DB_BigInt, Nullable: false}, @@ -70,7 +70,7 @@ func addAlertMigrations(mg *Migrator) { mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{ {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "message", Type: DB_Text, Nullable: false}, - {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 190, Nullable: false}, {Name: "settings", Type: DB_Text, Nullable: false}, {Name: "severity", Type: DB_Text, Nullable: false}, {Name: "execution_error", Type: DB_Text, Nullable: false}, diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go index 0ef2f3be54f..ee0cc1d893f 100644 --- a/pkg/services/sqlstore/migrations/dashboard_mig.go +++ b/pkg/services/sqlstore/migrations/dashboard_mig.go @@ -8,7 +8,7 @@ func addDashboardMigration(mg *Migrator) { Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, {Name: "account_id", Type: DB_BigInt, Nullable: false}, @@ -56,7 +56,7 @@ func addDashboardMigration(mg *Migrator) { Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "data", Type: DB_Text, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, @@ -114,7 +114,7 @@ func addDashboardMigration(mg *Migrator) { // add column to store plugin_id mg.AddMigration("Add column plugin_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{ - Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255, + Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 189, })) mg.AddMigration("Add index for plugin_id in dashboard", NewAddIndexMigration(dashboardV2, &Index{ @@ -127,9 +127,9 @@ func addDashboardMigration(mg *Migrator) { })) mg.AddMigration("Update dashboard table charset", NewTableCharsetMigration("dashboard", []*Column{ - {Name: "slug", Type: DB_NVarchar, Length: 190, Nullable: false}, + {Name: "slug", Type: DB_NVarchar, Length: 189, Nullable: false}, {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false}, - {Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255}, + {Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 189}, {Name: "data", Type: DB_MediumText, Nullable: false}, })) diff --git a/pkg/services/sqlstore/migrations/temp_user.go b/pkg/services/sqlstore/migrations/temp_user.go index 5592ab7e4ad..3913b18b3d8 100644 --- a/pkg/services/sqlstore/migrations/temp_user.go +++ b/pkg/services/sqlstore/migrations/temp_user.go @@ -9,10 +9,10 @@ func addTempUserMigrations(mg *Migrator) { {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "version", Type: DB_Int, Nullable: false}, - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true}, {Name: "email_sent", Type: DB_Bool}, @@ -37,10 +37,10 @@ func addTempUserMigrations(mg *Migrator) { addTableIndicesMigrations(mg, "v1-7", tempUserV1) mg.AddMigration("Update temp_user table charset", NewTableCharsetMigration("temp_user", []*Column{ - {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "email", Type: DB_NVarchar, Length: 190}, {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, - {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "code", Type: DB_NVarchar, Length: 190}, {Name: "status", Type: DB_Varchar, Length: 20}, {Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true}, })) diff --git a/pkg/social/common.go b/pkg/social/common.go index 7bce5d2ae8f..a5b8973306f 100644 --- a/pkg/social/common.go +++ b/pkg/social/common.go @@ -2,7 +2,11 @@ package social import ( "fmt" + "io/ioutil" + "net/http" "strings" + + "github.com/grafana/grafana/pkg/log" ) func isEmailAllowed(email string, allowedDomains []string) bool { @@ -18,3 +22,25 @@ func isEmailAllowed(email string, allowedDomains []string) bool { return valid } + +func HttpGet(client *http.Client, url string) ([]byte, error) { + r, err := client.Get(url) + if err != nil { + return nil, err + } + + defer r.Body.Close() + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + + if r.StatusCode >= 300 { + return nil, fmt.Errorf(string(body)) + } + + log.Trace("HTTP GET %s: %s %s", url, r.Status, string(body)) + + return body, nil +} diff --git a/pkg/social/generic_oauth.go b/pkg/social/generic_oauth.go index 04b0536852a..76b2b734cd6 100644 --- a/pkg/social/generic_oauth.go +++ b/pkg/social/generic_oauth.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net/http" "github.com/grafana/grafana/pkg/models" @@ -84,22 +83,14 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) { IsConfirmed bool `json:"is_confirmed"` } - emailsUrl := fmt.Sprintf(s.apiUrl + "/emails") - r, err := client.Get(emailsUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails")) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } - defer r.Body.Close() - var records []Record - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return "", err - } - - err = json.Unmarshal(body, records) + err = json.Unmarshal(body, &records) if err != nil { var data struct { Values []Record `json:"values"` @@ -107,7 +98,7 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) { err = json.Unmarshal(body, &data) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } records = data.Values @@ -129,18 +120,16 @@ func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") - r, err := client.Get(membershipUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting team memberships: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting team memberships: %s", err) } var ids = make([]int, len(records)) @@ -156,18 +145,16 @@ func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) Login string `json:"login"` } - url := fmt.Sprintf(s.apiUrl + "/orgs") - r, err := client.Get(url) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting organizations: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting organizations: %s", err) } var logins = make([]string, len(records)) @@ -188,16 +175,14 @@ func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) { Attributes map[string][]string `json:"attributes"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/pkg/social/github_oauth.go b/pkg/social/github_oauth.go index 271a472be84..b7b8d7c8156 100644 --- a/pkg/social/github_oauth.go +++ b/pkg/social/github_oauth.go @@ -85,18 +85,16 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) { Verified bool `json:"verified"` } - emailsUrl := fmt.Sprintf(s.apiUrl + "/emails") - r, err := client.Get(emailsUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails")) if err != nil { - return "", err + return "", fmt.Errorf("Error getting email address: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return "", err + err = json.Unmarshal(body, &records) + if err != nil { + return "", fmt.Errorf("Error getting email address: %s", err) } var email = "" @@ -114,18 +112,16 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") - r, err := client.Get(membershipUrl) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/teams")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting team memberships: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting team memberships: %s", err) } var ids = make([]int, len(records)) @@ -141,18 +137,16 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) Login string `json:"login"` } - url := fmt.Sprintf(s.apiUrl + "/orgs") - r, err := client.Get(url) + body, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs")) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting organizations: %s", err) } - defer r.Body.Close() - var records []Record - if err = json.NewDecoder(r.Body).Decode(&records); err != nil { - return nil, err + err = json.Unmarshal(body, &records) + if err != nil { + return nil, fmt.Errorf("Error getting organizations: %s", err) } var logins = make([]string, len(records)) @@ -170,16 +164,14 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) { Email string `json:"email"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/pkg/social/google_oauth.go b/pkg/social/google_oauth.go index d140f385b66..94a05b25140 100644 --- a/pkg/social/google_oauth.go +++ b/pkg/social/google_oauth.go @@ -2,6 +2,7 @@ package social import ( "encoding/json" + "fmt" "net/http" "github.com/grafana/grafana/pkg/models" @@ -34,16 +35,17 @@ func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) { Name string `json:"name"` Email string `json:"email"` } - var err error - r, err := client.Get(s.apiUrl) + body, err := HttpGet(client, s.apiUrl) if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } + return &BasicUserInfo{ Name: data.Name, Email: data.Email, diff --git a/pkg/social/grafana_com_oauth.go b/pkg/social/grafana_com_oauth.go index 498af7f1935..dd693f18d5d 100644 --- a/pkg/social/grafana_com_oauth.go +++ b/pkg/social/grafana_com_oauth.go @@ -2,6 +2,7 @@ package social import ( "encoding/json" + "fmt" "net/http" "github.com/grafana/grafana/pkg/models" @@ -57,16 +58,14 @@ func (s *SocialGrafanaCom) UserInfo(client *http.Client) (*BasicUserInfo, error) Orgs []OrgRecord `json:"orgs"` } - var err error - r, err := client.Get(s.url + "/api/oauth2/user") + body, err := HttpGet(client, s.url+"/api/oauth2/user") if err != nil { - return nil, err + return nil, fmt.Errorf("Error getting user info: %s", err) } - defer r.Body.Close() - - if err = json.NewDecoder(r.Body).Decode(&data); err != nil { - return nil, err + err = json.Unmarshal(body, &data) + if err != nil { + return nil, fmt.Errorf("Error getting user info: %s", err) } userInfo := &BasicUserInfo{ diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index f48d941a8ba..5a677094754 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -105,10 +105,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { if (pageClass) { body.removeClass(pageClass); } - pageClass = data.$$route.pageClass; - if (pageClass) { - body.addClass(pageClass); + + if (data.$$route) { + pageClass = data.$$route.pageClass; + if (pageClass) { + body.addClass(pageClass); + } } + $("#tooltip, .tooltip").remove(); // check for kiosk url param @@ -194,6 +198,15 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { }); } } + + // hide menus + var openMenus = body.find('.navbar-page-btn--open'); + if (openMenus.length > 0) { + if (target.parents('.navbar-page-btn--open').length === 0) { + openMenus.removeClass('navbar-page-btn--open'); + } + } + // hide sidemenu if (!ignoreSideMenuHide && !contextSrv.pinned && body.find('.sidemenu').length > 0) { if (target.parents('.sidemenu').length === 0) { diff --git a/public/app/core/components/help/help.html b/public/app/core/components/help/help.html index 3356f21d452..c07d57a0ffc 100644 --- a/public/app/core/components/help/help.html +++ b/public/app/core/components/help/help.html @@ -1,7 +1,7 @@