Merge branch 'master' into backend_plugins

* master: (48 commits)
  fix: unit test fixed
  prettier: change to single quoting
  ux: minor name change to search sections
  db: fix postgres regression when comparing boolean columns/values (#10303)
  dashboard: delete row improvements
  fix missing comma in documentation output example
  fix broken link (#10291)
  minor fixes and formatting after review
  dashfolders: use validation service for folder creation and dashboard import. #10197
  dashfolders: support creating new folder when moving dashboards. #10197
  dashfolders: support creating new folder when saving a dashboard. #10197
  dashfolders: support creating new folder in dashboard settings. #10197
  dashfolders: support creating new folder from the folder picker. #10197
  tech: ran prettier on all scss files
  tech: ran pretttier on all typescript files
  search: closes dash search when selecting current dashboard (#10285)
  fix: Original dashboard link from snapshot should be an a-tag, not a button (#10269) (#10283)
  dashboard: fixes #10262
  added new to new dahsboard and folder
  test: Update test with new component signature
  ...
This commit is contained in:
Carl Bergquist
2017-12-20 14:20:33 +01:00
501 changed files with 22164 additions and 14032 deletions
+2
View File
@@ -1,5 +1,7 @@
# 5.0.0 (unreleased / master branch) # 5.0.0 (unreleased / master branch)
Grafana v5.0 is going to be the biggest and most foundational release Grafana has ever had, coming with a ton of UX improvements, a new dashboard grid engine, dashboard folders, user teams and permissions. Checkout out this [video preview](https://www.youtube.com/watch?v=BC_YRNpqj5k) of Grafana v5.
### New Features ### New Features
- **Dashboards** Dashboard folders, [#1611](https://github.com/grafana/grafana/issues/1611) - **Dashboards** Dashboard folders, [#1611](https://github.com/grafana/grafana/issues/1611)
- **Teams** User groups (teams) implemented. Can be used in folder & dashboard permission list. - **Teams** User groups (teams) implemented. Can be used in folder & dashboard permission list.
+3
View File
@@ -9,6 +9,9 @@ Graphite, Elasticsearch, OpenTSDB, Prometheus and InfluxDB.
![](http://docs.grafana.org/assets/img/features/dashboard_ex1.png) ![](http://docs.grafana.org/assets/img/features/dashboard_ex1.png)
## Grafana v5 Alpha Preview
Grafana master is now v5.0 alpha. This is going to be the biggest and most foundational release Grafana has ever had, coming with a ton of UX improvements, a new dashboard grid engine, dashboard folders, user teams and permissions. Checkout out this [video preview](https://www.youtube.com/watch?v=BC_YRNpqj5k) of Grafana v5.
## Installation ## Installation
Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get) Head to [docs.grafana.org](http://docs.grafana.org/installation/) and [download](https://grafana.com/get)
the latest release. the latest release.
@@ -45,7 +45,14 @@ To simplify syntax and to allow for dynamic parts, like date range filters, the
Macro example | Description Macro example | Description
------------ | ------------- ------------ | -------------
*$__time(dateColumn)* | Will be replaced by an expression to convert to a UNIX timestamp and rename the column to `time_sec`. For example, *UNIX_TIMESTAMP(dateColumn) as time_sec*
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > FROM_UNIXTIME(1494410783) AND dateColumn < FROM_UNIXTIME(1494497183)* *$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn > FROM_UNIXTIME(1494410783) AND dateColumn < FROM_UNIXTIME(1494497183)*
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *FROM_UNIXTIME(1494410783)*
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *FROM_UNIXTIME(1494497183)*
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed) as time_sec,*
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
We plan to add many more macros. If you have suggestions for what macros you would like to see, please [open an issue](https://github.com/grafana/grafana) in our GitHub repo. We plan to add many more macros. If you have suggestions for what macros you would like to see, please [open an issue](https://github.com/grafana/grafana) in our GitHub repo.
@@ -99,6 +106,19 @@ GROUP BY metric1, UNIX_TIMESTAMP(time_date_time) DIV 300
ORDER BY time_sec asc ORDER BY time_sec asc
``` ```
Example with $__timeGroup macro:
```sql
SELECT
$__timeGroup(time_date_time,'5m') as time_sec,
min(value_double) as value,
metric_name as metric
FROM test_data
WHERE $__timeFilter(time_date_time)
GROUP BY 1, metric_name
ORDER BY 1
```
Currently, there is no support for a dynamic group by time based on time range & panel width. Currently, there is no support for a dynamic group by time based on time range & panel width.
This is something we plan to add. This is something we plan to add.
+1 -1
View File
@@ -47,7 +47,7 @@ The coloring options of the Singlestat Panel config allow you to dynamically cha
2. **Thresholds**: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90. 2. **Thresholds**: Change the background and value colors dynamically within the panel, depending on the Singlestat value. The threshold field accepts **2 comma-separated** values which represent 3 ranges that correspond to the three colors directly to the right. For example: if the thresholds are 70, 90 then the first color represents < 70, the second color represents between 70 and 90 and the third color represents > 90.
3. **Colors**: Select a color and opacity 3. **Colors**: Select a color and opacity
4. **Value**: This checkbox applies the configured thresholds and colors to the summary stat. 4. **Value**: This checkbox applies the configured thresholds and colors to the summary stat.
5. **Invert order**: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/docs(v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/docs/v1/ryg.png">). 5. **Invert order**: This link toggles the threshold color order.</br>For example: Green, Orange, Red (<img class="no-shadow" src="/img/docs/v1/gyr.png">) will become Red, Orange, Green (<img class="no-shadow" src="/img/docs/v1/ryg.png">).
### Spark Lines ### Spark Lines
+2 -2
View File
@@ -156,7 +156,7 @@ HTTP/1.1 200
Content-Type: application/json Content-Type: application/json
{ {
"email": "user@mygraf.com" "email": "user@mygraf.com",
"name": "admin", "name": "admin",
"login": "admin", "login": "admin",
"theme": "light", "theme": "light",
@@ -409,4 +409,4 @@ HTTP/1.1 200
Content-Type: application/json Content-Type: application/json
{"message":"Dashboard unstarred"} {"message":"Dashboard unstarred"}
``` ```
+13 -3
View File
@@ -65,7 +65,7 @@
"karma-sinon": "^1.0.5", "karma-sinon": "^1.0.5",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.4", "karma-webpack": "^2.0.4",
"lint-staged": "^4.2.3", "lint-staged": "^6.0.0",
"load-grunt-tasks": "3.5.2", "load-grunt-tasks": "3.5.2",
"mocha": "^4.0.1", "mocha": "^4.0.1",
"ng-annotate-loader": "^0.6.1", "ng-annotate-loader": "^0.6.1",
@@ -76,7 +76,7 @@
"postcss-browser-reporter": "^0.5.0", "postcss-browser-reporter": "^0.5.0",
"postcss-loader": "^2.0.6", "postcss-loader": "^2.0.6",
"postcss-reporter": "^5.0.0", "postcss-reporter": "^5.0.0",
"prettier": "1.7.3", "prettier": "1.9.2",
"react-test-renderer": "^16.0.0", "react-test-renderer": "^16.0.0",
"sass-lint": "^1.10.2", "sass-lint": "^1.10.2",
"sass-loader": "^6.0.6", "sass-loader": "^6.0.6",
@@ -103,7 +103,17 @@
"lint": "tslint -c tslint.json --project tsconfig.json --type-check", "lint": "tslint -c tslint.json --project tsconfig.json --type-check",
"karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev", "karma": "node ./node_modules/grunt-cli/bin/grunt karma:dev",
"jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch", "jest": "node ./node_modules/jest-cli/bin/jest.js --notify --watch",
"precommit": "node ./node_modules/grunt-cli/bin/grunt precommit" "precommit": "lint-staged && node ./node_modules/grunt-cli/bin/grunt precommit"
},
"lint-staged": {
"*.{ts,tsx}": [
"prettier --single-quote --trailing-comma es5 --write",
"git add"
],
"*.scss": [
"prettier --single-quote --write",
"git add"
]
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -92,7 +92,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Text: "Create", Text: "Create",
Id: "create", Id: "create",
Icon: "fa fa-fw fa-plus", Icon: "fa fa-fw fa-plus",
Url: setting.AppSubUrl + "dashboard/new", Url: setting.AppSubUrl + "/dashboard/new",
Children: []*dtos.NavLink{ Children: []*dtos.NavLink{
{Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"}, {Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"},
{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"}, {Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"},
+3 -2
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@@ -41,7 +42,7 @@ func OrgRedirect() macaron.Handler {
return return
} }
newUrl := setting.ToAbsUrl(fmt.Sprintf("%s?%s", c.Req.URL.Path, c.Req.URL.Query().Encode())) newURL := setting.ToAbsUrl(fmt.Sprintf("%s?%s", strings.TrimPrefix(c.Req.URL.Path, "/"), c.Req.URL.Query().Encode()))
c.Redirect(newUrl, 302) c.Redirect(newURL, 302)
} }
} }
+100 -75
View File
@@ -69,6 +69,90 @@ func copyJson(in *simplejson.Json) (*simplejson.Json, error) {
return simplejson.NewJson(rawJson) return simplejson.NewJson(rawJson)
} }
func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) {
alerts := make([]*m.Alert, 0)
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
panel := simplejson.NewFromAny(panelObj)
jsonAlert, hasAlert := panel.CheckGet("alert")
if !hasAlert {
continue
}
panelId, err := panel.Get("id").Int64()
if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err)
}
// backward compatibility check, can be removed later
enabled, hasEnabled := jsonAlert.CheckGet("enabled")
if hasEnabled && enabled.MustBool() == false {
continue
}
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
if err != nil {
return nil, ValidationError{Reason: "Could not parse frequency"}
}
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgId,
PanelId: panelId,
Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(),
Handler: jsonAlert.Get("handler").MustInt64(),
Message: jsonAlert.Get("message").MustString(),
Frequency: frequency,
}
for _, condition := range jsonAlert.Get("conditions").MustArray() {
jsonCondition := simplejson.NewFromAny(condition)
jsonQuery := jsonCondition.Get("query")
queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefId(panel, queryRefId)
if panelQuery == nil {
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
return nil, ValidationError{Reason: reason}
}
dsName := ""
if panelQuery.Get("datasource").MustString() != "" {
dsName = panelQuery.Get("datasource").MustString()
} else if panel.Get("datasource").MustString() != "" {
dsName = panel.Get("datasource").MustString()
}
if datasource, err := e.lookupDatasourceId(dsName); err != nil {
return nil, err
} else {
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
}
if interval, err := panel.Get("interval").String(); err == nil {
panelQuery.Set("interval", interval)
}
jsonQuery.Set("model", panelQuery.Interface())
}
alert.Settings = jsonAlert
// validate
_, err = NewRuleFromDBAlert(alert)
if err == nil && alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
return nil, err
}
}
return alerts, nil
}
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
e.log.Debug("GetAlerts") e.log.Debug("GetAlerts")
@@ -78,86 +162,27 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
} }
alerts := make([]*m.Alert, 0) alerts := make([]*m.Alert, 0)
for _, rowObj := range dashboardJson.Get("rows").MustArray() {
row := simplejson.NewFromAny(rowObj)
for _, panelObj := range row.Get("panels").MustArray() { // We extract alerts from rows to be backwards compatible
panel := simplejson.NewFromAny(panelObj) // with the old dashboard json model.
jsonAlert, hasAlert := panel.CheckGet("alert") rows := dashboardJson.Get("rows").MustArray()
if len(rows) > 0 {
if !hasAlert { for _, rowObj := range rows {
continue row := simplejson.NewFromAny(rowObj)
} a, err := e.GetAlertFromPanels(row)
panelId, err := panel.Get("id").Int64()
if err != nil { if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err)
}
// backward compatibility check, can be removed later
enabled, hasEnabled := jsonAlert.CheckGet("enabled")
if hasEnabled && enabled.MustBool() == false {
continue
}
frequency, err := getTimeDurationStringToSeconds(jsonAlert.Get("frequency").MustString())
if err != nil {
return nil, ValidationError{Reason: "Could not parse frequency"}
}
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgId,
PanelId: panelId,
Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(),
Handler: jsonAlert.Get("handler").MustInt64(),
Message: jsonAlert.Get("message").MustString(),
Frequency: frequency,
}
for _, condition := range jsonAlert.Get("conditions").MustArray() {
jsonCondition := simplejson.NewFromAny(condition)
jsonQuery := jsonCondition.Get("query")
queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefId(panel, queryRefId)
if panelQuery == nil {
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
return nil, ValidationError{Reason: reason}
}
dsName := ""
if panelQuery.Get("datasource").MustString() != "" {
dsName = panelQuery.Get("datasource").MustString()
} else if panel.Get("datasource").MustString() != "" {
dsName = panel.Get("datasource").MustString()
}
if datasource, err := e.lookupDatasourceId(dsName); err != nil {
return nil, err
} else {
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
}
if interval, err := panel.Get("interval").String(); err == nil {
panelQuery.Set("interval", interval)
}
jsonQuery.Set("model", panelQuery.Interface())
}
alert.Settings = jsonAlert
// validate
_, err = NewRuleFromDBAlert(alert)
if err == nil && alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
return nil, err return nil, err
} }
alerts = append(alerts, a...)
} }
} else {
a, err := e.GetAlertFromPanels(dashboardJson)
if err != nil {
return nil, err
}
alerts = append(alerts, a...)
} }
e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts)) e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))
+28 -416
View File
@@ -1,12 +1,12 @@
package alerting package alerting
import ( import (
"io/ioutil"
"testing" "testing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -18,10 +18,6 @@ func TestAlertRuleExtraction(t *testing.T) {
return &FakeCondition{}, nil return &FakeCondition{}, nil
}) })
setting.NewConfigContext(&setting.CommandLineArgs{
HomePath: "../../../",
})
// mock data // mock data
defaultDs := &m.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true} defaultDs := &m.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true}
graphite2Ds := &m.DataSource{Id: 15, OrgId: 1, Name: "graphite2"} graphite2Ds := &m.DataSource{Id: 15, OrgId: 1, Name: "graphite2"}
@@ -45,70 +41,8 @@ func TestAlertRuleExtraction(t *testing.T) {
return nil return nil
}) })
json := ` json, err := ioutil.ReadFile("./test-data/graphite-alert.json")
{ So(err, ShouldBeNil)
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": ["graphite"],
"rows": [
{
"panels": [
{
"title": "Active desktop users",
"editable": true,
"type": "graph",
"id": 3,
"targets": [
{
"refId": "A",
"target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
}
],
"datasource": null,
"alert": {
"name": "name1",
"message": "desc1",
"handler": 1,
"frequency": "60s",
"conditions": [
{
"type": "query",
"query": {"params": ["A", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
},
{
"title": "Active mobile users",
"id": 4,
"targets": [
{"refId": "A", "target": ""},
{"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
],
"datasource": "graphite2",
"alert": {
"name": "name2",
"message": "desc2",
"handler": 0,
"frequency": "60s",
"severity": "warning",
"conditions": [
{
"type": "query",
"query": {"params": ["B", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
}
]
}
]
}`
Convey("Extractor should not modify the original json", func() { Convey("Extractor should not modify the original json", func() {
dashJson, err := simplejson.NewJson([]byte(json)) dashJson, err := simplejson.NewJson([]byte(json))
@@ -201,69 +135,8 @@ func TestAlertRuleExtraction(t *testing.T) {
}) })
Convey("Panels missing id should return error", func() { Convey("Panels missing id should return error", func() {
panelWithoutId := ` panelWithoutId, err := ioutil.ReadFile("./test-data/panels-missing-id.json")
{ So(err, ShouldBeNil)
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": ["graphite"],
"rows": [
{
"panels": [
{
"title": "Active desktop users",
"editable": true,
"type": "graph",
"targets": [
{
"refId": "A",
"target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
}
],
"datasource": null,
"alert": {
"name": "name1",
"message": "desc1",
"handler": 1,
"frequency": "60s",
"conditions": [
{
"type": "query",
"query": {"params": ["A", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
},
{
"title": "Active mobile users",
"id": 4,
"targets": [
{"refId": "A", "target": ""},
{"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
],
"datasource": "graphite2",
"alert": {
"name": "name2",
"message": "desc2",
"handler": 0,
"frequency": "60s",
"severity": "warning",
"conditions": [
{
"type": "query",
"query": {"params": ["B", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
}
]
}
]
}`
dashJson, err := simplejson.NewJson([]byte(panelWithoutId)) dashJson, err := simplejson.NewJson([]byte(panelWithoutId))
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -277,292 +150,31 @@ func TestAlertRuleExtraction(t *testing.T) {
}) })
}) })
Convey("Parse alerts from dashboard without rows", func() {
json, err := ioutil.ReadFile("./test-data/v5-dashboard.json")
So(err, ShouldBeNil)
dashJson, err := simplejson.NewJson(json)
So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJson)
extractor := NewDashAlertExtractor(dash, 1)
alerts, err := extractor.GetAlerts()
Convey("Get rules without error", func() {
So(err, ShouldBeNil)
})
Convey("Should have 2 alert rule", func() {
So(len(alerts), ShouldEqual, 2)
})
})
Convey("Parse and validate dashboard containing influxdb alert", func() { Convey("Parse and validate dashboard containing influxdb alert", func() {
json, err := ioutil.ReadFile("./test-data/influxdb-alert.json")
So(err, ShouldBeNil)
json2 := `{ dashJson, err := simplejson.NewJson(json)
"id": 4,
"title": "Influxdb",
"tags": [
"apa"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "450px",
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
10
],
"type": "gt"
},
"query": {
"params": [
"B",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"frequency": "3s",
"handler": 1,
"name": "Influxdb",
"noDataState": "no_data",
"notifications": [
{
"id": 6
}
]
},
"alerting": {},
"aliasColors": {
"logins.count.count": "#890F02"
},
"bars": false,
"datasource": "InfluxDB",
"editable": true,
"error": false,
"fill": 1,
"grid": {},
"id": 1,
"interval": ">10s",
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"datacenter"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"hide": false,
"measurement": "logins.count",
"policy": "default",
"query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "count"
}
]
],
"tags": []
},
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": true,
"measurement": "cpu",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
],
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "sum"
}
]
],
"tags": []
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 10
}
],
"timeFrom": null,
"timeShift": null,
"title": "Panel Title",
"tooltip": {
"msResolution": false,
"ordering": "alphabetical",
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"editable": true,
"error": false,
"id": 2,
"isNew": true,
"limit": 10,
"links": [],
"show": "current",
"span": 2,
"stateFilter": [
"alerting"
],
"title": "Alert status",
"type": "alertlist"
}
],
"title": "Row"
}
],
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 13,
"version": 120,
"links": [],
"gnetId": null
}`
dashJson, err := simplejson.NewJson([]byte(json2))
So(err, ShouldBeNil) So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJson) dash := m.NewDashboardFromJson(dashJson)
extractor := NewDashAlertExtractor(dash, 1) extractor := NewDashAlertExtractor(dash, 1)
@@ -133,6 +133,7 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Error("Failed get rule link", "error", err) this.log.Error("Failed get rule link", "error", err)
return err return err
} }
message := evalContext.Rule.Message message := evalContext.Rule.Message
for idx, evt := range evalContext.EvalMatches { for idx, evt := range evalContext.EvalMatches {
message += fmt.Sprintf("\n<b>%s</b>: %v", evt.Metric, evt.Value) message += fmt.Sprintf("\n<b>%s</b>: %v", evt.Metric, evt.Value)
@@ -146,6 +147,9 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
message += fmt.Sprintf("\n<a href=\"%s\">Show graph image</a>", evalContext.ImagePublicUrl) message += fmt.Sprintf("\n<a href=\"%s\">Show graph image</a>", evalContext.ImagePublicUrl)
} }
if message == "" {
message = "Notification message missing (Set a notification message to replace this text.)"
}
q := url.Values{} q := url.Values{}
q.Add("user", this.UserKey) q.Add("user", this.UserKey)
@@ -0,0 +1,63 @@
{
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": ["graphite"],
"rows": [
{
"panels": [
{
"title": "Active desktop users",
"editable": true,
"type": "graph",
"id": 3,
"targets": [
{
"refId": "A",
"target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
}
],
"datasource": null,
"alert": {
"name": "name1",
"message": "desc1",
"handler": 1,
"frequency": "60s",
"conditions": [
{
"type": "query",
"query": {"params": ["A", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
},
{
"title": "Active mobile users",
"id": 4,
"targets": [
{"refId": "A", "target": ""},
{"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
],
"datasource": "graphite2",
"alert": {
"name": "name2",
"message": "desc2",
"handler": 0,
"frequency": "60s",
"severity": "warning",
"conditions": [
{
"type": "query",
"query": {"params": ["B", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
}
]
}
]
}
@@ -0,0 +1,282 @@
{
"id": 4,
"title": "Influxdb",
"tags": [
"apa"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "450px",
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
10
],
"type": "gt"
},
"query": {
"params": [
"B",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"frequency": "3s",
"handler": 1,
"name": "Influxdb",
"noDataState": "no_data",
"notifications": [
{
"id": 6
}
]
},
"alerting": {},
"aliasColors": {
"logins.count.count": "#890F02"
},
"bars": false,
"datasource": "InfluxDB",
"editable": true,
"error": false,
"fill": 1,
"grid": {},
"id": 1,
"interval": ">10s",
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"datacenter"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"hide": false,
"measurement": "logins.count",
"policy": "default",
"query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "count"
}
]
],
"tags": []
},
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": true,
"measurement": "cpu",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
],
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "sum"
}
]
],
"tags": []
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 10
}
],
"timeFrom": null,
"timeShift": null,
"title": "Panel Title",
"tooltip": {
"msResolution": false,
"ordering": "alphabetical",
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"editable": true,
"error": false,
"id": 2,
"isNew": true,
"limit": 10,
"links": [],
"show": "current",
"span": 2,
"stateFilter": [
"alerting"
],
"title": "Alert status",
"type": "alertlist"
}
],
"title": "Row"
}
],
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 13,
"version": 120,
"links": [],
"gnetId": null
}
@@ -0,0 +1,62 @@
{
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": ["graphite"],
"rows": [
{
"panels": [
{
"title": "Active desktop users",
"editable": true,
"type": "graph",
"targets": [
{
"refId": "A",
"target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
}
],
"datasource": null,
"alert": {
"name": "name1",
"message": "desc1",
"handler": 1,
"frequency": "60s",
"conditions": [
{
"type": "query",
"query": {"params": ["A", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
},
{
"title": "Active mobile users",
"id": 4,
"targets": [
{"refId": "A", "target": ""},
{"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
],
"datasource": "graphite2",
"alert": {
"name": "name2",
"message": "desc2",
"handler": 0,
"frequency": "60s",
"severity": "warning",
"conditions": [
{
"type": "query",
"query": {"params": ["B", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
}
]
}
]
}
@@ -0,0 +1,60 @@
{
"id": 57,
"title": "Graphite 4",
"originalTitle": "Graphite 4",
"tags": ["graphite"],
"panels": [
{
"title": "Active desktop users",
"editable": true,
"type": "graph",
"id": 3,
"targets": [
{
"refId": "A",
"target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
}
],
"datasource": null,
"alert": {
"name": "name1",
"message": "desc1",
"handler": 1,
"frequency": "60s",
"conditions": [
{
"type": "query",
"query": {"params": ["A", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
},
{
"title": "Active mobile users",
"id": 4,
"targets": [
{"refId": "A", "target": ""},
{"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
],
"datasource": "graphite2",
"alert": {
"name": "name2",
"message": "desc2",
"handler": 0,
"frequency": "60s",
"severity": "warning",
"conditions": [
{
"type": "query",
"query": {"params": ["B", "5m", "now"]},
"reducer": {"type": "avg", "params": []},
"evaluator": {"type": ">", "params": [100]}
}
]
}
}
]
}
+2 -1
View File
@@ -345,8 +345,9 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error { func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
var dashboards = make([]*m.Dashboard, 0) var dashboards = make([]*m.Dashboard, 0)
whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
err := x.Where("org_id=? AND plugin_id=? AND is_folder=0", query.OrgId, query.PluginId).Find(&dashboards) err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
query.Result = dashboards query.Result = dashboards
if err != nil { if err != nil {
+6 -1
View File
@@ -170,7 +170,12 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
FROM dashboard_acl as da, FROM dashboard_acl as da,
dashboard as dash dashboard as dash
LEFT JOIN dashboard folder on dash.folder_id = folder.id LEFT JOIN dashboard folder on dash.folder_id = folder.id
WHERE dash.id = ? AND (dash.has_acl = 0 or folder.has_acl = 0) AND da.dashboard_id = -1 WHERE
dash.id = ? AND (
dash.has_acl = ` + dialect.BooleanStr(false) + ` or
folder.has_acl = ` + dialect.BooleanStr(false) + `
) AND
da.dashboard_id = -1
` `
query.Result = make([]*m.DashboardAclInfoDTO, 0) query.Result = make([]*m.DashboardAclInfoDTO, 0)
+1 -1
View File
@@ -7,7 +7,7 @@ func addTeamMigrations(mg *Migrator) {
Name: "team", Name: "team",
Columns: []*Column{ Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "name", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "org_id", Type: DB_BigInt}, {Name: "org_id", Type: DB_BigInt},
{Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false},
+4 -4
View File
@@ -175,14 +175,14 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
} }
if sb.signedInUser.OrgRole != m.ROLE_ADMIN { if sb.signedInUser.OrgRole != m.ROLE_ADMIN {
allowedDashboardsSubQuery := ` AND (dashboard.has_acl = 0 OR dashboard.id in ( allowedDashboardsSubQuery := ` AND (dashboard.has_acl = ` + dialect.BooleanStr(false) + ` OR dashboard.id in (
SELECT distinct d.id AS DashboardId SELECT distinct d.id AS DashboardId
FROM dashboard AS d FROM dashboard AS d
LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
LEFT JOIN org_user ou on ou.role = da.role LEFT JOIN org_user ou on ou.role = da.role
WHERE WHERE
d.has_acl = 1 and d.has_acl = ` + dialect.BooleanStr(true) + ` and
(da.user_id = ? or ugm.user_id = ? or ou.id is not null) (da.user_id = ? or ugm.user_id = ? or ou.id is not null)
and d.org_id = ? and d.org_id = ?
) )
@@ -198,11 +198,11 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
} }
if sb.whereTypeFolder { if sb.whereTypeFolder {
sb.sql.WriteString(" AND dashboard.is_folder = 1") sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(true))
} }
if sb.whereTypeDash { if sb.whereTypeDash {
sb.sql.WriteString(" AND dashboard.is_folder = 0") sb.sql.WriteString(" AND dashboard.is_folder = " + dialect.BooleanStr(false))
} }
if len(sb.whereFolderIds) > 0 { if len(sb.whereFolderIds) > 0 {
+1 -1
View File
@@ -127,7 +127,7 @@ func init() {
"AWS/Events": {"RuleName"}, "AWS/Events": {"RuleName"},
"AWS/Firehose": {"DeliveryStreamName"}, "AWS/Firehose": {"DeliveryStreamName"},
"AWS/IoT": {"Protocol"}, "AWS/IoT": {"Protocol"},
"AWS/Kinesis": {"StreamName", "ShardID"}, "AWS/Kinesis": {"StreamName", "ShardId"},
"AWS/KinesisAnalytics": {"Flow", "Id", "Application"}, "AWS/KinesisAnalytics": {"Flow", "Id", "Application"},
"AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"}, "AWS/Lambda": {"FunctionName", "Resource", "Version", "Alias"},
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"}, "AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
+66 -44
View File
@@ -21,12 +21,12 @@ import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
// add move to lodash for backward compatabiltiy // add move to lodash for backward compatabiltiy
_.move = function (array, fromIndex, toIndex) { _.move = function(array, fromIndex, toIndex) {
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]); array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]);
return array; return array;
}; };
import {coreModule, registerAngularDirectives} from './core/core'; import { coreModule, registerAngularDirectives } from './core/core';
export class GrafanaApp { export class GrafanaApp {
registerFunctions: any; registerFunctions: any;
@@ -54,36 +54,49 @@ export class GrafanaApp {
moment.locale(config.bootData.user.locale); moment.locale(config.bootData.user.locale);
app.config(($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $httpProvider, $provide) => { app.config(
// pre assing bindings before constructor calls (
$compileProvider.preAssignBindingsEnabled(true); $locationProvider,
$controllerProvider,
$compileProvider,
$filterProvider,
$httpProvider,
$provide
) => {
// pre assing bindings before constructor calls
$compileProvider.preAssignBindingsEnabled(true);
if (config.buildInfo.env !== 'development') { if (config.buildInfo.env !== 'development') {
$compileProvider.debugInfoEnabled(false); $compileProvider.debugInfoEnabled(false);
}
$httpProvider.useApplyAsync(true);
this.registerFunctions.controller = $controllerProvider.register;
this.registerFunctions.directive = $compileProvider.directive;
this.registerFunctions.factory = $provide.factory;
this.registerFunctions.service = $provide.service;
this.registerFunctions.filter = $filterProvider.register;
$provide.decorator('$http', [
'$delegate',
'$templateCache',
function($delegate, $templateCache) {
var get = $delegate.get;
$delegate.get = function(url, config) {
if (url.match(/\.html$/)) {
// some template's already exist in the cache
if (!$templateCache.get(url)) {
url += '?v=' + new Date().getTime();
}
}
return get(url, config);
};
return $delegate;
},
]);
} }
);
$httpProvider.useApplyAsync(true);
this.registerFunctions.controller = $controllerProvider.register;
this.registerFunctions.directive = $compileProvider.directive;
this.registerFunctions.factory = $provide.factory;
this.registerFunctions.service = $provide.service;
this.registerFunctions.filter = $filterProvider.register;
$provide.decorator("$http", ["$delegate", "$templateCache", function($delegate, $templateCache) {
var get = $delegate.get;
$delegate.get = function(url, config) {
if (url.match(/\.html$/)) {
// some template's already exist in the cache
if (!$templateCache.get(url)) {
url += "?v=" + new Date().getTime();
}
}
return get(url, config);
};
return $delegate;
}]);
});
this.ngModuleDependencies = [ this.ngModuleDependencies = [
'grafana.core', 'grafana.core',
@@ -95,10 +108,17 @@ export class GrafanaApp {
'pasvaz.bindonce', 'pasvaz.bindonce',
'ui.bootstrap', 'ui.bootstrap',
'ui.bootstrap.tpls', 'ui.bootstrap.tpls',
'react' 'react',
]; ];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes']; var module_types = [
'controllers',
'directives',
'factories',
'services',
'filters',
'routes',
];
_.each(module_types, type => { _.each(module_types, type => {
var moduleName = 'grafana.' + type; var moduleName = 'grafana.' + type;
@@ -113,20 +133,22 @@ export class GrafanaApp {
var preBootRequires = [System.import('app/features/all')]; var preBootRequires = [System.import('app/features/all')];
Promise.all(preBootRequires).then(() => { Promise.all(preBootRequires)
// disable tool tip animation .then(() => {
$.fn.tooltip.defaults.animation = false; // disable tool tip animation
// bootstrap the app $.fn.tooltip.defaults.animation = false;
angular.bootstrap(document, this.ngModuleDependencies).invoke(() => { // bootstrap the app
_.each(this.preBootModules, module => { angular.bootstrap(document, this.ngModuleDependencies).invoke(() => {
_.extend(module, this.registerFunctions); _.each(this.preBootModules, module => {
}); _.extend(module, this.registerFunctions);
});
this.preBootModules = null; this.preBootModules = null;
});
})
.catch(function(err) {
console.log('Application boot failed:', err);
}); });
}).catch(function(err) {
console.log('Application boot failed:', err);
});
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import {Emitter} from './utils/emitter'; import { Emitter } from './utils/emitter';
var appEvents = new Emitter(); var appEvents = new Emitter();
export default appEvents; export default appEvents;
@@ -1,7 +1,7 @@
import React from 'react'; import React from "react";
import { NavModel, NavModelItem } from '../../nav_model_srv'; import { NavModel, NavModelItem } from "../../nav_model_srv";
import classNames from 'classnames'; import classNames from "classnames";
import appEvents from 'app/core/app_events'; import appEvents from "app/core/app_events";
export interface IProps { export interface IProps {
model: NavModel; model: NavModel;
@@ -9,12 +9,12 @@ export interface IProps {
function TabItem(tab: NavModelItem) { function TabItem(tab: NavModelItem) {
if (tab.hideFromTabs) { if (tab.hideFromTabs) {
return (null); return null;
} }
let tabClasses = classNames({ let tabClasses = classNames({
'gf-tabs-link': true, "gf-tabs-link": true,
active: tab.active, active: tab.active
}); });
return ( return (
@@ -28,8 +28,9 @@ function TabItem(tab: NavModelItem) {
} }
function SelectOption(navItem: NavModelItem) { function SelectOption(navItem: NavModelItem) {
if (navItem.hideFromTabs) { // TODO: Rename hideFromTabs => hideFromNav if (navItem.hideFromTabs) {
return (null); // TODO: Rename hideFromTabs => hideFromNav
return null;
} }
return ( return (
@@ -39,14 +40,22 @@ function SelectOption(navItem: NavModelItem) {
); );
} }
function Navigation({main}: {main: NavModelItem}) { function Navigation({ main }: { main: NavModelItem }) {
return (<nav> return (
<SelectNav customCss="page-header__select_nav" main={main} /> <nav>
<Tabs customCss="page-header__tabs" main={main} /> <SelectNav customCss="page-header__select-nav" main={main} />
</nav>); <Tabs customCss="page-header__tabs" main={main} />
</nav>
);
} }
function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) { function SelectNav({
main,
customCss
}: {
main: NavModelItem;
customCss: string;
}) {
const defaultSelectedItem = main.children.find(navItem => { const defaultSelectedItem = main.children.find(navItem => {
return navItem.active === true; return navItem.active === true;
}); });
@@ -54,17 +63,32 @@ function SelectNav({main, customCss}: {main: NavModelItem, customCss: string}) {
const gotoUrl = evt => { const gotoUrl = evt => {
var element = evt.target; var element = evt.target;
var url = element.options[element.selectedIndex].value; var url = element.options[element.selectedIndex].value;
appEvents.emit('location-change', {href: url}); appEvents.emit("location-change", { href: url });
}; };
return (<select return (
className={`gf-select-nav ${customCss}`} <div className={`gf-form-select-wrapper width-20 ${customCss}`}>
defaultValue={defaultSelectedItem.url} <label
onChange={gotoUrl}>{main.children.map(SelectOption)}</select>); className={`gf-form-select-icon ${defaultSelectedItem.icon}`}
htmlFor="page-header-select-nav"
/>
{/* Label to make it clickable */}
<select
className="gf-select-nav gf-form-input"
defaultValue={defaultSelectedItem.url}
onChange={gotoUrl}
id="page-header-select-nav"
>
{main.children.map(SelectOption)}
</select>
</div>
);
} }
function Tabs({main, customCss}: {main: NavModelItem, customCss: string}) { function Tabs({ main, customCss }: { main: NavModelItem; customCss: string }) {
return <ul className={`gf-tabs ${customCss}`}>{main.children.map(TabItem)}</ul>; return (
<ul className={`gf-tabs ${customCss}`}>{main.children.map(TabItem)}</ul>
);
} }
export default class PageHeader extends React.Component<IProps, any> { export default class PageHeader extends React.Component<IProps, any> {
@@ -77,7 +101,11 @@ export default class PageHeader extends React.Component<IProps, any> {
for (let i = 0; i < breadcrumbs.length; i++) { for (let i = 0; i < breadcrumbs.length; i++) {
const bc = breadcrumbs[i]; const bc = breadcrumbs[i];
if (bc.url) { if (bc.url) {
breadcrumbsResult.push(<a className="text-link" key={i} href={bc.url}>{bc.title}</a>); breadcrumbsResult.push(
<a className="text-link" key={i} href={bc.url}>
{bc.title}
</a>
);
} else { } else {
breadcrumbsResult.push(<span key={i}> / {bc.title}</span>); breadcrumbsResult.push(<span key={i}> / {bc.title}</span>);
} }
@@ -95,12 +123,15 @@ export default class PageHeader extends React.Component<IProps, any> {
<div className="page-header__info-block"> <div className="page-header__info-block">
{main.text && <h1 className="page-header__title">{main.text}</h1>} {main.text && <h1 className="page-header__title">{main.text}</h1>}
{main.breadcrumbs && main.breadcrumbs.length > 0 && ( {main.breadcrumbs &&
<h1 className="page-header__title"> main.breadcrumbs.length > 0 && (
{this.renderBreadcrumb(main.breadcrumbs)} <h1 className="page-header__title">
</h1>) {this.renderBreadcrumb(main.breadcrumbs)}
} </h1>
{main.subTitle && <div className="page-header__sub-title">{main.subTitle}</div>} )}
{main.subTitle && (
<div className="page-header__sub-title">{main.subTitle}</div>
)}
{main.subType && ( {main.subType && (
<div className="page-header__stamps"> <div className="page-header__stamps">
<i className={main.subType.icon} /> <i className={main.subType.icon} />
@@ -38,10 +38,12 @@ import 'brace/mode/sql';
import 'brace/snippets/sql'; import 'brace/snippets/sql';
import 'brace/mode/markdown'; import 'brace/mode/markdown';
import 'brace/snippets/markdown'; import 'brace/snippets/markdown';
import 'brace/mode/json';
import 'brace/snippets/json';
const DEFAULT_THEME_DARK = "ace/theme/grafana-dark"; const DEFAULT_THEME_DARK = 'ace/theme/grafana-dark';
const DEFAULT_THEME_LIGHT = "ace/theme/textmate"; const DEFAULT_THEME_LIGHT = 'ace/theme/textmate';
const DEFAULT_MODE = "text"; const DEFAULT_MODE = 'text';
const DEFAULT_MAX_LINES = 10; const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2; const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIOURS = true; const DEFAULT_BEHAVIOURS = true;
@@ -54,7 +56,9 @@ function link(scope, elem, attrs) {
let maxLines = attrs.maxLines || DEFAULT_MAX_LINES; let maxLines = attrs.maxLines || DEFAULT_MAX_LINES;
let showGutter = attrs.showGutter !== undefined; let showGutter = attrs.showGutter !== undefined;
let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE; let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS; let behavioursEnabled = attrs.behavioursEnabled
? attrs.behavioursEnabled === 'true'
: DEFAULT_BEHAVIOURS;
// Initialize editor // Initialize editor
let aceElem = elem.get(0); let aceElem = elem.get(0);
@@ -68,7 +72,7 @@ function link(scope, elem, attrs) {
behavioursEnabled: behavioursEnabled, behavioursEnabled: behavioursEnabled,
highlightActiveLine: false, highlightActiveLine: false,
showPrintMargin: false, showPrintMargin: false,
autoScrollEditorIntoView: true // this is needed if editor is inside scrollable page autoScrollEditorIntoView: true, // this is needed if editor is inside scrollable page
}; };
// Set options // Set options
@@ -84,12 +88,12 @@ function link(scope, elem, attrs) {
setEditorContent(scope.content); setEditorContent(scope.content);
// Add classes // Add classes
elem.addClass("gf-code-editor"); elem.addClass('gf-code-editor');
let textarea = elem.find("textarea"); let textarea = elem.find('textarea');
textarea.addClass('gf-form-input'); textarea.addClass('gf-form-input');
if (scope.codeEditorFocus) { if (scope.codeEditorFocus) {
setTimeout(function () { setTimeout(function() {
textarea.focus(); textarea.focus();
var domEl = textarea[0]; var domEl = textarea[0];
if (domEl.setSelectionRange) { if (domEl.setSelectionRange) {
@@ -100,7 +104,7 @@ function link(scope, elem, attrs) {
} }
// Event handlers // Event handlers
editorSession.on('change', (e) => { editorSession.on('change', e => {
scope.$apply(() => { scope.$apply(() => {
let newValue = codeEditor.getValue(); let newValue = codeEditor.getValue();
scope.content = newValue; scope.content = newValue;
@@ -121,25 +125,25 @@ function link(scope, elem, attrs) {
scope.onChange(); scope.onChange();
}); });
scope.$on("$destroy", () => { scope.$on('$destroy', () => {
codeEditor.destroy(); codeEditor.destroy();
}); });
// Keybindings // Keybindings
codeEditor.commands.addCommand({ codeEditor.commands.addCommand({
name: 'executeQuery', name: 'executeQuery',
bindKey: {win: 'Ctrl-Enter', mac: 'Command-Enter'}, bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: () => { exec: () => {
scope.onChange(); scope.onChange();
} },
}); });
function setLangMode(lang) { function setLangMode(lang) {
ace.acequire("ace/ext/language_tools"); ace.acequire('ace/ext/language_tools');
codeEditor.setOptions({ codeEditor.setOptions({
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
enableLiveAutocompletion: true, enableLiveAutocompletion: true,
enableSnippets: true enableSnippets: true,
}); });
if (scope.getCompleter()) { if (scope.getCompleter()) {
@@ -173,13 +177,13 @@ export function codeEditorDirective() {
restrict: 'E', restrict: 'E',
template: editorTemplate, template: editorTemplate,
scope: { scope: {
content: "=", content: '=',
datasource: "=", datasource: '=',
codeEditorFocus: "<", codeEditorFocus: '<',
onChange: "&", onChange: '&',
getCompleter: "&" getCompleter: '&',
}, },
link: link link: link,
}; };
} }
@@ -12,13 +12,14 @@ export function spectrumPicker() {
require: 'ngModel', require: 'ngModel',
scope: true, scope: true,
replace: true, replace: true,
template: '<color-picker color="ngModel.$viewValue" onChange="onColorChange"></color-picker>', template:
'<color-picker color="ngModel.$viewValue" onChange="onColorChange"></color-picker>',
link: function(scope, element, attrs, ngModel) { link: function(scope, element, attrs, ngModel) {
scope.ngModel = ngModel; scope.ngModel = ngModel;
scope.onColorChange = (color) => { scope.onColorChange = color => {
ngModel.$setViewValue(color); ngModel.$setViewValue(color);
}; };
} },
}; };
} }
coreModule.directive('spectrumPicker', spectrumPicker); coreModule.directive('spectrumPicker', spectrumPicker);
@@ -9,15 +9,14 @@ export class DashboardSelectorCtrl {
options: any; options: any;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv) { constructor(private backendSrv) {}
}
$onInit() { $onInit() {
this.options = [{value: 0, text: 'Default'}]; this.options = [{ value: 0, text: 'Default' }];
return this.backendSrv.search({starred: true}).then(res => { return this.backendSrv.search({ starred: true }).then(res => {
res.forEach(dash => { res.forEach(dash => {
this.options.push({value: dash.id, text: dash.title}); this.options.push({ value: dash.id, text: dash.title });
}); });
}); });
} }
@@ -31,8 +30,8 @@ export function dashboardSelector() {
controllerAs: 'ctrl', controllerAs: 'ctrl',
template: template, template: template,
scope: { scope: {
model: '=' model: '=',
} },
}; };
} }
@@ -4,8 +4,12 @@ import coreModule from '../../core_module';
function typeaheadMatcher(item) { function typeaheadMatcher(item) {
var str = this.query; var str = this.query;
if (str[0] === '/') { str = str.substring(1); } if (str[0] === '/') {
if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); } str = str.substring(1);
}
if (str[str.length - 1] === '/') {
str = str.substring(0, str.length - 1);
}
return item.toLowerCase().match(str.toLowerCase()); return item.toLowerCase().match(str.toLowerCase());
} }
@@ -28,19 +32,26 @@ export class FormDropdownCtrl {
lookupText: boolean; lookupText: boolean;
/** @ngInject **/ /** @ngInject **/
constructor(private $scope, $element, private $sce, private templateSrv, private $q) { constructor(
private $scope,
$element,
private $sce,
private templateSrv,
private $q
) {
this.inputElement = $element.find('input').first(); this.inputElement = $element.find('input').first();
this.linkElement = $element.find('a').first(); this.linkElement = $element.find('a').first();
this.linkMode = true; this.linkMode = true;
this.cancelBlur = null; this.cancelBlur = null;
// listen to model changes // listen to model changes
$scope.$watch("ctrl.model", this.modelChanged.bind(this)); $scope.$watch('ctrl.model', this.modelChanged.bind(this));
if (this.labelMode) { if (this.labelMode) {
this.cssClasses = 'gf-form-label ' + this.cssClass; this.cssClasses = 'gf-form-label ' + this.cssClass;
} else { } else {
this.cssClasses = 'gf-form-input gf-form-input--dropdown ' + this.cssClass; this.cssClasses =
'gf-form-input gf-form-input--dropdown ' + this.cssClass;
} }
this.inputElement.attr('data-provide', 'typeahead'); this.inputElement.attr('data-provide', 'typeahead');
@@ -55,7 +66,7 @@ export class FormDropdownCtrl {
// modify typeahead lookup // modify typeahead lookup
// this = typeahead // this = typeahead
var typeahead = this.inputElement.data('typeahead'); var typeahead = this.inputElement.data('typeahead');
typeahead.lookup = function () { typeahead.lookup = function() {
this.query = this.$element.val() || ''; this.query = this.$element.val() || '';
var items = this.source(this.query, $.proxy(this.process, this)); var items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items; return items ? this.process(items) : items;
@@ -80,7 +91,7 @@ export class FormDropdownCtrl {
} }
getOptionsInternal(query) { getOptionsInternal(query) {
var result = this.getOptions({$query: query}); var result = this.getOptions({ $query: query });
if (this.isPromiseLike(result)) { if (this.isPromiseLike(result)) {
return result; return result;
} }
@@ -88,7 +99,7 @@ export class FormDropdownCtrl {
} }
isPromiseLike(obj) { isPromiseLike(obj) {
return obj && (typeof obj.then === 'function'); return obj && typeof obj.then === 'function';
} }
modelChanged() { modelChanged() {
@@ -97,8 +108,8 @@ export class FormDropdownCtrl {
} else { } else {
// if we have text use it // if we have text use it
if (this.lookupText) { if (this.lookupText) {
this.getOptionsInternal("").then(options => { this.getOptionsInternal('').then(options => {
var item = _.find(options, {value: this.model}); var item = _.find(options, { value: this.model });
this.updateDisplay(item ? item.text : this.model); this.updateDisplay(item ? item.text : this.model);
}); });
} else { } else {
@@ -140,7 +151,9 @@ export class FormDropdownCtrl {
} }
switchToLink(fromClick) { switchToLink(fromClick) {
if (this.linkMode && !fromClick) { return; } if (this.linkMode && !fromClick) {
return;
}
clearTimeout(this.cancelBlur); clearTimeout(this.cancelBlur);
this.cancelBlur = null; this.cancelBlur = null;
@@ -164,7 +177,7 @@ export class FormDropdownCtrl {
} }
this.$scope.$apply(() => { this.$scope.$apply(() => {
var option = _.find(this.optionCache, {text: text}); var option = _.find(this.optionCache, { text: text });
if (option) { if (option) {
if (_.isObject(this.model)) { if (_.isObject(this.model)) {
@@ -186,20 +199,24 @@ export class FormDropdownCtrl {
// property is synced with outerscope // property is synced with outerscope
this.$scope.$$postDigest(() => { this.$scope.$$postDigest(() => {
this.$scope.$apply(() => { this.$scope.$apply(() => {
this.onChange({$option: option}); this.onChange({ $option: option });
}); });
}); });
}); });
} }
updateDisplay(text) { updateDisplay(text) {
this.text = text; this.text = text;
this.display = this.$sce.trustAsHtml(this.templateSrv.highlightVariablesAsHtml(text)); this.display = this.$sce.trustAsHtml(
this.templateSrv.highlightVariablesAsHtml(text)
);
} }
open() { open() {
this.inputElement.css('width', (Math.max(this.linkElement.width(), 80) + 16) + 'px'); this.inputElement.css(
'width',
Math.max(this.linkElement.width(), 80) + 16 + 'px'
);
this.inputElement.show(); this.inputElement.show();
this.inputElement.focus(); this.inputElement.focus();
@@ -215,7 +232,7 @@ export class FormDropdownCtrl {
} }
} }
const template = ` const template = `
<input type="text" <input type="text"
data-provide="typeahead" data-provide="typeahead"
class="gf-form-input" class="gf-form-input"
@@ -238,13 +255,13 @@ export function formDropdownDirective() {
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: { scope: {
model: "=", model: '=',
getOptions: "&", getOptions: '&',
onChange: "&", onChange: '&',
cssClass: "@", cssClass: '@',
allowCustom: "@", allowCustom: '@',
labelMode: "@", labelMode: '@',
lookupText: "@", lookupText: '@',
}, },
}; };
} }
+4 -4
View File
@@ -27,15 +27,15 @@ export function gfPageDirective() {
restrict: 'E', restrict: 'E',
template: template, template: template,
scope: { scope: {
"model": "=", model: '=',
}, },
transclude: { transclude: {
'header': '?gfPageHeader', header: '?gfPageHeader',
'body': 'gfPageBody', body: 'gfPageBody',
}, },
link: function(scope, elem, attrs) { link: function(scope, elem, attrs) {
console.log(scope); console.log(scope);
} },
}; };
} }
+87 -21
View File
@@ -3,15 +3,21 @@ import _ from 'lodash';
import $ from 'jquery'; import $ from 'jquery';
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import {profiler} from 'app/core/profiler'; import { profiler } from 'app/core/profiler';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import Drop from 'tether-drop'; import Drop from 'tether-drop';
export class GrafanaCtrl { export class GrafanaCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv, globalEventSrv) { constructor(
$scope,
alertSrv,
utilSrv,
$rootScope,
$controller,
contextSrv,
globalEventSrv
) {
$scope.init = function() { $scope.init = function() {
$scope.contextSrv = contextSrv; $scope.contextSrv = contextSrv;
@@ -27,7 +33,7 @@ export class GrafanaCtrl {
}; };
$scope.initDashboard = function(dashboardData, viewScope) { $scope.initDashboard = function(dashboardData, viewScope) {
$scope.appEvent("dashboard-fetch-end", dashboardData); $scope.appEvent('dashboard-fetch-end', dashboardData);
$controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData); $controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
}; };
@@ -49,13 +55,62 @@ export class GrafanaCtrl {
}; };
$rootScope.colors = [ $rootScope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", '#7EB26D',
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", '#EAB839',
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", '#6ED0E0',
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", '#EF843C',
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", '#E24D42',
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", '#1F78C1',
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" '#BA43A9',
'#705DA0',
'#508642',
'#CCA300',
'#447EBC',
'#C15C17',
'#890F02',
'#0A437C',
'#6D1F62',
'#584477',
'#B7DBAB',
'#F4D598',
'#70DBED',
'#F9BA8F',
'#F29191',
'#82B5D8',
'#E5A8E2',
'#AEA2E0',
'#629E51',
'#E5AC0E',
'#64B0C8',
'#E0752D',
'#BF1B00',
'#0A50A1',
'#962D82',
'#614D93',
'#9AC48A',
'#F2C96D',
'#65C5DB',
'#F9934E',
'#EA6460',
'#5195CE',
'#D683CE',
'#806EB7',
'#3F6833',
'#967302',
'#2F575E',
'#99440A',
'#58140C',
'#052B51',
'#511749',
'#3F2B5B',
'#E0F9D7',
'#FCEACA',
'#CFFAFF',
'#F9E2D2',
'#FCE2DE',
'#BADFF4',
'#F9D9F9',
'#DEDAF7',
]; ];
$scope.init(); $scope.init();
@@ -63,7 +118,12 @@ export class GrafanaCtrl {
} }
/** @ngInject */ /** @ngInject */
export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScope) { export function grafanaAppDirective(
playlistSrv,
contextSrv,
$timeout,
$rootScope
) {
return { return {
restrict: 'E', restrict: 'E',
controller: GrafanaCtrl, controller: GrafanaCtrl,
@@ -92,7 +152,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
// tooltip removal fix // tooltip removal fix
// manage page classes // manage page classes
var pageClass; var pageClass;
scope.$on("$routeChangeSuccess", function(evt, data) { scope.$on('$routeChangeSuccess', function(evt, data) {
if (pageClass) { if (pageClass) {
body.removeClass(pageClass); body.removeClass(pageClass);
} }
@@ -107,7 +167,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
// clear body class sidemenu states // clear body class sidemenu states
body.removeClass('sidemenu-open--xs'); body.removeClass('sidemenu-open--xs');
$("#tooltip, .tooltip").remove(); $('#tooltip, .tooltip').remove();
// check for kiosk url param // check for kiosk url param
if (data.params.kiosk) { if (data.params.kiosk) {
@@ -140,7 +200,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
return; return;
} }
if ((new Date().getTime() - lastActivity) > inActiveTimeLimit) { if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
activeUser = false; activeUser = false;
body.addClass('user-activity-low'); body.addClass('user-activity-low');
// hide sidemenu // hide sidemenu
@@ -148,7 +208,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
sidemenuHidden = true; sidemenuHidden = true;
body.removeClass('sidemenu-open'); body.removeClass('sidemenu-open');
$timeout(function() { $timeout(function() {
$rootScope.$broadcast("render"); $rootScope.$broadcast('render');
}, 100); }, 100);
} }
} }
@@ -165,7 +225,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
sidemenuHidden = false; sidemenuHidden = false;
body.addClass('sidemenu-open'); body.addClass('sidemenu-open');
$timeout(function() { $timeout(function() {
$rootScope.$broadcast("render"); $rootScope.$broadcast('render');
}, 100); }, 100);
} }
} }
@@ -209,7 +269,10 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
// hide search // hide search
if (body.find('.search-container').length > 0) { if (body.find('.search-container').length > 0) {
if (target.parents('.search-results-container, .search-field-wrapper').length === 0) { if (
target.parents('.search-results-container, .search-field-wrapper')
.length === 0
) {
scope.$apply(function() { scope.$apply(function() {
scope.appEvent('hide-dash-search'); scope.appEvent('hide-dash-search');
}); });
@@ -218,11 +281,14 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
// hide popovers // hide popovers
var popover = elem.find('.popover'); var popover = elem.find('.popover');
if (popover.length > 0 && target.parents('.graph-legend').length === 0) { if (
popover.length > 0 &&
target.parents('.graph-legend').length === 0
) {
popover.hide(); popover.hide();
} }
}); });
} },
}; };
} }
+32 -26
View File
@@ -11,39 +11,45 @@ export class HelpCtrl {
constructor() { constructor() {
this.tabIndex = 0; this.tabIndex = 0;
this.shortcuts = { this.shortcuts = {
'Global': [ Global: [
{keys: ['g', 'h'], description: 'Go to Home Dashboard'}, { keys: ['g', 'h'], description: 'Go to Home Dashboard' },
{keys: ['g', 'p'], description: 'Go to Profile'}, { keys: ['g', 'p'], description: 'Go to Profile' },
{keys: ['s', 'o'], description: 'Open search'}, { keys: ['s', 'o'], description: 'Open search' },
{keys: ['s', 's'], description: 'Open search with starred filter'}, { keys: ['s', 's'], description: 'Open search with starred filter' },
{keys: ['s', 't'], description: 'Open search in tags view'}, { keys: ['s', 't'], description: 'Open search in tags view' },
{keys: ['esc'], description: 'Exit edit/setting views'}, { keys: ['esc'], description: 'Exit edit/setting views' },
], ],
'Dashboard': [ Dashboard: [
{keys: ['mod+s'], description: 'Save dashboard'}, { keys: ['mod+s'], description: 'Save dashboard' },
{keys: ['mod+h'], description: 'Hide row controls'}, { keys: ['mod+h'], description: 'Hide row controls' },
{keys: ['d', 'r'], description: 'Refresh all panels'}, { keys: ['d', 'r'], description: 'Refresh all panels' },
{keys: ['d', 's'], description: 'Dashboard settings'}, { keys: ['d', 's'], description: 'Dashboard settings' },
{keys: ['d', 'v'], description: 'Toggle in-active / view mode'}, { keys: ['d', 'v'], description: 'Toggle in-active / view mode' },
{keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)'}, { keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
{keys: ['d', 'E'], description: 'Expand all rows'}, { keys: ['d', 'E'], description: 'Expand all rows' },
{keys: ['d', 'C'], description: 'Collapse all rows'}, { keys: ['d', 'C'], description: 'Collapse all rows' },
{keys: ['mod+o'], description: 'Toggle shared graph crosshair'}, { keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
], ],
'Focused Panel': [ 'Focused Panel': [
{keys: ['e'], description: 'Toggle panel edit view'}, { keys: ['e'], description: 'Toggle panel edit view' },
{keys: ['v'], description: 'Toggle panel fullscreen view'}, { keys: ['v'], description: 'Toggle panel fullscreen view' },
{keys: ['p', 's'], description: 'Open Panel Share Modal'}, { keys: ['p', 's'], description: 'Open Panel Share Modal' },
{keys: ['p', 'r'], description: 'Remove Panel'}, { keys: ['p', 'r'], description: 'Remove Panel' },
], ],
'Focused Row': [ 'Focused Row': [
{keys: ['r', 'c'], description: 'Collapse Row'}, { keys: ['r', 'c'], description: 'Collapse Row' },
{keys: ['r', 'r'], description: 'Remove Row'}, { keys: ['r', 'r'], description: 'Remove Row' },
], ],
'Time Range': [ 'Time Range': [
{keys: ['t', 'z'], description: 'Zoom out time range'}, { keys: ['t', 'z'], description: 'Zoom out time range' },
{keys: ['t', '<i class="fa fa-long-arrow-left"></i>'], description: 'Move time range back'}, {
{keys: ['t', '<i class="fa fa-long-arrow-right"></i>'], description: 'Move time range forward'}, keys: ['t', '<i class="fa fa-long-arrow-left"></i>'],
description: 'Move time range back',
},
{
keys: ['t', '<i class="fa fa-long-arrow-right"></i>'],
description: 'Move time range forward',
},
], ],
}; };
} }
+10 -11
View File
@@ -26,10 +26,10 @@ export function infoPopover() {
} }
transclude(function(clone, newScope) { transclude(function(clone, newScope) {
var content = document.createElement("div"); var content = document.createElement('div');
content.className = 'markdown-html'; content.className = 'markdown-html';
_.each(clone, (node) => { _.each(clone, node => {
content.appendChild(node); content.appendChild(node);
}); });
@@ -43,22 +43,21 @@ export function infoPopover() {
tetherOptions: { tetherOptions: {
offset: offset, offset: offset,
constraints: [ constraints: [
{ {
to: 'window', to: 'window',
attachment: 'together', attachment: 'together',
pin: true pin: true,
} },
], ],
} },
}); });
var unbind = scope.$on('$destroy', function() { var unbind = scope.$on('$destroy', function() {
drop.destroy(); drop.destroy();
unbind(); unbind();
}); });
}); });
} },
}; };
} }
@@ -5,7 +5,7 @@
* Escapes `"` charachters from string * Escapes `"` charachters from string
*/ */
function escapeString(str: string): string { function escapeString(str: string): string {
return str.replace('"', '\"'); return str.replace('"', '"');
} }
/* /*
@@ -13,7 +13,7 @@ function escapeString(str: string): string {
*/ */
export function isObject(value: any): boolean { export function isObject(value: any): boolean {
var type = typeof value; var type = typeof value;
return !!value && (type === 'object'); return !!value && type === 'object';
} }
/* /*
@@ -29,11 +29,11 @@ export function getObjectName(object: Object): string {
return 'Object'; return 'Object';
} }
if (typeof object === 'object' && !object.constructor) { if (typeof object === 'object' && !object.constructor) {
return 'Object'; return 'Object';
} }
const funcNameRegex = /function ([^(]*)/; const funcNameRegex = /function ([^(]*)/;
const results = (funcNameRegex).exec((object).constructor.toString()); const results = funcNameRegex.exec(object.constructor.toString());
if (results && results.length > 1) { if (results && results.length > 1) {
return results[1]; return results[1];
} else { } else {
@@ -45,27 +45,33 @@ export function getObjectName(object: Object): string {
* Gets type of an object. Returns "null" for null objects * Gets type of an object. Returns "null" for null objects
*/ */
export function getType(object: Object): string { export function getType(object: Object): string {
if (object === null) { return 'null'; } if (object === null) {
return 'null';
}
return typeof object; return typeof object;
} }
/* /*
* Generates inline preview for a JavaScript object based on a value * Generates inline preview for a JavaScript object based on a value
*/ */
export function getValuePreview (object: Object, value: string): string { export function getValuePreview(object: Object, value: string): string {
var type = getType(object); var type = getType(object);
if (type === 'null' || type === 'undefined') { return type; } if (type === 'null' || type === 'undefined') {
return type;
}
if (type === 'string') { if (type === 'string') {
value = '"' + escapeString(value) + '"'; value = '"' + escapeString(value) + '"';
} }
if (type === 'function') { if (type === 'function') {
// Remove content of the function // Remove content of the function
return object.toString() return (
object
.toString()
.replace(/[\r\n]/g, '') .replace(/[\r\n]/g, '')
.replace(/\{.*\}/, '') + '{…}'; .replace(/\{.*\}/, '') + '{…}'
);
} }
return value; return value;
} }
@@ -97,7 +103,11 @@ export function cssClass(className: string): string {
* Creates a new DOM element wiht given type and class * Creates a new DOM element wiht given type and class
* TODO: move me to helpers * TODO: move me to helpers
*/ */
export function createElement(type: string, className?: string, content?: Element|string): Element { export function createElement(
type: string,
className?: string,
content?: Element | string
): Element {
const el = document.createElement(type); const el = document.createElement(type);
if (className) { if (className) {
el.classList.add(cssClass(className)); el.classList.add(cssClass(className));
@@ -7,7 +7,7 @@ import {
getType, getType,
getValuePreview, getValuePreview,
cssClass, cssClass,
createElement createElement,
} from './helpers'; } from './helpers';
import _ from 'lodash'; import _ from 'lodash';
@@ -19,7 +19,12 @@ const JSON_DATE_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
// When toggleing, don't animated removal or addition of more than a few items // When toggleing, don't animated removal or addition of more than a few items
const MAX_ANIMATED_TOGGLE_ITEMS = 10; const MAX_ANIMATED_TOGGLE_ITEMS = 10;
const requestAnimationFrame = window.requestAnimationFrame || function(cb: ()=>void) { cb(); return 0; }; const requestAnimationFrame =
window.requestAnimationFrame ||
function(cb: () => void) {
cb();
return 0;
};
export interface JsonExplorerConfig { export interface JsonExplorerConfig {
animateOpen?: boolean; animateOpen?: boolean;
@@ -30,18 +35,16 @@ export interface JsonExplorerConfig {
const _defaultConfig: JsonExplorerConfig = { const _defaultConfig: JsonExplorerConfig = {
animateOpen: true, animateOpen: true,
animateClose: true, animateClose: true,
theme: null theme: null,
}; };
/** /**
* @class JsonExplorer * @class JsonExplorer
* *
* JsonExplorer allows you to render JSON objects in HTML with a * JsonExplorer allows you to render JSON objects in HTML with a
* **collapsible** navigation. * **collapsible** navigation.
*/ */
export class JsonExplorer { export class JsonExplorer {
// Hold the open state after the toggler is used // Hold the open state after the toggler is used
private _isOpen: boolean = null; private _isOpen: boolean = null;
@@ -77,9 +80,13 @@ export class JsonExplorer {
* *
* @param {string} [key=undefined] The key that this object in it's parent * @param {string} [key=undefined] The key that this object in it's parent
* context * context
*/ */
constructor(public json: any, private open = 1, private config: JsonExplorerConfig = _defaultConfig, private key?: string) { constructor(
} public json: any,
private open = 1,
private config: JsonExplorerConfig = _defaultConfig,
private key?: string
) {}
/* /*
* is formatter open? * is formatter open?
@@ -103,17 +110,19 @@ export class JsonExplorer {
* is this a date string? * is this a date string?
*/ */
private get isDate(): boolean { private get isDate(): boolean {
return (this.type === 'string') && return (
this.type === 'string' &&
(DATE_STRING_REGEX.test(this.json) || (DATE_STRING_REGEX.test(this.json) ||
JSON_DATE_REGEX.test(this.json) || JSON_DATE_REGEX.test(this.json) ||
PARTIAL_DATE_REGEX.test(this.json)); PARTIAL_DATE_REGEX.test(this.json))
);
} }
/* /*
* is this a URL string? * is this a URL string?
*/ */
private get isUrl(): boolean { private get isUrl(): boolean {
return this.type === 'string' && (this.json.indexOf('http') === 0); return this.type === 'string' && this.json.indexOf('http') === 0;
} }
/* /*
@@ -142,7 +151,9 @@ export class JsonExplorer {
* is this an empty object or array? * is this an empty object or array?
*/ */
private get isEmpty(): boolean { private get isEmpty(): boolean {
return this.isEmptyObject || (this.keys && !this.keys.length && this.isArray); return (
this.isEmptyObject || (this.keys && !this.keys.length && this.isArray)
);
} }
/* /*
@@ -174,7 +185,7 @@ export class JsonExplorer {
*/ */
private get keys(): string[] { private get keys(): string[] {
if (this.isObject) { if (this.isObject) {
return Object.keys(this.json).map((key)=> key ? key : '""'); return Object.keys(this.json).map(key => (key ? key : '""'));
} else { } else {
return []; return [];
} }
@@ -183,7 +194,7 @@ export class JsonExplorer {
/** /**
* Toggles `isOpen` state * Toggles `isOpen` state
* *
*/ */
toggleOpen() { toggleOpen() {
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
@@ -198,17 +209,17 @@ export class JsonExplorer {
} }
/** /**
* Open all children up to a certain depth. * Open all children up to a certain depth.
* Allows actions such as expand all/collapse all * Allows actions such as expand all/collapse all
* *
*/ */
openAtDepth(depth = 1) { openAtDepth(depth = 1) {
if (depth < 0) { if (depth < 0) {
return; return;
} }
this.open = depth; this.open = depth;
this.isOpen = (depth !== 0); this.isOpen = depth !== 0;
if (this.element) { if (this.element) {
this.removeChildren(false); this.removeChildren(false);
@@ -223,8 +234,11 @@ export class JsonExplorer {
} }
isNumberArray() { isNumberArray() {
return (this.json.length > 0 && this.json.length < 4) && return (
(_.isNumber(this.json[0]) || _.isNumber(this.json[1])); this.json.length > 0 &&
this.json.length < 4 &&
(_.isNumber(this.json[0]) || _.isNumber(this.json[1]))
);
} }
renderArray() { renderArray() {
@@ -235,13 +249,17 @@ export class JsonExplorer {
if (this.isNumberArray()) { if (this.isNumberArray()) {
this.json.forEach((val, index) => { this.json.forEach((val, index) => {
if (index > 0) { if (index > 0) {
arrayWrapperSpan.appendChild(createElement('span', 'array-comma', ',')); arrayWrapperSpan.appendChild(
createElement('span', 'array-comma', ',')
);
} }
arrayWrapperSpan.appendChild(createElement('span', 'number', val)); arrayWrapperSpan.appendChild(createElement('span', 'number', val));
}); });
this.skipChildren = true; this.skipChildren = true;
} else { } else {
arrayWrapperSpan.appendChild(createElement('span', 'number', (this.json.length))); arrayWrapperSpan.appendChild(
createElement('span', 'number', this.json.length)
);
} }
arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']')); arrayWrapperSpan.appendChild(createElement('span', 'bracket', ']'));
@@ -280,7 +298,11 @@ export class JsonExplorer {
const objectWrapperSpan = createElement('span'); const objectWrapperSpan = createElement('span');
// get constructor name and append it to wrapper span // get constructor name and append it to wrapper span
var constructorName = createElement('span', 'constructor-name', this.constructorName); var constructorName = createElement(
'span',
'constructor-name',
this.constructorName
);
objectWrapperSpan.appendChild(constructorName); objectWrapperSpan.appendChild(constructorName);
// if it's an array append the array specific elements like brackets and length // if it's an array append the array specific elements like brackets and length
@@ -294,7 +316,6 @@ export class JsonExplorer {
togglerLink.appendChild(value); togglerLink.appendChild(value);
// Primitive values // Primitive values
} else { } else {
// make a value holder element // make a value holder element
const value = this.isUrl ? createElement('a') : createElement('span'); const value = this.isUrl ? createElement('a') : createElement('span');
@@ -366,17 +387,24 @@ export class JsonExplorer {
/** /**
* Appends all the children to children element * Appends all the children to children element
* Animated option is used when user triggers this via a click * Animated option is used when user triggers this via a click
*/ */
appendChildren(animated = false) { appendChildren(animated = false) {
const children = this.element.querySelector(`div.${cssClass('children')}`); const children = this.element.querySelector(`div.${cssClass('children')}`);
if (!children || this.isEmpty) { return; } if (!children || this.isEmpty) {
return;
}
if (animated) { if (animated) {
let index = 0; let index = 0;
const addAChild = ()=> { const addAChild = () => {
const key = this.keys[index]; const key = this.keys[index];
const formatter = new JsonExplorer(this.json[key], this.open - 1, this.config, key); const formatter = new JsonExplorer(
this.json[key],
this.open - 1,
this.config,
key
);
children.appendChild(formatter.render()); children.appendChild(formatter.render());
index += 1; index += 1;
@@ -391,10 +419,14 @@ export class JsonExplorer {
}; };
requestAnimationFrame(addAChild); requestAnimationFrame(addAChild);
} else { } else {
this.keys.forEach(key => { this.keys.forEach(key => {
const formatter = new JsonExplorer(this.json[key], this.open - 1, this.config, key); const formatter = new JsonExplorer(
this.json[key],
this.open - 1,
this.config,
key
);
children.appendChild(formatter.render()); children.appendChild(formatter.render());
}); });
} }
@@ -403,13 +435,15 @@ export class JsonExplorer {
/** /**
* Removes all the children from children element * Removes all the children from children element
* Animated option is used when user triggers this via a click * Animated option is used when user triggers this via a click
*/ */
removeChildren(animated = false) { removeChildren(animated = false) {
const childrenElement = this.element.querySelector(`div.${cssClass('children')}`) as HTMLDivElement; const childrenElement = this.element.querySelector(
`div.${cssClass('children')}`
) as HTMLDivElement;
if (animated) { if (animated) {
let childrenRemoved = 0; let childrenRemoved = 0;
const removeAChild = ()=> { const removeAChild = () => {
if (childrenElement && childrenElement.children.length) { if (childrenElement && childrenElement.children.length) {
childrenElement.removeChild(childrenElement.children[0]); childrenElement.removeChild(childrenElement.children[0]);
childrenRemoved += 1; childrenRemoved += 1;
+20 -19
View File
@@ -1,22 +1,23 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import {JsonExplorer} from '../json_explorer/json_explorer'; import { JsonExplorer } from '../json_explorer/json_explorer';
coreModule.directive('jsonTree', [function jsonTreeDirective() { coreModule.directive('jsonTree', [
return{ function jsonTreeDirective() {
restrict: 'E', return {
scope: { restrict: 'E',
object: '=', scope: {
startExpanded: '@', object: '=',
rootName: '@', startExpanded: '@',
}, rootName: '@',
link: function(scope, elem) { },
link: function(scope, elem) {
var jsonExp = new JsonExplorer(scope.object, 3, {
animateOpen: true,
});
var jsonExp = new JsonExplorer(scope.object, 3, { const html = jsonExp.render(true);
animateOpen: true elem.html(html);
}); },
};
const html = jsonExp.render(true); },
elem.html(html); ]);
}
};
}]);
@@ -31,7 +31,6 @@ export class LayoutSelectorCtrl {
store.set('grafana.list.layout.mode', 'grid'); store.set('grafana.list.layout.mode', 'grid');
this.$rootScope.appEvent('layout-mode-changed', 'grid'); this.$rootScope.appEvent('layout-mode-changed', 'grid');
} }
} }
/** @ngInject **/ /** @ngInject **/
@@ -56,12 +55,16 @@ export function layoutMode($rootScope) {
var className = 'card-list-layout-' + layout; var className = 'card-list-layout-' + layout;
elem.addClass(className); elem.addClass(className);
$rootScope.onAppEvent('layout-mode-changed', (evt, newLayout) => { $rootScope.onAppEvent(
elem.removeClass(className); 'layout-mode-changed',
className = 'card-list-layout-' + newLayout; (evt, newLayout) => {
elem.addClass(className); elem.removeClass(className);
}, scope); className = 'card-list-layout-' + newLayout;
} elem.addClass(className);
},
scope
);
},
}; };
} }
@@ -60,20 +60,24 @@
switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox" switch-class="gf-form-switch--transparent gf-form-switch--search-result-filter-row__checkbox"
/> />
<div class="search-results-filter-row__filters"> <div class="search-results-filter-row__filters">
<select <div class="gf-form-select-wrapper">
class="search-results-filter-row__filters-item gf-form-input" <select
ng-model="ctrl.selectedStarredFilter" class="search-results-filter-row__filters-item gf-form-input"
ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions" ng-model="ctrl.selectedStarredFilter"
ng-change="ctrl.onStarredFilterChange()" ng-options="t.text disable when t.disabled for t in ctrl.starredFilterOptions"
ng-show="!(ctrl.canMove || ctrl.canDelete)" ng-change="ctrl.onStarredFilterChange()"
/> ng-show="!(ctrl.canMove || ctrl.canDelete)"
<select />
class="search-results-filter-row__filters-item gf-form-input" </div>
ng-model="ctrl.selectedTagFilter" <div class="gf-form-select-wrapper">
ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions" <select
ng-change="ctrl.onTagFilterChange()" class="search-results-filter-row__filters-item gf-form-input"
ng-show="!(ctrl.canMove || ctrl.canDelete)" ng-model="ctrl.selectedTagFilter"
/> ng-options="t.term disable when t.disabled for t in ctrl.tagFilterOptions"
ng-change="ctrl.onTagFilterChange()"
ng-show="!(ctrl.canMove || ctrl.canDelete)"
/>
</div>
<div class="gf-form-button-row" ng-show="ctrl.canMove || ctrl.canDelete"> <div class="gf-form-button-row" ng-show="ctrl.canMove || ctrl.canDelete">
<button type="button" <button type="button"
class="btn gf-form-button btn-inverse" class="btn gf-form-button btn-inverse"
@@ -13,13 +13,24 @@ export class ManageDashboardsCtrl {
canMove = false; canMove = false;
hasFilters = false; hasFilters = false;
selectAllChecked = false; selectAllChecked = false;
starredFilterOptions = [{ text: 'Filter by Starred', disabled: true }, { text: 'Yes' }, { text: 'No' }]; starredFilterOptions = [
{ text: 'Filter by Starred', disabled: true },
{ text: 'Yes' },
{ text: 'No' },
];
selectedStarredFilter: any; selectedStarredFilter: any;
folderId?: number; folderId?: number;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, navModelSrv, private searchSrv: SearchSrv) { constructor(private backendSrv, navModelSrv, private searchSrv: SearchSrv) {
this.query = { query: '', mode: 'tree', tag: [], starred: false, skipRecent: true, skipStarred: true }; this.query = {
query: '',
mode: 'tree',
tag: [],
starred: false,
skipRecent: true,
skipStarred: true,
};
if (this.folderId) { if (this.folderId) {
this.query.folderIds = [this.folderId]; this.query.folderIds = [this.folderId];
@@ -33,7 +44,7 @@ export class ManageDashboardsCtrl {
} }
getDashboards() { getDashboards() {
return this.searchSrv.search(this.query).then((result) => { return this.searchSrv.search(this.query).then(result => {
return this.initDashboardList(result); return this.initDashboardList(result);
}); });
} }
@@ -42,7 +53,10 @@ export class ManageDashboardsCtrl {
this.canMove = false; this.canMove = false;
this.canDelete = false; this.canDelete = false;
this.selectAllChecked = false; this.selectAllChecked = false;
this.hasFilters = this.query.query.length > 0 || this.query.tag.length > 0 || this.query.starred; this.hasFilters =
this.query.query.length > 0 ||
this.query.tag.length > 0 ||
this.query.starred;
if (!result) { if (!result) {
this.sections = []; this.sections = [];
@@ -79,7 +93,7 @@ export class ManageDashboardsCtrl {
getFoldersAndDashboardsToDelete() { getFoldersAndDashboardsToDelete() {
let selectedDashboards = { let selectedDashboards = {
folders: [], folders: [],
dashboards: [] dashboards: [],
}; };
for (const section of this.sections) { for (const section of this.sections) {
@@ -112,10 +126,16 @@ export class ManageDashboardsCtrl {
let text2; let text2;
if (folderCount > 0 && dashCount > 0) { if (folderCount > 0 && dashCount > 0) {
text += `selected folder${folderCount === 1 ? '' : 's'} and dashboard${dashCount === 1 ? '' : 's'}?`; text += `selected folder${folderCount === 1 ? '' : 's'} and dashboard${
text2 = `All dashboards of the selected folder${folderCount === 1 ? '' : 's'} will also be deleted`; dashCount === 1 ? '' : 's'
}?`;
text2 = `All dashboards of the selected folder${
folderCount === 1 ? '' : 's'
} will also be deleted`;
} else if (folderCount > 0) { } else if (folderCount > 0) {
text += `selected folder${folderCount === 1 ? '' : 's'} and all its dashboards?`; text += `selected folder${
folderCount === 1 ? '' : 's'
} and all its dashboards?`;
} else { } else {
text += `selected dashboard${dashCount === 1 ? '' : 's'}?`; text += `selected dashboard${dashCount === 1 ? '' : 's'}?`;
} }
@@ -129,7 +149,7 @@ export class ManageDashboardsCtrl {
onConfirm: () => { onConfirm: () => {
const foldersAndDashboards = data.folders.concat(data.dashboards); const foldersAndDashboards = data.folders.concat(data.dashboards);
this.deleteFoldersAndDashboards(foldersAndDashboards); this.deleteFoldersAndDashboards(foldersAndDashboards);
} },
}); });
} }
@@ -145,16 +165,22 @@ export class ManageDashboardsCtrl {
let msg; let msg;
if (folderCount > 0 && dashCount > 0) { if (folderCount > 0 && dashCount > 0) {
header = `Folder${folderCount === 1 ? '' : 's'} And Dashboard${dashCount === 1 ? '' : 's'} Deleted`; header = `Folder${folderCount === 1 ? '' : 's'} And Dashboard${
dashCount === 1 ? '' : 's'
} Deleted`;
msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} `; msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} `;
msg += `and ${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`; msg += `and ${dashCount} dashboard${
dashCount === 1 ? '' : 's'
} has been deleted`;
} else if (folderCount > 0) { } else if (folderCount > 0) {
header = `Folder${folderCount === 1 ? '' : 's'} Deleted`; header = `Folder${folderCount === 1 ? '' : 's'} Deleted`;
if (folderCount === 1) { if (folderCount === 1) {
msg = `${folders[0].dashboard.title} has been deleted`; msg = `${folders[0].dashboard.title} has been deleted`;
} else { } else {
msg = `${folderCount} folder${folderCount === 1 ? '' : 's'} has been deleted`; msg = `${folderCount} folder${
folderCount === 1 ? '' : 's'
} has been deleted`;
} }
} else if (dashCount > 0) { } else if (dashCount > 0) {
header = `Dashboard${dashCount === 1 ? '' : 's'} Deleted`; header = `Dashboard${dashCount === 1 ? '' : 's'} Deleted`;
@@ -162,7 +188,9 @@ export class ManageDashboardsCtrl {
if (dashCount === 1) { if (dashCount === 1) {
msg = `${dashboards[0].dashboard.title} has been deleted`; msg = `${dashboards[0].dashboard.title} has been deleted`;
} else { } else {
msg = `${dashCount} dashboard${dashCount === 1 ? '' : 's'} has been deleted`; msg = `${dashCount} dashboard${
dashCount === 1 ? '' : 's'
} has been deleted`;
} }
} }
@@ -187,19 +215,25 @@ export class ManageDashboardsCtrl {
moveTo() { moveTo() {
const selectedDashboards = this.getDashboardsToMove(); const selectedDashboards = this.getDashboardsToMove();
const template = '<move-to-folder-modal dismiss="dismiss()" ' + const template =
'<move-to-folder-modal dismiss="dismiss()" ' +
'dashboards="model.dashboards" after-save="model.afterSave()">' + 'dashboards="model.dashboards" after-save="model.afterSave()">' +
'</move-to-folder-modal>`'; '</move-to-folder-modal>`';
appEvents.emit('show-modal', { appEvents.emit('show-modal', {
templateHtml: template, templateHtml: template,
modalClass: 'modal--narrow', modalClass: 'modal--narrow',
model: { dashboards: selectedDashboards, afterSave: this.getDashboards.bind(this) } model: {
dashboards: selectedDashboards,
afterSave: this.getDashboards.bind(this),
},
}); });
} }
getTags() { getTags() {
return this.searchSrv.getDashboardTags().then((results) => { return this.searchSrv.getDashboardTags().then(results => {
this.tagFilterOptions = [{ term: 'Filter By Tag', disabled: true }].concat(results); this.tagFilterOptions = [
{ term: 'Filter By Tag', disabled: true },
].concat(results);
this.selectedTagFilter = this.tagFilterOptions[0]; this.selectedTagFilter = this.tagFilterOptions[0];
}); });
} }
@@ -248,7 +282,7 @@ export class ManageDashboardsCtrl {
section.checked = this.selectAllChecked; section.checked = this.selectAllChecked;
} }
section.items = _.map(section.items, (item) => { section.items = _.map(section.items, item => {
item.checked = this.selectAllChecked; item.checked = this.selectAllChecked;
return item; return item;
}); });
@@ -268,13 +302,14 @@ export class ManageDashboardsCtrl {
export function manageDashboardsDirective() { export function manageDashboardsDirective() {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: 'public/app/core/components/manage_dashboards/manage_dashboards.html', templateUrl:
'public/app/core/components/manage_dashboards/manage_dashboards.html',
controller: ManageDashboardsCtrl, controller: ManageDashboardsCtrl,
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: { scope: {
folderId: '=' folderId: '=',
} },
}; };
} }
+8 -10
View File
@@ -1,15 +1,15 @@
import coreModule from '../../core_module'; import coreModule from '../../core_module';
import {NavModel} from '../../nav_model_srv'; import { NavModel } from '../../nav_model_srv';
import appEvents from 'app/core/app_events';
export class NavbarCtrl { export class NavbarCtrl {
model: NavModel; model: NavModel;
/** @ngInject */ /** @ngInject */
constructor(private $rootScope) { constructor() {}
}
showSearch() { showSearch() {
this.$rootScope.appEvent('show-dash-search'); appEvents.emit('show-dash-search');
} }
navItemClicked(navItem, evt) { navItemClicked(navItem, evt) {
@@ -28,10 +28,9 @@ export function navbarDirective() {
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: { scope: {
model: "=", model: '=',
}, },
link: function(scope, elem) { link: function(scope, elem) {},
}
}; };
} }
@@ -46,11 +45,10 @@ export function pageH1() {
</h1> </h1>
`, `,
scope: { scope: {
model: "=", model: '=',
} },
}; };
} }
coreModule.directive('pageH1', pageH1); coreModule.directive('pageH1', pageH1);
coreModule.directive('navbar', navbarDirective); coreModule.directive('navbar', navbarDirective);
+5 -3
View File
@@ -1,7 +1,7 @@
///<reference path="../../headers/common.d.ts" /> ///<reference path="../../headers/common.d.ts" />
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import {contextSrv} from 'app/core/services/context_srv'; import { contextSrv } from 'app/core/services/context_srv';
const template = ` const template = `
<div class="modal-body"> <div class="modal-body">
@@ -63,7 +63,9 @@ export class OrgSwitchCtrl {
setUsingOrg(org) { setUsingOrg(org) {
return this.backendSrv.post('/api/user/using/' + org.orgId).then(() => { return this.backendSrv.post('/api/user/using/' + org.orgId).then(() => {
const re = /orgId=\d+/gi; const re = /orgId=\d+/gi;
this.setWindowLocationHref(this.getWindowLocationHref().replace(re, 'orgId=' + org.orgId)); this.setWindowLocationHref(
this.getWindowLocationHref().replace(re, 'orgId=' + org.orgId)
);
}); });
} }
@@ -83,7 +85,7 @@ export function orgSwitcher() {
controller: OrgSwitchCtrl, controller: OrgSwitchCtrl,
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: {dismiss: "&"}, scope: { dismiss: '&' },
}; };
} }
@@ -30,7 +30,7 @@ export class QueryPart {
this.part = part; this.part = part;
this.def = def; this.def = def;
if (!this.def) { if (!this.def) {
throw {message: 'Could not find query part ' + part.type}; throw { message: 'Could not find query part ' + part.type };
} }
part.params = part.params || _.clone(this.def.defaultParams); part.params = part.params || _.clone(this.def.defaultParams);
@@ -42,7 +42,7 @@ export class QueryPart {
return this.def.renderer(this, innerExpr); return this.def.renderer(this, innerExpr);
} }
hasMultipleParamsInString (strValue, index) { hasMultipleParamsInString(strValue, index) {
if (strValue.indexOf(',') === -1) { if (strValue.indexOf(',') === -1) {
return false; return false;
} }
@@ -50,7 +50,7 @@ export class QueryPart {
return this.def.params[index + 1] && this.def.params[index + 1].optional; return this.def.params[index + 1] && this.def.params[index + 1].optional;
} }
updateParam (strValue, index) { updateParam(strValue, index) {
// handle optional parameters // handle optional parameters
// if string contains ',' and next param is optional, split and update both // if string contains ',' and next param is optional, split and update both
if (this.hasMultipleParamsInString(strValue, index)) { if (this.hasMultipleParamsInString(strValue, index)) {
@@ -107,7 +107,6 @@ export function functionRenderer(part, innerExpr) {
return str + parameters.join(', ') + ')'; return str + parameters.join(', ') + ')';
} }
export function suffixRenderer(part, innerExpr) { export function suffixRenderer(part, innerExpr) {
return innerExpr + ' ' + part.params[0]; return innerExpr + ' ' + part.params[0];
} }
@@ -15,17 +15,17 @@ var template = `
</ul> </ul>
`; `;
/** @ngInject */ /** @ngInject */
export function queryPartEditorDirective($compile, templateSrv) { export function queryPartEditorDirective($compile, templateSrv) {
var paramTemplate =
var paramTemplate = '<input type="text" class="hide input-mini tight-form-func-param"></input>'; '<input type="text" class="hide input-mini tight-form-func-param"></input>';
return { return {
restrict: 'E', restrict: 'E',
template: template, template: template,
scope: { scope: {
part: "=", part: '=',
handleEvent: "&", handleEvent: '&',
}, },
link: function postLink($scope, elem) { link: function postLink($scope, elem) {
var part = $scope.part; var part = $scope.part;
@@ -40,7 +40,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
var $input = $link.next(); var $input = $link.next();
$input.val(part.params[paramIndex]); $input.val(part.params[paramIndex]);
$input.css('width', ($link.width() + 16) + 'px'); $input.css('width', $link.width() + 16 + 'px');
$link.hide(); $link.hide();
$input.show(); $input.show();
@@ -65,7 +65,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
part.updateParam($input.val(), paramIndex); part.updateParam($input.val(), paramIndex);
$scope.$apply(() => { $scope.$apply(() => {
$scope.handleEvent({$event: {name: 'part-param-changed'}}); $scope.handleEvent({ $event: { name: 'part-param-changed' } });
}); });
} }
@@ -90,20 +90,26 @@ export function queryPartEditorDirective($compile, templateSrv) {
return; return;
} }
var typeaheadSource = function (query, callback) { var typeaheadSource = function(query, callback) {
if (param.options) { if (param.options) {
var options = param.options; var options = param.options;
if (param.type === 'int') { if (param.type === 'int') {
options = _.map(options, function(val) { return val.toString(); }); options = _.map(options, function(val) {
return val.toString();
});
} }
return options; return options;
} }
$scope.$apply(function() { $scope.$apply(function() {
$scope.handleEvent({$event: {name: 'get-param-options'}}).then(function(result) { $scope
var dynamicOptions = _.map(result, function(op) { return op.value; }); .handleEvent({ $event: { name: 'get-param-options' } })
callback(dynamicOptions); .then(function(result) {
}); var dynamicOptions = _.map(result, function(op) {
return op.value;
});
callback(dynamicOptions);
});
}); });
}; };
@@ -113,16 +119,16 @@ export function queryPartEditorDirective($compile, templateSrv) {
source: typeaheadSource, source: typeaheadSource,
minLength: 0, minLength: 0,
items: 1000, items: 1000,
updater: function (value) { updater: function(value) {
setTimeout(function() { setTimeout(function() {
inputBlur.call($input[0], paramIndex); inputBlur.call($input[0], paramIndex);
}, 0); }, 0);
return value; return value;
} },
}); });
var typeahead = $input.data('typeahead'); var typeahead = $input.data('typeahead');
typeahead.lookup = function () { typeahead.lookup = function() {
this.query = this.$element.val() || ''; this.query = this.$element.val() || '';
var items = this.source(this.query, $.proxy(this.process, this)); var items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items; return items ? this.process(items) : items;
@@ -130,13 +136,15 @@ export function queryPartEditorDirective($compile, templateSrv) {
} }
$scope.showActionsMenu = function() { $scope.showActionsMenu = function() {
$scope.handleEvent({$event: {name: 'get-part-actions'}}).then(res => { $scope
$scope.partActions = res; .handleEvent({ $event: { name: 'get-part-actions' } })
}); .then(res => {
$scope.partActions = res;
});
}; };
$scope.triggerPartAction = function(action) { $scope.triggerPartAction = function(action) {
$scope.handleEvent({$event: {name: 'action', action: action}}); $scope.handleEvent({ $event: { name: 'action', action: action } });
}; };
function addElementsAndCompile() { function addElementsAndCompile() {
@@ -149,8 +157,12 @@ export function queryPartEditorDirective($compile, templateSrv) {
$('<span>, </span>').appendTo($paramsContainer); $('<span>, </span>').appendTo($paramsContainer);
} }
var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); var paramValue = templateSrv.highlightVariablesAsHtml(
var $paramLink = $('<a class="graphite-func-param-link pointer">' + paramValue + '</a>'); part.params[index]
);
var $paramLink = $(
'<a class="graphite-func-param-link pointer">' + paramValue + '</a>'
);
var $input = $(paramTemplate); var $input = $(paramTemplate);
$paramLink.appendTo($paramsContainer); $paramLink.appendTo($paramsContainer);
@@ -171,7 +183,7 @@ export function queryPartEditorDirective($compile, templateSrv) {
} }
relink(); relink();
} },
}; };
} }
+1 -3
View File
@@ -5,7 +5,6 @@ export function geminiScrollbar() {
return { return {
restrict: 'A', restrict: 'A',
link: function(scope, elem, attrs) { link: function(scope, elem, attrs) {
let scrollbar = new PerfectScrollbar(elem[0]); let scrollbar = new PerfectScrollbar(elem[0]);
scope.$on('$routeChangeSuccess', () => { scope.$on('$routeChangeSuccess', () => {
@@ -19,8 +18,7 @@ export function geminiScrollbar() {
scope.$on('$destroy', () => { scope.$on('$destroy', () => {
scrollbar.destroy(); scrollbar.destroy();
}); });
},
}
}; };
} }
@@ -57,11 +57,11 @@
<div class="search-filter-box"> <div class="search-filter-box">
<a href="dashboard/new" class="search-filter-box-link"> <a href="dashboard/new" class="search-filter-box-link">
<i class="gicon gicon-dashboard-new"></i> <i class="gicon gicon-dashboard-new"></i>
Dashboard New dashboard
</a> </a>
<a href="dashboards/folder/new" class="search-filter-box-link"> <a href="dashboards/folder/new" class="search-filter-box-link">
<i class="gicon gicon-folder-new"></i> <i class="gicon gicon-folder-new"></i>
Folder New folder
</a> </a>
<a class="search-filter-box-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search"> <a class="search-filter-box-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
<img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com <img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com
+42 -20
View File
@@ -1,6 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../../core_module'; import coreModule from '../../core_module';
import { SearchSrv } from 'app/core/services/search_srv'; import { SearchSrv } from 'app/core/services/search_srv';
import appEvents from 'app/core/app_events';
export class SearchCtrl { export class SearchCtrl {
isOpen: boolean; isOpen: boolean;
@@ -16,11 +17,16 @@ export class SearchCtrl {
initialFolderFilterTitle: string; initialFolderFilterTitle: string;
/** @ngInject */ /** @ngInject */
constructor($scope, private $location, private $timeout, private searchSrv: SearchSrv, $rootScope) { constructor(
$rootScope.onAppEvent('show-dash-search', this.openSearch.bind(this), $scope); $scope,
$rootScope.onAppEvent('hide-dash-search', this.closeSearch.bind(this), $scope); private $location,
private $timeout,
private searchSrv: SearchSrv
) {
appEvents.on('show-dash-search', this.openSearch.bind(this), $scope);
appEvents.on('hide-dash-search', this.closeSearch.bind(this), $scope);
this.initialFolderFilterTitle = "All"; this.initialFolderFilterTitle = 'All';
} }
closeSearch() { closeSearch() {
@@ -69,11 +75,14 @@ export class SearchCtrl {
if (currentItem) { if (currentItem) {
if (currentItem.dashboardIndex !== undefined) { if (currentItem.dashboardIndex !== undefined) {
const selectedDash = this.results[currentItem.folderIndex].items[currentItem.dashboardIndex]; const selectedDash = this.results[currentItem.folderIndex].items[
currentItem.dashboardIndex
];
if (selectedDash) { if (selectedDash) {
this.$location.search({}); this.$location.search({});
this.$location.path(selectedDash.url); this.$location.path(selectedDash.url);
this.closeSearch();
} }
} else { } else {
const selectedFolder = this.results[currentItem.folderIndex]; const selectedFolder = this.results[currentItem.folderIndex];
@@ -96,7 +105,9 @@ export class SearchCtrl {
if (currentItem) { if (currentItem) {
if (currentItem.dashboardIndex !== undefined) { if (currentItem.dashboardIndex !== undefined) {
this.results[currentItem.folderIndex].items[currentItem.dashboardIndex].selected = false; this.results[currentItem.folderIndex].items[
currentItem.dashboardIndex
].selected = false;
} else { } else {
this.results[currentItem.folderIndex].selected = false; this.results[currentItem.folderIndex].selected = false;
} }
@@ -109,10 +120,13 @@ export class SearchCtrl {
const max = flattenedResult.length; const max = flattenedResult.length;
let newIndex = this.selectedIndex + direction; let newIndex = this.selectedIndex + direction;
this.selectedIndex = ((newIndex %= max) < 0) ? newIndex + max : newIndex; this.selectedIndex = (newIndex %= max) < 0 ? newIndex + max : newIndex;
const selectedItem = flattenedResult[this.selectedIndex]; const selectedItem = flattenedResult[this.selectedIndex];
if (selectedItem.dashboardIndex === undefined && this.results[selectedItem.folderIndex].id === 0) { if (
selectedItem.dashboardIndex === undefined &&
this.results[selectedItem.folderIndex].id === 0
) {
this.moveSelection(direction); this.moveSelection(direction);
return; return;
} }
@@ -123,7 +137,9 @@ export class SearchCtrl {
return; return;
} }
this.results[selectedItem.folderIndex].items[selectedItem.dashboardIndex].selected = true; this.results[selectedItem.folderIndex].items[
selectedItem.dashboardIndex
].selected = true;
return; return;
} }
@@ -140,7 +156,9 @@ export class SearchCtrl {
var localSearchId = this.currentSearchId; var localSearchId = this.currentSearchId;
return this.searchSrv.search(this.query).then(results => { return this.searchSrv.search(this.query).then(results => {
if (localSearchId < this.currentSearchId) { return; } if (localSearchId < this.currentSearchId) {
return;
}
this.results = results || []; this.results = results || [];
this.isLoading = false; this.isLoading = false;
this.moveSelection(1); this.moveSelection(1);
@@ -149,7 +167,9 @@ export class SearchCtrl {
queryHasNoFilters() { queryHasNoFilters() {
var query = this.query; var query = this.query;
return query.query === '' && query.starred === false && query.tag.length === 0; return (
query.query === '' && query.starred === false && query.tag.length === 0
);
} }
filterByTag(tag) { filterByTag(tag) {
@@ -169,7 +189,7 @@ export class SearchCtrl {
} }
getTags() { getTags() {
return this.searchSrv.getDashboardTags().then((results) => { return this.searchSrv.getDashboardTags().then(results => {
this.results = results; this.results = results;
this.giveSearchFocus = this.giveSearchFocus + 1; this.giveSearchFocus = this.giveSearchFocus + 1;
}); });
@@ -194,21 +214,23 @@ export class SearchCtrl {
private getFlattenedResultForNavigation() { private getFlattenedResultForNavigation() {
let folderIndex = 0; let folderIndex = 0;
return _.flatMap(this.results, (s) => { return _.flatMap(this.results, s => {
let result = []; let result = [];
result.push({ result.push({
folderIndex: folderIndex folderIndex: folderIndex,
}); });
let dashboardIndex = 0; let dashboardIndex = 0;
result = result.concat(_.map(s.items || [], (i) => { result = result.concat(
return { _.map(s.items || [], i => {
folderIndex: folderIndex, return {
dashboardIndex: dashboardIndex++ folderIndex: folderIndex,
}; dashboardIndex: dashboardIndex++,
})); };
})
);
folderIndex++; folderIndex++;
return result; return result;
@@ -32,7 +32,7 @@
<span class="search-item__icon"> <span class="search-item__icon">
<i class="gicon mini gicon-dashboard-list"></i> <i class="gicon mini gicon-dashboard-list"></i>
</span> </span>
<span class="search-item__body"> <span class="search-item__body" ng-click="ctrl.onItemClick(item)">
<div class="search-item__body-title">{{::item.title}}</div> <div class="search-item__body-title">{{::item.title}}</div>
</span> </span>
<span class="search-item__tags"> <span class="search-item__tags">
@@ -1,5 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import coreModule from '../../core_module'; import coreModule from '../../core_module';
import appEvents from 'app/core/app_events';
export class SearchResultsCtrl { export class SearchResultsCtrl {
results: any; results: any;
@@ -61,9 +62,15 @@ export class SearchResultsCtrl {
} }
} }
onItemClick(item) {
if (this.$location.path().indexOf(item.url) > -1) {
appEvents.emit('hide-dash-search');
}
}
selectTag(tag, evt) { selectTag(tag, evt) {
if (this.onTagSelected) { if (this.onTagSelected) {
this.onTagSelected({$tag: tag}); this.onTagSelected({ $tag: tag });
} }
if (evt) { if (evt) {
@@ -85,7 +92,7 @@ export function searchResultsDirective() {
results: '=', results: '=',
onSelectionChanged: '&', onSelectionChanged: '&',
onTagSelected: '&', onTagSelected: '&',
onFolderExpanding: '&' onFolderExpanding: '&',
}, },
}; };
} }
@@ -13,22 +13,36 @@ export class SideMenuCtrl {
isOpenMobile: boolean; isOpenMobile: boolean;
/** @ngInject */ /** @ngInject */
constructor(private $scope, private $rootScope, private $location, private contextSrv, private $timeout) { constructor(
private $scope,
private $rootScope,
private $location,
private contextSrv,
private $timeout
) {
this.isSignedIn = contextSrv.isSignedIn; this.isSignedIn = contextSrv.isSignedIn;
this.user = contextSrv.user; this.user = contextSrv.user;
this.mainLinks = _.filter(config.bootData.navTree, item => !item.hideFromMenu); this.mainLinks = _.filter(
this.bottomNav = _.filter(config.bootData.navTree, item => item.hideFromMenu); config.bootData.navTree,
this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path()); item => !item.hideFromMenu
);
this.bottomNav = _.filter(
config.bootData.navTree,
item => item.hideFromMenu
);
this.loginUrl =
'login?redirect=' + encodeURIComponent(this.$location.path());
if (contextSrv.user.orgCount > 1) { if (contextSrv.user.orgCount > 1) {
let profileNode = _.find(this.bottomNav, {id: 'profile'}); let profileNode = _.find(this.bottomNav, { id: 'profile' });
if (profileNode) { if (profileNode) {
profileNode.showOrgSwitcher = true; profileNode.showOrgSwitcher = true;
} }
} }
this.$scope.$on('$routeChangeSuccess', () => { this.$scope.$on('$routeChangeSuccess', () => {
this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path()); this.loginUrl =
'login?redirect=' + encodeURIComponent(this.$location.path());
}); });
} }
@@ -53,7 +67,9 @@ export class SideMenuCtrl {
itemClicked(item, evt) { itemClicked(item, evt) {
if (item.url === '/shortcuts') { if (item.url === '/shortcuts') {
appEvents.emit('show-modal', {templateHtml: '<help-modal></help-modal>'}); appEvents.emit('show-modal', {
templateHtml: '<help-modal></help-modal>',
});
evt.preventDefault(); evt.preventDefault();
} }
} }
@@ -78,7 +94,7 @@ export function sideMenuDirective() {
parent.append(menu); parent.append(menu);
}, 100); }, 100);
}); });
} },
}; };
} }
+6 -7
View File
@@ -33,7 +33,6 @@ export class SwitchCtrl {
return this.onChange(); return this.onChange();
}); });
} }
} }
export function switchDirective() { export function switchDirective() {
@@ -43,12 +42,12 @@ export function switchDirective() {
controllerAs: 'ctrl', controllerAs: 'ctrl',
bindToController: true, bindToController: true,
scope: { scope: {
checked: "=", checked: '=',
label: "@", label: '@',
labelClass: "@", labelClass: '@',
tooltip: "@", tooltip: '@',
switchClass: "@", switchClass: '@',
onChange: "&", onChange: '&',
}, },
template: template, template: template,
}; };
+17 -10
View File
@@ -17,24 +17,31 @@ export class TeamPickerCtrl {
/** @ngInject */ /** @ngInject */
constructor(private backendSrv) { constructor(private backendSrv) {
this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {'leading': true, 'trailing': false}); this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {
leading: true,
trailing: false,
});
this.reset(); this.reset();
} }
reset() { reset() {
this.group = {text: 'Choose', value: null}; this.group = { text: 'Choose', value: null };
} }
searchGroups(query: string) { searchGroups(query: string) {
return Promise.resolve(this.backendSrv.get('/api/teams/search?perpage=10&page=1&query=' + query).then(result => { return Promise.resolve(
return _.map(result.teams, ug => { this.backendSrv
return {text: ug.name, value: ug}; .get('/api/teams/search?perpage=10&page=1&query=' + query)
}); .then(result => {
})); return _.map(result.teams, ug => {
return { text: ug.name, value: ug };
});
})
);
} }
onChange(option) { onChange(option) {
this.teamPicked({$group: option.value}); this.teamPicked({ $group: option.value });
} }
} }
@@ -49,10 +56,10 @@ export function teamPicker() {
teamPicked: '&', teamPicked: '&',
}, },
link: function(scope, elem, attrs, ctrl) { link: function(scope, elem, attrs, ctrl) {
scope.$on("team-picker-reset", () => { scope.$on('team-picker-reset', () => {
ctrl.reset(); ctrl.reset();
}); });
} },
}; };
} }
+17 -10
View File
@@ -18,23 +18,30 @@ export class UserPickerCtrl {
/** @ngInject */ /** @ngInject */
constructor(private backendSrv) { constructor(private backendSrv) {
this.reset(); this.reset();
this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {'leading': true, 'trailing': false}); this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {
leading: true,
trailing: false,
});
} }
searchUsers(query: string) { searchUsers(query: string) {
return Promise.resolve(this.backendSrv.get('/api/users/search?perpage=10&page=1&query=' + query).then(result => { return Promise.resolve(
return _.map(result.users, user => { this.backendSrv
return {text: user.login + ' - ' + user.email, value: user}; .get('/api/users/search?perpage=10&page=1&query=' + query)
}); .then(result => {
})); return _.map(result.users, user => {
return { text: user.login + ' - ' + user.email, value: user };
});
})
);
} }
onChange(option) { onChange(option) {
this.userPicked({$user: option.value}); this.userPicked({ $user: option.value });
} }
reset() { reset() {
this.user = {text: 'Choose', value: null}; this.user = { text: 'Choose', value: null };
} }
} }
@@ -56,10 +63,10 @@ export function userPicker() {
userPicked: '&', userPicked: '&',
}, },
link: function(scope, elem, attrs, ctrl) { link: function(scope, elem, attrs, ctrl) {
scope.$on("user-picker-reset", () => { scope.$on('user-picker-reset', () => {
ctrl.reset(); ctrl.reset();
}); });
} },
}; };
} }
+32 -32
View File
@@ -1,39 +1,39 @@
import _ from 'lodash'; import _ from 'lodash';
class Settings { class Settings {
datasources: any; datasources: any;
panels: any; panels: any;
appSubUrl: string; appSubUrl: string;
window_title_prefix: string; window_title_prefix: string;
buildInfo: any; buildInfo: any;
new_panel_title: string; new_panel_title: string;
bootData: any; bootData: any;
externalUserMngLinkUrl: string; externalUserMngLinkUrl: string;
externalUserMngLinkName: string; externalUserMngLinkName: string;
externalUserMngInfo: string; externalUserMngInfo: string;
allowOrgCreate: boolean; allowOrgCreate: boolean;
disableLoginForm: boolean; disableLoginForm: boolean;
defaultDatasource: string; defaultDatasource: string;
alertingEnabled: boolean; alertingEnabled: boolean;
authProxyEnabled: boolean; authProxyEnabled: boolean;
ldapEnabled: boolean; ldapEnabled: boolean;
oauth: any; oauth: any;
disableUserSignUp: boolean; disableUserSignUp: boolean;
loginHint: any; loginHint: any;
loginError: any; loginError: any;
constructor(options) { constructor(options) {
var defaults = { var defaults = {
datasources: {}, datasources: {},
window_title_prefix: 'Grafana - ', window_title_prefix: 'Grafana - ',
panels: {}, panels: {},
new_panel_title: 'Panel Title', new_panel_title: 'Panel Title',
playlist_timespan: "1m", playlist_timespan: '1m',
unsaved_changes_warning: true, unsaved_changes_warning: true,
appSubUrl: "" appSubUrl: '',
}; };
_.extend(this, defaults, options); _.extend(this, defaults, options);
} }
} }
var bootData = (<any>window).grafanaBootData || { settings: {} }; var bootData = (<any>window).grafanaBootData || { settings: {} };
-1
View File
@@ -1,4 +1,3 @@
export const GRID_CELL_HEIGHT = 30; export const GRID_CELL_HEIGHT = 30;
export const GRID_CELL_VMARGIN = 10; export const GRID_CELL_VMARGIN = 10;
export const GRID_COLUMN_COUNT = 24; export const GRID_COLUMN_COUNT = 24;
+1 -2
View File
@@ -3,7 +3,6 @@ import coreModule from '../core_module';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
export class ErrorCtrl { export class ErrorCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, contextSrv, navModelSrv) { constructor($scope, contextSrv, navModelSrv) {
$scope.navModel = navModelSrv.getNotFoundNav(); $scope.navModel = navModelSrv.getNotFoundNav();
@@ -13,7 +12,7 @@ export class ErrorCtrl {
appEvents.emit('toggle-sidemenu-hidden'); appEvents.emit('toggle-sidemenu-hidden');
} }
$scope.$on("destroy", () => { $scope.$on('destroy', () => {
if (!contextSrv.isSignedIn) { if (!contextSrv.isSignedIn) {
appEvents.emit('toggle-sidemenu-hidden'); appEvents.emit('toggle-sidemenu-hidden');
} }
+23 -13
View File
@@ -4,20 +4,19 @@ import $ from 'jquery';
import coreModule from '../core_module'; import coreModule from '../core_module';
export class InspectCtrl { export class InspectCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, $sanitize) { constructor($scope, $sanitize) {
var model = $scope.inspector; var model = $scope.inspector;
$scope.init = function () { $scope.init = function() {
$scope.editor = { index: 0 }; $scope.editor = { index: 0 };
if (!model.error) { if (!model.error) {
return; return;
} }
if (_.isString(model.error.data)) { if (_.isString(model.error.data)) {
$scope.response = $("<div>" + model.error.data + "</div>").text(); $scope.response = $('<div>' + model.error.data + '</div>').text();
} else if (model.error.data) { } else if (model.error.data) {
if (model.error.data.response) { if (model.error.data.response) {
$scope.response = $sanitize(model.error.data.response); $scope.response = $sanitize(model.error.data.response);
@@ -29,8 +28,11 @@ export class InspectCtrl {
} }
if (model.error.config && model.error.config.params) { if (model.error.config && model.error.config.params) {
$scope.request_parameters = _.map(model.error.config.params, function(value, key) { $scope.request_parameters = _.map(model.error.config.params, function(
return { key: key, value: value}; value,
key
) {
return { key: key, value: value };
}); });
} }
@@ -44,10 +46,15 @@ export class InspectCtrl {
$scope.editor.index = 2; $scope.editor.index = 2;
if (_.isString(model.error.config.data)) { if (_.isString(model.error.config.data)) {
$scope.request_parameters = this.getParametersFromQueryString(model.error.config.data); $scope.request_parameters = this.getParametersFromQueryString(
} else { model.error.config.data
$scope.request_parameters = _.map(model.error.config.data, function(value, key) { );
return {key: key, value: angular.toJson(value, true)}; } else {
$scope.request_parameters = _.map(model.error.config.data, function(
value,
key
) {
return { key: key, value: angular.toJson(value, true) };
}); });
} }
} }
@@ -55,11 +62,14 @@ export class InspectCtrl {
} }
getParametersFromQueryString(queryString) { getParametersFromQueryString(queryString) {
var result = []; var result = [];
var parameters = queryString.split("&"); var parameters = queryString.split('&');
for (var i = 0; i < parameters.length; i++) { for (var i = 0; i < parameters.length; i++) {
var keyValue = parameters[i].split("="); var keyValue = parameters[i].split('=');
if (keyValue[1].length > 0) { if (keyValue[1].length > 0) {
result.push({ key: keyValue[0], value: (<any>window).unescape(keyValue[1]) }); result.push({
key: keyValue[0],
value: (<any>window).unescape(keyValue[1]),
});
} }
} }
return result; return result;
+17 -17
View File
@@ -2,7 +2,6 @@ import coreModule from '../core_module';
import config from 'app/core/config'; import config from 'app/core/config';
export class InvitedCtrl { export class InvitedCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, $routeParams, contextSrv, backendSrv) { constructor($scope, $routeParams, contextSrv, backendSrv) {
contextSrv.sidemenu = false; contextSrv.sidemenu = false;
@@ -12,23 +11,22 @@ export class InvitedCtrl {
main: { main: {
icon: 'gicon gicon-branding', icon: 'gicon gicon-branding',
subTitle: 'Register your Grafana account', subTitle: 'Register your Grafana account',
breadcrumbs: [ breadcrumbs: [{ title: 'Login', url: '/login' }, { title: 'Invite' }],
{ title: 'Login', url: '/login' }, },
{ title: 'Invite' },
]
}
}; };
$scope.init = function() { $scope.init = function() {
backendSrv.get('/api/user/invite/' + $routeParams.code).then(function(invite) { backendSrv
$scope.formModel.name = invite.name; .get('/api/user/invite/' + $routeParams.code)
$scope.formModel.email = invite.email; .then(function(invite) {
$scope.formModel.username = invite.email; $scope.formModel.name = invite.name;
$scope.formModel.inviteCode = $routeParams.code; $scope.formModel.email = invite.email;
$scope.formModel.username = invite.email;
$scope.formModel.inviteCode = $routeParams.code;
$scope.greeting = invite.name || invite.email || invite.username; $scope.greeting = invite.name || invite.email || invite.username;
$scope.invitedBy = invite.invitedBy; $scope.invitedBy = invite.invitedBy;
}); });
}; };
$scope.submit = function() { $scope.submit = function() {
@@ -36,9 +34,11 @@ export class InvitedCtrl {
return; return;
} }
backendSrv.post('/api/user/invite/complete', $scope.formModel).then(function() { backendSrv
window.location.href = config.appSubUrl + '/'; .post('/api/user/invite/complete', $scope.formModel)
}); .then(function() {
window.location.href = config.appSubUrl + '/';
});
}; };
$scope.init(); $scope.init();
@@ -2,13 +2,13 @@ import angular from 'angular';
import coreModule from '../core_module'; import coreModule from '../core_module';
export class JsonEditorCtrl { export class JsonEditorCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope) { constructor($scope) {
$scope.json = angular.toJson($scope.object, true); $scope.json = angular.toJson($scope.object, true);
$scope.canUpdate = $scope.updateHandler !== void 0 && $scope.contextSrv.isEditor; $scope.canUpdate =
$scope.updateHandler !== void 0 && $scope.contextSrv.isEditor;
$scope.update = function () { $scope.update = function() {
var newObject = angular.fromJson($scope.json); var newObject = angular.fromJson($scope.json);
$scope.updateHandler(newObject, $scope.object); $scope.updateHandler(newObject, $scope.object);
}; };
+11 -10
View File
@@ -3,7 +3,6 @@ import coreModule from '../core_module';
import config from 'app/core/config'; import config from 'app/core/config';
export class LoginCtrl { export class LoginCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, backendSrv, contextSrv, $location) { constructor($scope, backendSrv, contextSrv, $location) {
$scope.formModel = { $scope.formModel = {
@@ -19,13 +18,13 @@ export class LoginCtrl {
$scope.disableLoginForm = config.disableLoginForm; $scope.disableLoginForm = config.disableLoginForm;
$scope.disableUserSignUp = config.disableUserSignUp; $scope.disableUserSignUp = config.disableUserSignUp;
$scope.loginHint = config.loginHint; $scope.loginHint = config.loginHint;
$scope.loginMode = true; $scope.loginMode = true;
$scope.submitBtnText = 'Log in'; $scope.submitBtnText = 'Log in';
$scope.init = function() { $scope.init = function() {
$scope.$watch("loginMode", $scope.loginModeChanged); $scope.$watch('loginMode', $scope.loginModeChanged);
if (config.loginError) { if (config.loginError) {
$scope.appEvent('alert-warning', ['Login Failed', config.loginError]); $scope.appEvent('alert-warning', ['Login Failed', config.loginError]);
@@ -49,13 +48,15 @@ export class LoginCtrl {
return; return;
} }
backendSrv.post('/api/user/signup', $scope.formModel).then(function(result) { backendSrv
if (result.status === 'SignUpCreated') { .post('/api/user/signup', $scope.formModel)
$location.path('/signup').search({email: $scope.formModel.email}); .then(function(result) {
} else { if (result.status === 'SignUpCreated') {
window.location.href = config.appSubUrl + '/'; $location.path('/signup').search({ email: $scope.formModel.email });
} } else {
}); window.location.href = config.appSubUrl + '/';
}
});
}; };
$scope.login = function() { $scope.login = function() {
@@ -1,7 +1,6 @@
import coreModule from '../core_module'; import coreModule from '../core_module';
export class ResetPasswordCtrl { export class ResetPasswordCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, contextSrv, backendSrv, $location) { constructor($scope, contextSrv, backendSrv, $location) {
contextSrv.sidemenu = false; contextSrv.sidemenu = false;
@@ -21,30 +20,36 @@ export class ResetPasswordCtrl {
breadcrumbs: [ breadcrumbs: [
{ title: 'Login', url: '/login' }, { title: 'Login', url: '/login' },
{ title: 'Reset Password' }, { title: 'Reset Password' },
] ],
} },
}; };
$scope.sendResetEmail = function() { $scope.sendResetEmail = function() {
if (!$scope.sendResetForm.$valid) { if (!$scope.sendResetForm.$valid) {
return; return;
} }
backendSrv.post('/api/user/password/send-reset-email', $scope.formModel).then(function() { backendSrv
$scope.mode = 'email-sent'; .post('/api/user/password/send-reset-email', $scope.formModel)
}); .then(function() {
$scope.mode = 'email-sent';
});
}; };
$scope.submitReset = function() { $scope.submitReset = function() {
if (!$scope.resetForm.$valid) { return; } if (!$scope.resetForm.$valid) {
return;
}
if ($scope.formModel.newPassword !== $scope.formModel.confirmPassword) { if ($scope.formModel.newPassword !== $scope.formModel.confirmPassword) {
$scope.appEvent('alert-warning', ['New passwords do not match', '']); $scope.appEvent('alert-warning', ['New passwords do not match', '']);
return; return;
} }
backendSrv.post('/api/user/password/reset', $scope.formModel).then(function() { backendSrv
$location.path('login'); .post('/api/user/password/reset', $scope.formModel)
}); .then(function() {
$location.path('login');
});
}; };
} }
} }
+18 -20
View File
@@ -4,14 +4,13 @@ import config from 'app/core/config';
import coreModule from '../core_module'; import coreModule from '../core_module';
export class SignUpCtrl { export class SignUpCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
private $scope: any, private $scope: any,
private backendSrv: any, private backendSrv: any,
$location: any, $location: any,
contextSrv: any) { contextSrv: any
) {
contextSrv.sidemenu = false; contextSrv.sidemenu = false;
$scope.ctrl = this; $scope.ctrl = this;
@@ -30,11 +29,8 @@ export class SignUpCtrl {
main: { main: {
icon: 'gicon gicon-branding', icon: 'gicon gicon-branding',
subTitle: 'Register your Grafana account', subTitle: 'Register your Grafana account',
breadcrumbs: [ breadcrumbs: [{ title: 'Login', url: '/login' }, { title: 'Sign Up' }],
{ title: 'Login', url: '/login' }, },
{ title: 'Sign Up' },
]
}
}; };
backendSrv.get('/api/user/signup/options').then(options => { backendSrv.get('/api/user/signup/options').then(options => {
@@ -43,20 +39,22 @@ export class SignUpCtrl {
}); });
} }
submit () { submit() {
if (!this.$scope.signUpForm.$valid) { if (!this.$scope.signUpForm.$valid) {
return; return;
} }
this.backendSrv.post('/api/user/signup/step2', this.$scope.formModel).then(rsp => { this.backendSrv
if (rsp.code === 'redirect-to-select-org') { .post('/api/user/signup/step2', this.$scope.formModel)
window.location.href = config.appSubUrl + '/profile/select-org?signup=1'; .then(rsp => {
} else { if (rsp.code === 'redirect-to-select-org') {
window.location.href = config.appSubUrl + '/'; window.location.href =
} config.appSubUrl + '/profile/select-org?signup=1';
}); } else {
window.location.href = config.appSubUrl + '/';
}
});
} }
} }
coreModule.controller('SignUpCtrl', SignUpCtrl); coreModule.controller('SignUpCtrl', SignUpCtrl);
+41 -41
View File
@@ -1,14 +1,14 @@
import "./directives/dash_class"; import './directives/dash_class';
import "./directives/dash_edit_link"; import './directives/dash_edit_link';
import "./directives/dropdown_typeahead"; import './directives/dropdown_typeahead';
import "./directives/metric_segment"; import './directives/metric_segment';
import "./directives/misc"; import './directives/misc';
import "./directives/ng_model_on_blur"; import './directives/ng_model_on_blur';
import "./directives/tags"; import './directives/tags';
import "./directives/value_select_dropdown"; import './directives/value_select_dropdown';
import "./directives/rebuild_on_change"; import './directives/rebuild_on_change';
import "./directives/give_focus"; import './directives/give_focus';
import "./directives/diff-view"; import './directives/diff-view';
import './jquery_extended'; import './jquery_extended';
import './partials'; import './partials';
import './components/jsontree/jsontree'; import './components/jsontree/jsontree';
@@ -20,19 +20,19 @@ import './components/colorpicker/spectrum_picker';
import './services/search_srv'; import './services/search_srv';
import './services/ng_react'; import './services/ng_react';
import {grafanaAppDirective} from './components/grafana_app'; import { grafanaAppDirective } from './components/grafana_app';
import {sideMenuDirective} from './components/sidemenu/sidemenu'; import { sideMenuDirective } from './components/sidemenu/sidemenu';
import {searchDirective} from './components/search/search'; import { searchDirective } from './components/search/search';
import {infoPopover} from './components/info_popover'; import { infoPopover } from './components/info_popover';
import {navbarDirective} from './components/navbar/navbar'; import { navbarDirective } from './components/navbar/navbar';
import {arrayJoin} from './directives/array_join'; import { arrayJoin } from './directives/array_join';
import {liveSrv} from './live/live_srv'; import { liveSrv } from './live/live_srv';
import {Emitter} from './utils/emitter'; import { Emitter } from './utils/emitter';
import {layoutSelector} from './components/layout_selector/layout_selector'; import { layoutSelector } from './components/layout_selector/layout_selector';
import {switchDirective} from './components/switch'; import { switchDirective } from './components/switch';
import {dashboardSelector} from './components/dashboard_selector'; import { dashboardSelector } from './components/dashboard_selector';
import {queryPartEditorDirective} from './components/query_part/query_part_editor'; import { queryPartEditorDirective } from './components/query_part/query_part_editor';
import {formDropdownDirective} from './components/form_dropdown/form_dropdown'; import { formDropdownDirective } from './components/form_dropdown/form_dropdown';
import 'app/core/controllers/all'; import 'app/core/controllers/all';
import 'app/core/services/all'; import 'app/core/services/all';
import 'app/core/routes/routes'; import 'app/core/routes/routes';
@@ -40,23 +40,23 @@ import './filters/filters';
import coreModule from './core_module'; import coreModule from './core_module';
import appEvents from './app_events'; import appEvents from './app_events';
import colors from './utils/colors'; import colors from './utils/colors';
import {assignModelProperties} from './utils/model_utils'; import { assignModelProperties } from './utils/model_utils';
import {contextSrv} from './services/context_srv'; import { contextSrv } from './services/context_srv';
import {KeybindingSrv} from './services/keybindingSrv'; import { KeybindingSrv } from './services/keybindingSrv';
import {helpModal} from './components/help/help'; import { helpModal } from './components/help/help';
import {JsonExplorer} from './components/json_explorer/json_explorer'; import { JsonExplorer } from './components/json_explorer/json_explorer';
import {NavModelSrv, NavModel} from './nav_model_srv'; import { NavModelSrv, NavModel } from './nav_model_srv';
import {userPicker} from './components/user_picker'; import { userPicker } from './components/user_picker';
import {teamPicker} from './components/team_picker'; import { teamPicker } from './components/team_picker';
import {geminiScrollbar} from './components/scroll/scroll'; import { geminiScrollbar } from './components/scroll/scroll';
import {gfPageDirective} from './components/gf_page'; import { gfPageDirective } from './components/gf_page';
import {orgSwitcher} from './components/org_switcher'; import { orgSwitcher } from './components/org_switcher';
import {profiler} from './profiler'; import { profiler } from './profiler';
import {registerAngularDirectives} from './angular_wrappers'; import { registerAngularDirectives } from './angular_wrappers';
import {updateLegendValues} from './time_series2'; import { updateLegendValues } from './time_series2';
import TimeSeries from './time_series2'; import TimeSeries from './time_series2';
import {searchResultsDirective} from './components/search/search_results'; import { searchResultsDirective } from './components/search/search_results';
import {manageDashboardsDirective} from './components/manage_dashboards/manage_dashboards'; import { manageDashboardsDirective } from './components/manage_dashboards/manage_dashboards';
export { export {
profiler, profiler,
@@ -92,5 +92,5 @@ export {
manageDashboardsDirective, manageDashboardsDirective,
TimeSeries, TimeSeries,
updateLegendValues, updateLegendValues,
searchResultsDirective searchResultsDirective,
}; };
+1 -3
View File
@@ -10,7 +10,6 @@ export function arrayJoin() {
restrict: 'A', restrict: 'A',
require: 'ngModel', require: 'ngModel',
link: function(scope, element, attr, ngModel) { link: function(scope, element, attr, ngModel) {
function split_array(text) { function split_array(text) {
return (text || '').split(','); return (text || '').split(',');
} }
@@ -25,9 +24,8 @@ export function arrayJoin() {
ngModel.$parsers.push(split_array); ngModel.$parsers.push(split_array);
ngModel.$formatters.push(join_array); ngModel.$formatters.push(join_array);
} },
}; };
} }
coreModule.directive('arrayJoin', arrayJoin); coreModule.directive('arrayJoin', arrayJoin);
+2 -3
View File
@@ -8,8 +8,7 @@ export class DeltaCtrl {
/** @ngInject */ /** @ngInject */
constructor(private $rootScope) { constructor(private $rootScope) {
const waitForCompile = mutations => {
const waitForCompile = (mutations) => {
if (mutations.length === 1) { if (mutations.length === 1) {
this.$rootScope.appEvent('json-diff-ready'); this.$rootScope.appEvent('json-diff-ready');
} }
@@ -72,7 +71,7 @@ export function linkJson() {
link: '@lineLink', link: '@lineLink',
switchView: '&', switchView: '&',
}, },
template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>` template: `<a class="diff-linenum btn btn-inverse btn-small" ng-click="ctrl.goToLine(link)">Line {{ line }}</a>`,
}; };
} }
coreModule.directive('diffLinkJson', linkJson); coreModule.directive('diffLinkJson', linkJson);
+16 -12
View File
@@ -8,19 +8,23 @@ coreModule.directive('giveFocus', function() {
e.stopPropagation(); e.stopPropagation();
}); });
scope.$watch(attrs.giveFocus, function (newValue) { scope.$watch(
if (!newValue) { attrs.giveFocus,
return; function(newValue) {
} if (!newValue) {
setTimeout(function() { return;
element.focus();
var domEl = element[0];
if (domEl.setSelectionRange) {
var pos = element.val().length * 2;
domEl.setSelectionRange(pos, pos);
} }
}, 200); setTimeout(function() {
}, true); element.focus();
var domEl = element[0];
if (domEl.setSelectionRange) {
var pos = element.val().length * 2;
domEl.setSelectionRange(pos, pos);
}
}, 200);
},
true
);
}; };
}); });
+58 -58
View File
@@ -1,50 +1,50 @@
import angular from "angular"; import angular from 'angular';
import Clipboard from "clipboard"; import Clipboard from 'clipboard';
import coreModule from "../core_module"; import coreModule from '../core_module';
import kbn from "app/core/utils/kbn"; import kbn from 'app/core/utils/kbn';
/** @ngInject */ /** @ngInject */
function tip($compile) { function tip($compile) {
return { return {
restrict: "E", restrict: 'E',
link: function(scope, elem, attrs) { link: function(scope, elem, attrs) {
var _t = var _t =
'<i class="grafana-tip fa fa-' + '<i class="grafana-tip fa fa-' +
(attrs.icon || "question-circle") + (attrs.icon || 'question-circle') +
'" bs-tooltip="\'' + '" bs-tooltip="\'' +
kbn.addslashes(elem.text()) + kbn.addslashes(elem.text()) +
"'\"></i>"; '\'"></i>';
_t = _t.replace(/{/g, "\\{").replace(/}/g, "\\}"); _t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
elem.replaceWith($compile(angular.element(_t))(scope)); elem.replaceWith($compile(angular.element(_t))(scope));
} },
}; };
} }
function clipboardButton() { function clipboardButton() {
return { return {
scope: { scope: {
getText: "&clipboardButton" getText: '&clipboardButton',
}, },
link: function(scope, elem) { link: function(scope, elem) {
scope.clipboard = new Clipboard(elem[0], { scope.clipboard = new Clipboard(elem[0], {
text: function() { text: function() {
return scope.getText(); return scope.getText();
} },
}); });
scope.$on("$destroy", function() { scope.$on('$destroy', function() {
if (scope.clipboard) { if (scope.clipboard) {
scope.clipboard.destroy(); scope.clipboard.destroy();
} }
}); });
} },
}; };
} }
/** @ngInject */ /** @ngInject */
function compile($compile) { function compile($compile) {
return { return {
restrict: "A", restrict: 'A',
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
scope.$watch( scope.$watch(
function(scope) { function(scope) {
@@ -55,42 +55,42 @@ function compile($compile) {
$compile(element.contents())(scope); $compile(element.contents())(scope);
} }
); );
} },
}; };
} }
function watchChange() { function watchChange() {
return { return {
scope: { onchange: "&watchChange" }, scope: { onchange: '&watchChange' },
link: function(scope, element) { link: function(scope, element) {
element.on("input", function() { element.on('input', function() {
scope.$apply(function() { scope.$apply(function() {
scope.onchange({ inputValue: element.val() }); scope.onchange({ inputValue: element.val() });
}); });
}); });
} },
}; };
} }
/** @ngInject */ /** @ngInject */
function editorOptBool($compile) { function editorOptBool($compile) {
return { return {
restrict: "E", restrict: 'E',
link: function(scope, elem, attrs) { link: function(scope, elem, attrs) {
var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : ""; var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
var tip = attrs.tip ? " <tip>" + attrs.tip + "</tip>" : ""; var tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
var showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : ""; var showIf = attrs.showIf ? ' ng-show="' + attrs.showIf + '" ' : '';
var template = var template =
'<div class="editor-option gf-form-checkbox text-center"' + '<div class="editor-option gf-form-checkbox text-center"' +
showIf + showIf +
">" + '>' +
' <label for="' + ' <label for="' +
attrs.model + attrs.model +
'" class="small">' + '" class="small">' +
attrs.text + attrs.text +
tip + tip +
"</label>" + '</label>' +
'<input class="cr1" id="' + '<input class="cr1" id="' +
attrs.model + attrs.model +
'" type="checkbox" ' + '" type="checkbox" ' +
@@ -105,19 +105,19 @@ function editorOptBool($compile) {
attrs.model + attrs.model +
'" class="cr1"></label>'; '" class="cr1"></label>';
elem.replaceWith($compile(angular.element(template))(scope)); elem.replaceWith($compile(angular.element(template))(scope));
} },
}; };
} }
/** @ngInject */ /** @ngInject */
function editorCheckbox($compile, $interpolate) { function editorCheckbox($compile, $interpolate) {
return { return {
restrict: "E", restrict: 'E',
link: function(scope, elem, attrs) { link: function(scope, elem, attrs) {
var text = $interpolate(attrs.text)(scope); var text = $interpolate(attrs.text)(scope);
var model = $interpolate(attrs.model)(scope); var model = $interpolate(attrs.model)(scope);
var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : ""; var ngchange = attrs.change ? ' ng-change="' + attrs.change + '"' : '';
var tip = attrs.tip ? " <tip>" + attrs.tip + "</tip>" : ""; var tip = attrs.tip ? ' <tip>' + attrs.tip + '</tip>' : '';
var label = var label =
'<label for="' + '<label for="' +
scope.$id + scope.$id +
@@ -125,7 +125,7 @@ function editorCheckbox($compile, $interpolate) {
'" class="checkbox-label">' + '" class="checkbox-label">' +
text + text +
tip + tip +
"</label>"; '</label>';
var template = var template =
'<input class="cr1" id="' + '<input class="cr1" id="' +
@@ -145,21 +145,21 @@ function editorCheckbox($compile, $interpolate) {
'" class="cr1"></label>'; '" class="cr1"></label>';
template = template + label; template = template + label;
elem.addClass("gf-form-checkbox"); elem.addClass('gf-form-checkbox');
elem.html($compile(angular.element(template))(scope)); elem.html($compile(angular.element(template))(scope));
} },
}; };
} }
/** @ngInject */ /** @ngInject */
function gfDropdown($parse, $compile, $timeout) { function gfDropdown($parse, $compile, $timeout) {
function buildTemplate(items, placement?) { function buildTemplate(items, placement?) {
var upclass = placement === "top" ? "dropup" : ""; var upclass = placement === 'top' ? 'dropup' : '';
var ul = [ var ul = [
'<ul class="dropdown-menu ' + '<ul class="dropdown-menu ' +
upclass + upclass +
'" role="menu" aria-labelledby="drop1">', '" role="menu" aria-labelledby="drop1">',
"</ul>" '</ul>',
]; ];
for (let index = 0; index < items.length; index++) { for (let index = 0; index < items.length; index++) {
@@ -171,26 +171,26 @@ function gfDropdown($parse, $compile, $timeout) {
} }
var li = var li =
"<li" + '<li' +
(item.submenu && item.submenu.length (item.submenu && item.submenu.length
? ' class="dropdown-submenu"' ? ' class="dropdown-submenu"'
: "") + : '') +
">" + '>' +
'<a tabindex="-1" ng-href="' + '<a tabindex="-1" ng-href="' +
(item.href || "") + (item.href || '') +
'"' + '"' +
(item.click ? ' ng-click="' + item.click + '"' : "") + (item.click ? ' ng-click="' + item.click + '"' : '') +
(item.target ? ' target="' + item.target + '"' : "") + (item.target ? ' target="' + item.target + '"' : '') +
(item.method ? ' data-method="' + item.method + '"' : "") + (item.method ? ' data-method="' + item.method + '"' : '') +
">" + '>' +
(item.text || "") + (item.text || '') +
"</a>"; '</a>';
if (item.submenu && item.submenu.length) { if (item.submenu && item.submenu.length) {
li += buildTemplate(item.submenu).join("\n"); li += buildTemplate(item.submenu).join('\n');
} }
li += "</li>"; li += '</li>';
ul.splice(index + 1, 0, li); ul.splice(index + 1, 0, li);
} }
@@ -198,29 +198,29 @@ function gfDropdown($parse, $compile, $timeout) {
} }
return { return {
restrict: "EA", restrict: 'EA',
scope: true, scope: true,
link: function postLink(scope, iElement, iAttrs) { link: function postLink(scope, iElement, iAttrs) {
var getter = $parse(iAttrs.gfDropdown), var getter = $parse(iAttrs.gfDropdown),
items = getter(scope); items = getter(scope);
$timeout(function() { $timeout(function() {
var placement = iElement.data("placement"); var placement = iElement.data('placement');
var dropdown = angular.element( var dropdown = angular.element(
buildTemplate(items, placement).join("") buildTemplate(items, placement).join('')
); );
dropdown.insertAfter(iElement); dropdown.insertAfter(iElement);
$compile(iElement.next("ul.dropdown-menu"))(scope); $compile(iElement.next('ul.dropdown-menu'))(scope);
}); });
iElement.addClass("dropdown-toggle").attr("data-toggle", "dropdown"); iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
} },
}; };
} }
coreModule.directive("tip", tip); coreModule.directive('tip', tip);
coreModule.directive("clipboardButton", clipboardButton); coreModule.directive('clipboardButton', clipboardButton);
coreModule.directive("compile", compile); coreModule.directive('compile', compile);
coreModule.directive("watchChange", watchChange); coreModule.directive('watchChange', watchChange);
coreModule.directive("editorOptBool", editorOptBool); coreModule.directive('editorOptBool', editorOptBool);
coreModule.directive("editorCheckbox", editorCheckbox); coreModule.directive('editorCheckbox', editorCheckbox);
coreModule.directive("gfDropdown", gfDropdown); coreModule.directive('gfDropdown', gfDropdown);
@@ -17,7 +17,7 @@ function ngModelOnBlur() {
ngModelCtrl.$setViewValue(elm.val()); ngModelCtrl.$setViewValue(elm.val());
}); });
}); });
} },
}; };
} }
@@ -25,12 +25,14 @@ function emptyToNull() {
return { return {
restrict: 'A', restrict: 'A',
require: 'ngModel', require: 'ngModel',
link: function (scope, elm, attrs, ctrl) { link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.push(function (viewValue) { ctrl.$parsers.push(function(viewValue) {
if (viewValue === "") { return null; } if (viewValue === '') {
return null;
}
return viewValue; return viewValue;
}); });
} },
}; };
} }
@@ -48,7 +50,7 @@ function validTimeSpan() {
var info = rangeUtil.describeTextRange(viewValue); var info = rangeUtil.describeTextRange(viewValue);
return info.invalid !== true; return info.invalid !== true;
}; };
} },
}; };
} }
@@ -20,7 +20,6 @@ function getBlockNodes(nodes) {
/** @ngInject **/ /** @ngInject **/
function rebuildOnChange($animate) { function rebuildOnChange($animate) {
return { return {
multiElement: true, multiElement: true,
terminal: true, terminal: true,
@@ -48,7 +47,10 @@ function rebuildOnChange($animate) {
} }
} }
scope.$watch(attrs.property, function rebuildOnChangeAction(value, oldValue) { scope.$watch(attrs.property, function rebuildOnChangeAction(
value,
oldValue
) {
if (childScope && value !== oldValue) { if (childScope && value !== oldValue) {
cleanUp(); cleanUp();
} }
@@ -56,15 +58,17 @@ function rebuildOnChange($animate) {
if (!childScope && (value || attrs.showNull)) { if (!childScope && (value || attrs.showNull)) {
transclude(function(clone, newScope) { transclude(function(clone, newScope) {
childScope = newScope; childScope = newScope;
clone[clone.length++] = document.createComment(' end rebuild on change '); clone[clone.length++] = document.createComment(
block = {clone: clone}; ' end rebuild on change '
);
block = { clone: clone };
$animate.enter(clone, elem.parent(), elem); $animate.enter(clone, elem.parent(), elem);
}); });
} else { } else {
cleanUp(); cleanUp();
} }
}); });
} },
}; };
} }
+94 -36
View File
@@ -6,7 +6,7 @@ import 'vendor/tagsinput/bootstrap-tagsinput.js';
function djb2(str) { function djb2(str) {
var hash = 5381; var hash = 5381;
for (var i = 0; i < str.length; i++) { for (var i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */ hash = (hash << 5) + hash + str.charCodeAt(i); /* hash * 33 + c */
} }
return hash; return hash;
} }
@@ -14,33 +14,79 @@ function djb2(str) {
function setColor(name, element) { function setColor(name, element) {
var hash = djb2(name.toLowerCase()); var hash = djb2(name.toLowerCase());
var colors = [ var colors = [
"#E24D42","#1F78C1","#BA43A9","#705DA0","#466803", '#E24D42',
"#508642","#447EBC","#C15C17","#890F02","#757575", '#1F78C1',
"#0A437C","#6D1F62","#584477","#629E51","#2F4F4F", '#BA43A9',
"#BF1B00","#806EB7","#8a2eb8", "#699e00","#000000", '#705DA0',
"#3F6833","#2F575E","#99440A","#E0752D","#0E4AB4", '#466803',
"#58140C","#052B51","#511749","#3F2B5B", '#508642',
'#447EBC',
'#C15C17',
'#890F02',
'#757575',
'#0A437C',
'#6D1F62',
'#584477',
'#629E51',
'#2F4F4F',
'#BF1B00',
'#806EB7',
'#8a2eb8',
'#699e00',
'#000000',
'#3F6833',
'#2F575E',
'#99440A',
'#E0752D',
'#0E4AB4',
'#58140C',
'#052B51',
'#511749',
'#3F2B5B',
]; ];
var borderColors = [ var borderColors = [
"#FF7368","#459EE7","#E069CF","#9683C6","#6C8E29", '#FF7368',
"#76AC68","#6AA4E2","#E7823D","#AF3528","#9B9B9B", '#459EE7',
"#3069A2","#934588","#7E6A9D","#88C477","#557575", '#E069CF',
"#E54126","#A694DD","#B054DE", "#8FC426","#262626", '#9683C6',
"#658E59","#557D84","#BF6A30","#FF9B53","#3470DA", '#6C8E29',
"#7E3A32","#2B5177","#773D6F","#655181", '#76AC68',
'#6AA4E2',
'#E7823D',
'#AF3528',
'#9B9B9B',
'#3069A2',
'#934588',
'#7E6A9D',
'#88C477',
'#557575',
'#E54126',
'#A694DD',
'#B054DE',
'#8FC426',
'#262626',
'#658E59',
'#557D84',
'#BF6A30',
'#FF9B53',
'#3470DA',
'#7E3A32',
'#2B5177',
'#773D6F',
'#655181',
]; ];
var color = colors[Math.abs(hash % colors.length)]; var color = colors[Math.abs(hash % colors.length)];
var borderColor = borderColors[Math.abs(hash % borderColors.length)]; var borderColor = borderColors[Math.abs(hash % borderColors.length)];
element.css("background-color", color); element.css('background-color', color);
element.css("border-color", borderColor); element.css('border-color', borderColor);
} }
function tagColorFromName() { function tagColorFromName() {
return { return {
scope: { tagColorFromName: "=" }, scope: { tagColorFromName: '=' },
link: function (scope, element) { link: function(scope, element) {
setColor(scope.tagColorFromName, element); setColor(scope.tagColorFromName, element);
} },
}; };
} }
@@ -63,12 +109,11 @@ function bootstrapTagsinput() {
restrict: 'EA', restrict: 'EA',
scope: { scope: {
model: '=ngModel', model: '=ngModel',
onTagsUpdated: "&", onTagsUpdated: '&',
}, },
template: '<select multiple></select>', template: '<select multiple></select>',
replace: false, replace: false,
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
if (!angular.isArray(scope.model)) { if (!angular.isArray(scope.model)) {
scope.model = []; scope.model = [];
} }
@@ -81,13 +126,18 @@ function bootstrapTagsinput() {
select.tagsinput({ select.tagsinput({
typeahead: { typeahead: {
source: angular.isFunction(scope.$parent[attrs.typeaheadSource]) ? scope.$parent[attrs.typeaheadSource] : null source: angular.isFunction(scope.$parent[attrs.typeaheadSource])
? scope.$parent[attrs.typeaheadSource]
: null,
}, },
widthClass: attrs.widthClass, widthClass: attrs.widthClass,
itemValue: getItemProperty(scope, attrs.itemvalue), itemValue: getItemProperty(scope, attrs.itemvalue),
itemText : getItemProperty(scope, attrs.itemtext), itemText: getItemProperty(scope, attrs.itemtext),
tagClass : angular.isFunction(scope.$parent[attrs.tagclass]) ? tagClass: angular.isFunction(scope.$parent[attrs.tagclass])
scope.$parent[attrs.tagclass] : function() { return attrs.tagclass; } ? scope.$parent[attrs.tagclass]
: function() {
return attrs.tagclass;
},
}); });
select.on('itemAdded', function(event) { select.on('itemAdded', function(event) {
@@ -97,7 +147,12 @@ function bootstrapTagsinput() {
scope.onTagsUpdated(); scope.onTagsUpdated();
} }
} }
var tagElement = select.next().children("span").filter(function() { return $(this).text() === event.item; }); var tagElement = select
.next()
.children('span')
.filter(function() {
return $(this).text() === event.item;
});
setColor(event.item, tagElement); setColor(event.item, tagElement);
}); });
@@ -111,19 +166,22 @@ function bootstrapTagsinput() {
} }
}); });
scope.$watch("model", function() { scope.$watch(
if (!angular.isArray(scope.model)) { 'model',
scope.model = []; function() {
} if (!angular.isArray(scope.model)) {
scope.model = [];
}
select.tagsinput('removeAll'); select.tagsinput('removeAll');
for (var i = 0; i < scope.model.length; i++) { for (var i = 0; i < scope.model.length; i++) {
select.tagsinput('add', scope.model[i]); select.tagsinput('add', scope.model[i]);
} }
},
}, true); true
} );
},
}; };
} }
+9 -11
View File
@@ -41,19 +41,17 @@ coreModule.filter('moment', function() {
coreModule.filter('noXml', function() { coreModule.filter('noXml', function() {
var noXml = function(text) { var noXml = function(text) {
return _.isString(text) return _.isString(text)
? text ? text
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
.replace(/'/g, '&#39;') .replace(/'/g, '&#39;')
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
: text; : text;
}; };
return function(text) { return function(text) {
return _.isArray(text) return _.isArray(text) ? _.map(text, noXml) : noXml(text);
? _.map(text, noXml)
: noXml(text);
}; };
}); });
+20 -16
View File
@@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import config from 'app/core/config'; import config from 'app/core/config';
import {Observable} from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
export class LiveSrv { export class LiveSrv {
conn: any; conn: any;
@@ -14,7 +14,12 @@ export class LiveSrv {
getWebSocketUrl() { getWebSocketUrl() {
var l = window.location; var l = window.location;
return ((l.protocol === "https:") ? "wss://" : "ws://") + l.host + config.appSubUrl + '/ws'; return (
(l.protocol === 'https:' ? 'wss://' : 'ws://') +
l.host +
config.appSubUrl +
'/ws'
);
} }
getConnection() { getConnection() {
@@ -30,25 +35,25 @@ export class LiveSrv {
console.log('Live: connecting...'); console.log('Live: connecting...');
this.conn = new WebSocket(this.getWebSocketUrl()); this.conn = new WebSocket(this.getWebSocketUrl());
this.conn.onclose = (evt) => { this.conn.onclose = evt => {
console.log("Live: websocket onclose", evt); console.log('Live: websocket onclose', evt);
reject({message: 'Connection closed'}); reject({ message: 'Connection closed' });
this.initPromise = null; this.initPromise = null;
setTimeout(this.reconnect.bind(this), 2000); setTimeout(this.reconnect.bind(this), 2000);
}; };
this.conn.onmessage = (evt) => { this.conn.onmessage = evt => {
this.handleMessage(evt.data); this.handleMessage(evt.data);
}; };
this.conn.onerror = (evt) => { this.conn.onerror = evt => {
this.initPromise = null; this.initPromise = null;
reject({message: 'Connection error'}); reject({ message: 'Connection error' });
console.log("Live: websocket error", evt); console.log('Live: websocket error', evt);
}; };
this.conn.onopen = (evt) => { this.conn.onopen = evt => {
console.log('opened'); console.log('opened');
this.initPromise = null; this.initPromise = null;
resolve(this.conn); resolve(this.conn);
@@ -62,7 +67,7 @@ export class LiveSrv {
message = JSON.parse(message); message = JSON.parse(message);
if (!message.stream) { if (!message.stream) {
console.log("Error: stream message without stream!", message); console.log('Error: stream message without stream!', message);
return; return;
} }
@@ -85,7 +90,7 @@ export class LiveSrv {
this.getConnection().then(conn => { this.getConnection().then(conn => {
_.each(this.observers, (value, key) => { _.each(this.observers, (value, key) => {
this.send({action: 'subscribe', stream: key}); this.send({ action: 'subscribe', stream: key });
}); });
}); });
} }
@@ -98,7 +103,7 @@ export class LiveSrv {
this.observers[stream] = observer; this.observers[stream] = observer;
this.getConnection().then(conn => { this.getConnection().then(conn => {
this.send({action: 'subscribe', stream: stream}); this.send({ action: 'subscribe', stream: stream });
}); });
} }
@@ -107,7 +112,7 @@ export class LiveSrv {
delete this.observers[stream]; delete this.observers[stream];
this.getConnection().then(conn => { this.getConnection().then(conn => {
this.send({action: 'unsubscribe', stream: stream}); this.send({ action: 'unsubscribe', stream: stream });
}); });
} }
@@ -126,8 +131,7 @@ export class LiveSrv {
// this.send({action: 'subscribe', stream: name}); // this.send({action: 'subscribe', stream: name});
// }); // });
} }
} }
var instance = new LiveSrv(); var instance = new LiveSrv();
export {instance as liveSrv}; export { instance as liveSrv };
+5 -9
View File
@@ -1,18 +1,14 @@
declare module "app/core/controllers/all" { declare module 'app/core/controllers/all' {
let json: any; let json: any;
export {json}; export { json };
} }
declare module "app/core/routes/all" { declare module 'app/core/routes/all' {
let json: any; let json: any;
export {json}; export { json };
} }
declare module "app/core/services/all" { declare module 'app/core/services/all' {
let json: any; let json: any;
export default json; export default json;
} }
+6 -6
View File
@@ -34,7 +34,7 @@ export class NavModelSrv {
} }
getCfgNode() { getCfgNode() {
return _.find(this.navItems, {id: 'cfg'}); return _.find(this.navItems, { id: 'cfg' });
} }
getNav(...args) { getNav(...args) {
@@ -48,7 +48,7 @@ export class NavModelSrv {
break; break;
} }
let node = _.find(children, {id: id}); let node = _.find(children, { id: id });
nav.breadcrumbs.push(node); nav.breadcrumbs.push(node);
nav.node = node; nav.node = node;
nav.main = node; nav.main = node;
@@ -70,15 +70,15 @@ export class NavModelSrv {
getNotFoundNav() { getNotFoundNav() {
var node = { var node = {
text: "Page not found", text: 'Page not found',
icon: "fa fa-fw fa-warning", icon: 'fa fa-fw fa-warning',
subTitle: "404 Error" subTitle: '404 Error',
}; };
return { return {
breadcrumbs: [node], breadcrumbs: [node],
node: node, node: node,
main: node main: node,
}; };
} }
} }
+44 -18
View File
@@ -20,15 +20,30 @@ export class Profiler {
return; return;
} }
$rootScope.$watch(() => { $rootScope.$watch(
this.digestCounter++; () => {
return false; this.digestCounter++;
}, () => {}); return false;
},
() => {}
);
$rootScope.onAppEvent('refresh', this.refresh.bind(this), $rootScope); $rootScope.onAppEvent('refresh', this.refresh.bind(this), $rootScope);
$rootScope.onAppEvent('dashboard-fetch-end', this.dashboardFetched.bind(this), $rootScope); $rootScope.onAppEvent(
$rootScope.onAppEvent('dashboard-initialized', this.dashboardInitialized.bind(this), $rootScope); 'dashboard-fetch-end',
$rootScope.onAppEvent('panel-initialized', this.panelInitialized.bind(this), $rootScope); this.dashboardFetched.bind(this),
$rootScope
);
$rootScope.onAppEvent(
'dashboard-initialized',
this.dashboardInitialized.bind(this),
$rootScope
);
$rootScope.onAppEvent(
'panel-initialized',
this.panelInitialized.bind(this),
$rootScope
);
} }
refresh() { refresh() {
@@ -55,12 +70,21 @@ export class Profiler {
dashboardInitialized() { dashboardInitialized() {
setTimeout(() => { setTimeout(() => {
console.log("Dashboard::Performance Total Digests: " + this.digestCounter); console.log(
console.log("Dashboard::Performance Total Watchers: " + this.getTotalWatcherCount()); 'Dashboard::Performance Total Digests: ' + this.digestCounter
console.log("Dashboard::Performance Total ScopeCount: " + this.scopeCount); );
console.log(
'Dashboard::Performance Total Watchers: ' + this.getTotalWatcherCount()
);
console.log(
'Dashboard::Performance Total ScopeCount: ' + this.scopeCount
);
var timeTaken = this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart; var timeTaken =
console.log("Dashboard::Performance All panels initialized in " + timeTaken + " ms"); this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart;
console.log(
'Dashboard::Performance All panels initialized in ' + timeTaken + ' ms'
);
// measure digest performance // measure digest performance
var rootDigestStart = window.performance.now(); var rootDigestStart = window.performance.now();
@@ -68,7 +92,10 @@ export class Profiler {
this.$rootScope.$apply(); this.$rootScope.$apply();
} }
console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30)); console.log(
'Dashboard::Performance Root Digest ' +
(window.performance.now() - rootDigestStart) / 30
);
}, 3000); }, 3000);
} }
@@ -77,15 +104,15 @@ export class Profiler {
var scopes = 0; var scopes = 0;
var root = $(document.getElementsByTagName('body')); var root = $(document.getElementsByTagName('body'));
var f = function (element) { var f = function(element) {
if (element.data().hasOwnProperty('$scope')) { if (element.data().hasOwnProperty('$scope')) {
scopes++; scopes++;
angular.forEach(element.data().$scope.$$watchers, function () { angular.forEach(element.data().$scope.$$watchers, function() {
count++; count++;
}); });
} }
angular.forEach(element.children(), function (childElement) { angular.forEach(element.children(), function(childElement) {
f($(childElement)); f($(childElement));
}); });
}; };
@@ -116,8 +143,7 @@ export class Profiler {
this.panelsInitCount++; this.panelsInitCount++;
this.timings.lastPanelInitializedAt = new Date().getTime(); this.timings.lastPanelInitializedAt = new Date().getTime();
} }
} }
var profiler = new Profiler(); var profiler = new Profiler();
export {profiler}; export { profiler };
+17 -13
View File
@@ -4,19 +4,23 @@ export class BundleLoader {
constructor(bundleName) { constructor(bundleName) {
var defer = null; var defer = null;
this.lazy = ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => { this.lazy = [
if (defer) { '$q',
'$route',
'$rootScope',
($q, $route, $rootScope) => {
if (defer) {
return defer.promise;
}
defer = $q.defer();
System.import(bundleName).then(() => {
defer.resolve();
});
return defer.promise; return defer.promise;
} },
];
defer = $q.defer();
System.import(bundleName).then(() => {
defer.resolve();
});
return defer.promise;
}];
} }
} }
+25 -22
View File
@@ -1,10 +1,9 @@
import coreModule from '../core_module'; import coreModule from '../core_module';
export class LoadDashboardCtrl { export class LoadDashboardCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) { constructor($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
$scope.appEvent("dashboard-fetch-start"); $scope.appEvent('dashboard-fetch-start');
if (!$routeParams.slug) { if (!$routeParams.slug) {
backendSrv.get('/api/dashboards/home').then(function(homeDash) { backendSrv.get('/api/dashboards/home').then(function(homeDash) {
@@ -19,33 +18,37 @@ export class LoadDashboardCtrl {
return; return;
} }
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) { dashboardLoaderSrv
if ($routeParams.keepRows) { .loadDashboard($routeParams.type, $routeParams.slug)
result.meta.keepRows = true; .then(function(result) {
} if ($routeParams.keepRows) {
$scope.initDashboard(result, $scope); result.meta.keepRows = true;
}); }
$scope.initDashboard(result, $scope);
});
} }
} }
export class NewDashboardCtrl { export class NewDashboardCtrl {
/** @ngInject */ /** @ngInject */
constructor($scope, $routeParams) { constructor($scope, $routeParams) {
$scope.initDashboard({ $scope.initDashboard(
meta: { canStar: false, canShare: false, isNew: true }, {
dashboard: { meta: { canStar: false, canShare: false, isNew: true },
title: "New dashboard", dashboard: {
panels: [ title: 'New dashboard',
{ panels: [
type: 'add-panel', {
gridPos: {x: 0, y: 0, w: 12, h: 9}, type: 'add-panel',
title: 'Panel Title', gridPos: { x: 0, y: 0, w: 12, h: 9 },
} title: 'Panel Title',
], },
folderId: Number($routeParams.folderId) ],
folderId: Number($routeParams.folderId),
},
}, },
}, $scope); $scope
);
} }
} }
+284 -262
View File
@@ -6,277 +6,299 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true); $locationProvider.html5Mode(true);
var loadOrgBundle = { var loadOrgBundle = {
lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => { lazy: [
return System.import('app/features/org/all'); '$q',
}] '$route',
'$rootScope',
($q, $route, $rootScope) => {
return System.import('app/features/org/all');
},
],
}; };
var loadAdminBundle = { var loadAdminBundle = {
lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => { lazy: [
return System.import('app/features/admin/admin'); '$q',
}] '$route',
'$rootScope',
($q, $route, $rootScope) => {
return System.import('app/features/admin/admin');
},
],
}; };
var loadAlertingBundle = { var loadAlertingBundle = {
lazy: ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => { lazy: [
return System.import('app/features/alerting/all'); '$q',
}] '$route',
'$rootScope',
($q, $route, $rootScope) => {
return System.import('app/features/alerting/all');
},
],
}; };
$routeProvider $routeProvider
.when('/', { .when('/', {
templateUrl: 'public/app/partials/dashboard.html', templateUrl: 'public/app/partials/dashboard.html',
controller : 'LoadDashboardCtrl', controller: 'LoadDashboardCtrl',
reloadOnSearch: false, reloadOnSearch: false,
pageClass: 'page-dashboard', pageClass: 'page-dashboard',
}) })
.when('/dashboard/:type/:slug', { .when('/dashboard/:type/:slug', {
templateUrl: 'public/app/partials/dashboard.html', templateUrl: 'public/app/partials/dashboard.html',
controller : 'LoadDashboardCtrl', controller: 'LoadDashboardCtrl',
reloadOnSearch: false, reloadOnSearch: false,
pageClass: 'page-dashboard', pageClass: 'page-dashboard',
}) })
.when('/dashboard-solo/:type/:slug', { .when('/dashboard-solo/:type/:slug', {
templateUrl: 'public/app/features/panel/partials/soloPanel.html', templateUrl: 'public/app/features/panel/partials/soloPanel.html',
controller : 'SoloPanelCtrl', controller: 'SoloPanelCtrl',
reloadOnSearch: false, reloadOnSearch: false,
pageClass: 'page-dashboard', pageClass: 'page-dashboard',
}) })
.when('/dashboard/new', { .when('/dashboard/new', {
templateUrl: 'public/app/partials/dashboard.html', templateUrl: 'public/app/partials/dashboard.html',
controller : 'NewDashboardCtrl', controller: 'NewDashboardCtrl',
reloadOnSearch: false, reloadOnSearch: false,
pageClass: 'page-dashboard', pageClass: 'page-dashboard',
}) })
.when('/dashboard/import', { .when('/dashboard/import', {
templateUrl: 'public/app/features/dashboard/partials/dashboardImport.html', templateUrl:
controller : 'DashboardImportCtrl', 'public/app/features/dashboard/partials/dashboard_import.html',
controllerAs: 'ctrl', controller: 'DashboardImportCtrl',
}) controllerAs: 'ctrl',
.when('/datasources', { })
templateUrl: 'public/app/features/plugins/partials/ds_list.html', .when('/datasources', {
controller : 'DataSourcesCtrl', templateUrl: 'public/app/features/plugins/partials/ds_list.html',
controllerAs: 'ctrl', controller: 'DataSourcesCtrl',
}) controllerAs: 'ctrl',
.when('/datasources/edit/:id', { })
templateUrl: 'public/app/features/plugins/partials/ds_edit.html', .when('/datasources/edit/:id', {
controller : 'DataSourceEditCtrl', templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controllerAs: 'ctrl', controller: 'DataSourceEditCtrl',
}) controllerAs: 'ctrl',
.when('/datasources/new', { })
templateUrl: 'public/app/features/plugins/partials/ds_edit.html', .when('/datasources/new', {
controller : 'DataSourceEditCtrl', templateUrl: 'public/app/features/plugins/partials/ds_edit.html',
controllerAs: 'ctrl', controller: 'DataSourceEditCtrl',
}) controllerAs: 'ctrl',
.when('/dashboards', { })
templateUrl: 'public/app/features/dashboard/partials/dashboard_list.html', .when('/dashboards', {
controller : 'DashboardListCtrl', templateUrl: 'public/app/features/dashboard/partials/dashboard_list.html',
controllerAs: 'ctrl', controller: 'DashboardListCtrl',
}) controllerAs: 'ctrl',
.when('/dashboards/folder/new', { })
templateUrl: 'public/app/features/dashboard/partials/create_folder.html', .when('/dashboards/folder/new', {
controller : 'CreateFolderCtrl', templateUrl: 'public/app/features/dashboard/partials/create_folder.html',
controllerAs: 'ctrl', controller: 'CreateFolderCtrl',
}) controllerAs: 'ctrl',
.when('/dashboards/folder/:folderId/:slug/permissions', { })
templateUrl: 'public/app/features/dashboard/partials/folder_permissions.html', .when('/dashboards/folder/:folderId/:slug/permissions', {
controller : 'FolderPermissionsCtrl', templateUrl:
controllerAs: 'ctrl', 'public/app/features/dashboard/partials/folder_permissions.html',
}) controller: 'FolderPermissionsCtrl',
.when('/dashboards/folder/:folderId/:slug/settings', { controllerAs: 'ctrl',
templateUrl: 'public/app/features/dashboard/partials/folder_settings.html', })
controller : 'FolderSettingsCtrl', .when('/dashboards/folder/:folderId/:slug/settings', {
controllerAs: 'ctrl', templateUrl:
}) 'public/app/features/dashboard/partials/folder_settings.html',
.when('/dashboards/folder/:folderId/:slug', { controller: 'FolderSettingsCtrl',
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html', controllerAs: 'ctrl',
controller : 'FolderDashboardsCtrl', })
controllerAs: 'ctrl', .when('/dashboards/folder/:folderId/:slug', {
}) templateUrl:
.when('/org', { 'public/app/features/dashboard/partials/folder_dashboards.html',
templateUrl: 'public/app/features/org/partials/orgDetails.html', controller: 'FolderDashboardsCtrl',
controller : 'OrgDetailsCtrl', controllerAs: 'ctrl',
resolve: loadOrgBundle, })
}) .when('/org', {
.when('/org/new', { templateUrl: 'public/app/features/org/partials/orgDetails.html',
templateUrl: 'public/app/features/org/partials/newOrg.html', controller: 'OrgDetailsCtrl',
controller : 'NewOrgCtrl', resolve: loadOrgBundle,
resolve: loadOrgBundle, })
}) .when('/org/new', {
.when('/org/users', { templateUrl: 'public/app/features/org/partials/newOrg.html',
templateUrl: 'public/app/features/org/partials/orgUsers.html', controller: 'NewOrgCtrl',
controller : 'OrgUsersCtrl', resolve: loadOrgBundle,
controllerAs: 'ctrl', })
resolve: loadOrgBundle, .when('/org/users', {
}) templateUrl: 'public/app/features/org/partials/orgUsers.html',
.when('/org/users/invite', { controller: 'OrgUsersCtrl',
templateUrl: 'public/app/features/org/partials/invite.html', controllerAs: 'ctrl',
controller : 'UserInviteCtrl', resolve: loadOrgBundle,
controllerAs: 'ctrl', })
resolve: loadOrgBundle, .when('/org/users/invite', {
}) templateUrl: 'public/app/features/org/partials/invite.html',
.when('/org/apikeys', { controller: 'UserInviteCtrl',
templateUrl: 'public/app/features/org/partials/orgApiKeys.html', controllerAs: 'ctrl',
controller : 'OrgApiKeysCtrl', resolve: loadOrgBundle,
resolve: loadOrgBundle, })
}) .when('/org/apikeys', {
.when('/org/teams', { templateUrl: 'public/app/features/org/partials/orgApiKeys.html',
templateUrl: 'public/app/features/org/partials/teams.html', controller: 'OrgApiKeysCtrl',
controller : 'TeamsCtrl', resolve: loadOrgBundle,
controllerAs: 'ctrl', })
resolve: loadOrgBundle, .when('/org/teams', {
}) templateUrl: 'public/app/features/org/partials/teams.html',
.when('/org/teams/edit/:id', { controller: 'TeamsCtrl',
templateUrl: 'public/app/features/org/partials/team_details.html', controllerAs: 'ctrl',
controller : 'TeamDetailsCtrl', resolve: loadOrgBundle,
controllerAs: 'ctrl', })
resolve: loadOrgBundle, .when('/org/teams/edit/:id', {
}) templateUrl: 'public/app/features/org/partials/team_details.html',
.when('/profile', { controller: 'TeamDetailsCtrl',
templateUrl: 'public/app/features/org/partials/profile.html', controllerAs: 'ctrl',
controller : 'ProfileCtrl', resolve: loadOrgBundle,
controllerAs: 'ctrl', })
resolve: loadOrgBundle, .when('/profile', {
}) templateUrl: 'public/app/features/org/partials/profile.html',
.when('/profile/password', { controller: 'ProfileCtrl',
templateUrl: 'public/app/features/org/partials/change_password.html', controllerAs: 'ctrl',
controller : 'ChangePasswordCtrl', resolve: loadOrgBundle,
resolve: loadOrgBundle, })
}) .when('/profile/password', {
.when('/profile/select-org', { templateUrl: 'public/app/features/org/partials/change_password.html',
templateUrl: 'public/app/features/org/partials/select_org.html', controller: 'ChangePasswordCtrl',
controller : 'SelectOrgCtrl', resolve: loadOrgBundle,
resolve: loadOrgBundle, })
}) .when('/profile/select-org', {
// ADMIN templateUrl: 'public/app/features/org/partials/select_org.html',
.when('/admin', { controller: 'SelectOrgCtrl',
templateUrl: 'public/app/features/admin/partials/admin_home.html', resolve: loadOrgBundle,
controller : 'AdminHomeCtrl', })
controllerAs: 'ctrl', // ADMIN
resolve: loadAdminBundle, .when('/admin', {
}) templateUrl: 'public/app/features/admin/partials/admin_home.html',
.when('/admin/settings', { controller: 'AdminHomeCtrl',
templateUrl: 'public/app/features/admin/partials/settings.html', controllerAs: 'ctrl',
controller : 'AdminSettingsCtrl', resolve: loadAdminBundle,
controllerAs: 'ctrl', })
resolve: loadAdminBundle, .when('/admin/settings', {
}) templateUrl: 'public/app/features/admin/partials/settings.html',
.when('/admin/users', { controller: 'AdminSettingsCtrl',
templateUrl: 'public/app/features/admin/partials/users.html', controllerAs: 'ctrl',
controller : 'AdminListUsersCtrl', resolve: loadAdminBundle,
controllerAs: 'ctrl', })
resolve: loadAdminBundle, .when('/admin/users', {
}) templateUrl: 'public/app/features/admin/partials/users.html',
.when('/admin/users/create', { controller: 'AdminListUsersCtrl',
templateUrl: 'public/app/features/admin/partials/new_user.html', controllerAs: 'ctrl',
controller : 'AdminEditUserCtrl', resolve: loadAdminBundle,
resolve: loadAdminBundle, })
}) .when('/admin/users/create', {
.when('/admin/users/edit/:id', { templateUrl: 'public/app/features/admin/partials/new_user.html',
templateUrl: 'public/app/features/admin/partials/edit_user.html', controller: 'AdminEditUserCtrl',
controller : 'AdminEditUserCtrl', resolve: loadAdminBundle,
resolve: loadAdminBundle, })
}) .when('/admin/users/edit/:id', {
.when('/admin/orgs', { templateUrl: 'public/app/features/admin/partials/edit_user.html',
templateUrl: 'public/app/features/admin/partials/orgs.html', controller: 'AdminEditUserCtrl',
controller : 'AdminListOrgsCtrl', resolve: loadAdminBundle,
controllerAs: 'ctrl', })
resolve: loadAdminBundle, .when('/admin/orgs', {
}) templateUrl: 'public/app/features/admin/partials/orgs.html',
.when('/admin/orgs/edit/:id', { controller: 'AdminListOrgsCtrl',
templateUrl: 'public/app/features/admin/partials/edit_org.html', controllerAs: 'ctrl',
controller : 'AdminEditOrgCtrl', resolve: loadAdminBundle,
controllerAs: 'ctrl', })
resolve: loadAdminBundle, .when('/admin/orgs/edit/:id', {
}) templateUrl: 'public/app/features/admin/partials/edit_org.html',
.when('/admin/stats', { controller: 'AdminEditOrgCtrl',
templateUrl: 'public/app/features/admin/partials/stats.html', controllerAs: 'ctrl',
controller : 'AdminStatsCtrl', resolve: loadAdminBundle,
controllerAs: 'ctrl', })
resolve: loadAdminBundle, .when('/admin/stats', {
}) templateUrl: 'public/app/features/admin/partials/stats.html',
// LOGIN / SIGNUP controller: 'AdminStatsCtrl',
.when('/login', { controllerAs: 'ctrl',
templateUrl: 'public/app/partials/login.html', resolve: loadAdminBundle,
controller : 'LoginCtrl', })
pageClass: 'login-page sidemenu-hidden', // LOGIN / SIGNUP
}) .when('/login', {
.when('/invite/:code', { templateUrl: 'public/app/partials/login.html',
templateUrl: 'public/app/partials/signup_invited.html', controller: 'LoginCtrl',
controller : 'InvitedCtrl', pageClass: 'login-page sidemenu-hidden',
pageClass: 'sidemenu-hidden', })
}) .when('/invite/:code', {
.when('/signup', { templateUrl: 'public/app/partials/signup_invited.html',
templateUrl: 'public/app/partials/signup_step2.html', controller: 'InvitedCtrl',
controller : 'SignUpCtrl', pageClass: 'sidemenu-hidden',
pageClass: 'sidemenu-hidden', })
}) .when('/signup', {
.when('/user/password/send-reset-email', { templateUrl: 'public/app/partials/signup_step2.html',
templateUrl: 'public/app/partials/reset_password.html', controller: 'SignUpCtrl',
controller : 'ResetPasswordCtrl', pageClass: 'sidemenu-hidden',
pageClass: 'sidemenu-hidden', })
}) .when('/user/password/send-reset-email', {
.when('/user/password/reset', { templateUrl: 'public/app/partials/reset_password.html',
templateUrl: 'public/app/partials/reset_password.html', controller: 'ResetPasswordCtrl',
controller : 'ResetPasswordCtrl', pageClass: 'sidemenu-hidden',
pageClass: 'sidemenu-hidden', })
}) .when('/user/password/reset', {
.when('/dashboard/snapshots', { templateUrl: 'public/app/partials/reset_password.html',
templateUrl: 'public/app/features/snapshot/partials/snapshots.html', controller: 'ResetPasswordCtrl',
controller : 'SnapshotsCtrl', pageClass: 'sidemenu-hidden',
controllerAs: 'ctrl', })
}) .when('/dashboard/snapshots', {
.when('/plugins', { templateUrl: 'public/app/features/snapshot/partials/snapshots.html',
templateUrl: 'public/app/features/plugins/partials/plugin_list.html', controller: 'SnapshotsCtrl',
controller: 'PluginListCtrl', controllerAs: 'ctrl',
controllerAs: 'ctrl', })
}) .when('/plugins', {
.when('/plugins/:pluginId/edit', { templateUrl: 'public/app/features/plugins/partials/plugin_list.html',
templateUrl: 'public/app/features/plugins/partials/plugin_edit.html', controller: 'PluginListCtrl',
controller: 'PluginEditCtrl', controllerAs: 'ctrl',
controllerAs: 'ctrl', })
}) .when('/plugins/:pluginId/edit', {
.when('/plugins/:pluginId/page/:slug', { templateUrl: 'public/app/features/plugins/partials/plugin_edit.html',
templateUrl: 'public/app/features/plugins/partials/plugin_page.html', controller: 'PluginEditCtrl',
controller: 'AppPageCtrl', controllerAs: 'ctrl',
controllerAs: 'ctrl', })
}) .when('/plugins/:pluginId/page/:slug', {
.when('/styleguide/:page?', { templateUrl: 'public/app/features/plugins/partials/plugin_page.html',
controller: 'StyleGuideCtrl', controller: 'AppPageCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
templateUrl: 'public/app/features/styleguide/styleguide.html', })
}) .when('/styleguide/:page?', {
.when('/alerting', { controller: 'StyleGuideCtrl',
redirectTo: '/alerting/list' controllerAs: 'ctrl',
}) templateUrl: 'public/app/features/styleguide/styleguide.html',
.when('/alerting/list', { })
templateUrl: 'public/app/features/alerting/partials/alert_list.html', .when('/alerting', {
controller: 'AlertListCtrl', redirectTo: '/alerting/list',
controllerAs: 'ctrl', })
resolve: loadAlertingBundle, .when('/alerting/list', {
}) templateUrl: 'public/app/features/alerting/partials/alert_list.html',
.when('/alerting/notifications', { controller: 'AlertListCtrl',
templateUrl: 'public/app/features/alerting/partials/notifications_list.html', controllerAs: 'ctrl',
controller: 'AlertNotificationsListCtrl', resolve: loadAlertingBundle,
controllerAs: 'ctrl', })
resolve: loadAlertingBundle, .when('/alerting/notifications', {
}) templateUrl:
.when('/alerting/notification/new', { 'public/app/features/alerting/partials/notifications_list.html',
templateUrl: 'public/app/features/alerting/partials/notification_edit.html', controller: 'AlertNotificationsListCtrl',
controller: 'AlertNotificationEditCtrl', controllerAs: 'ctrl',
controllerAs: 'ctrl', resolve: loadAlertingBundle,
resolve: loadAlertingBundle, })
}) .when('/alerting/notification/new', {
.when('/alerting/notification/:id/edit', { templateUrl:
templateUrl: 'public/app/features/alerting/partials/notification_edit.html', 'public/app/features/alerting/partials/notification_edit.html',
controller: 'AlertNotificationEditCtrl', controller: 'AlertNotificationEditCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAlertingBundle, resolve: loadAlertingBundle,
}) })
.otherwise({ .when('/alerting/notification/:id/edit', {
templateUrl: 'public/app/partials/error.html', templateUrl:
controller: 'ErrorCtrl' 'public/app/features/alerting/partials/notification_edit.html',
}); controller: 'AlertNotificationEditCtrl',
controllerAs: 'ctrl',
resolve: loadAlertingBundle,
})
.otherwise({
templateUrl: 'public/app/partials/error.html',
controller: 'ErrorCtrl',
});
} }
coreModule.config(setupAngularRoutes); coreModule.config(setupAngularRoutes);
+44 -22
View File
@@ -14,29 +14,50 @@ export class AlertSrv {
} }
init() { init() {
this.$rootScope.onAppEvent('alert-error', (e, alert) => { this.$rootScope.onAppEvent(
this.set(alert[0], alert[1], 'error', 12000); 'alert-error',
}, this.$rootScope); (e, alert) => {
this.set(alert[0], alert[1], 'error', 12000);
},
this.$rootScope
);
this.$rootScope.onAppEvent('alert-warning', (e, alert) => { this.$rootScope.onAppEvent(
this.set(alert[0], alert[1], 'warning', 5000); 'alert-warning',
}, this.$rootScope); (e, alert) => {
this.set(alert[0], alert[1], 'warning', 5000);
},
this.$rootScope
);
this.$rootScope.onAppEvent('alert-success', (e, alert) => { this.$rootScope.onAppEvent(
this.set(alert[0], alert[1], 'success', 3000); 'alert-success',
}, this.$rootScope); (e, alert) => {
this.set(alert[0], alert[1], 'success', 3000);
},
this.$rootScope
);
appEvents.on('alert-warning', options => this.set(options[0], options[1], 'warning', 5000)); appEvents.on('alert-warning', options =>
appEvents.on('alert-success', options => this.set(options[0], options[1], 'success', 3000)); this.set(options[0], options[1], 'warning', 5000)
appEvents.on('alert-error', options => this.set(options[0], options[1], 'error', 7000)); );
appEvents.on('alert-success', options =>
this.set(options[0], options[1], 'success', 3000)
);
appEvents.on('alert-error', options =>
this.set(options[0], options[1], 'error', 7000)
);
appEvents.on('confirm-modal', this.showConfirmModal.bind(this)); appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
} }
getIconForSeverity(severity) { getIconForSeverity(severity) {
switch (severity) { switch (severity) {
case 'success': return 'fa fa-check'; case 'success':
case 'error': return 'fa fa-exclamation-triangle'; return 'fa fa-check';
default: return 'fa fa-exclamation'; case 'error':
return 'fa fa-exclamation-triangle';
default:
return 'fa fa-exclamation';
} }
} }
@@ -52,7 +73,7 @@ export class AlertSrv {
title: title || '', title: title || '',
text: text || '', text: text || '',
severity: severity || 'info', severity: severity || 'info',
icon: this.getIconForSeverity(severity) icon: this.getIconForSeverity(severity),
}; };
var newAlertJson = angular.toJson(newAlert); var newAlertJson = angular.toJson(newAlert);
@@ -73,7 +94,7 @@ export class AlertSrv {
this.$rootScope.$digest(); this.$rootScope.$digest();
} }
return(newAlert); return newAlert;
} }
clear(alert) { clear(alert) {
@@ -93,7 +114,8 @@ export class AlertSrv {
}; };
scope.updateConfirmText = function(value) { scope.updateConfirmText = function(value) {
scope.confirmTextValid = payload.confirmText.toLowerCase() === value.toLowerCase(); scope.confirmTextValid =
payload.confirmText.toLowerCase() === value.toLowerCase();
}; };
scope.title = payload.title; scope.title = payload.title;
@@ -104,9 +126,9 @@ export class AlertSrv {
scope.onConfirm = payload.onConfirm; scope.onConfirm = payload.onConfirm;
scope.onAltAction = payload.onAltAction; scope.onAltAction = payload.onAltAction;
scope.altActionText = payload.altActionText; scope.altActionText = payload.altActionText;
scope.icon = payload.icon || "fa-check"; scope.icon = payload.icon || 'fa-check';
scope.yesText = payload.yesText || "Yes"; scope.yesText = payload.yesText || 'Yes';
scope.noText = payload.noText || "Cancel"; scope.noText = payload.noText || 'Cancel';
scope.confirmTextValid = scope.confirmText ? false : true; scope.confirmTextValid = scope.confirmText ? false : true;
var confirmModal = this.$modal({ var confirmModal = this.$modal({
@@ -115,7 +137,7 @@ export class AlertSrv {
modalClass: 'confirm-modal', modalClass: 'confirm-modal',
show: false, show: false,
scope: scope, scope: scope,
keyboard: false keyboard: false,
}); });
confirmModal.then(function(modalEl) { confirmModal.then(function(modalEl) {
+7 -5
View File
@@ -3,14 +3,17 @@ import coreModule from 'app/core/core_module';
import config from 'app/core/config'; import config from 'app/core/config';
export class Analytics { export class Analytics {
/** @ngInject */ /** @ngInject */
constructor(private $rootScope, private $location) { constructor(private $rootScope, private $location) {}
}
gaInit() { gaInit() {
$.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut $.getScript('https://www.google-analytics.com/analytics.js'); // jQuery shortcut
var ga = (<any>window).ga = (<any>window).ga || function () { (ga.q = ga.q || []).push(arguments); }; ga.l = +new Date; var ga = ((<any>window).ga =
(<any>window).ga ||
function() {
(ga.q = ga.q || []).push(arguments);
});
ga.l = +new Date();
ga('create', (<any>config).googleAnalyticsId, 'auto'); ga('create', (<any>config).googleAnalyticsId, 'auto');
return ga; return ga;
} }
@@ -33,4 +36,3 @@ function startAnalytics(googleAnalyticsSrv) {
} }
coreModule.service('googleAnalyticsSrv', Analytics).run(startAnalytics); coreModule.service('googleAnalyticsSrv', Analytics).run(startAnalytics);
+126 -102
View File
@@ -11,8 +11,13 @@ export class BackendSrv {
private noBackendCache: boolean; private noBackendCache: boolean;
/** @ngInject */ /** @ngInject */
constructor(private $http, private alertSrv, private $q, private $timeout, private contextSrv) { constructor(
} private $http,
private alertSrv,
private $q,
private $timeout,
private contextSrv
) {}
get(url, params?) { get(url, params?) {
return this.request({ method: 'GET', url: url, params: params }); return this.request({ method: 'GET', url: url, params: params });
@@ -52,22 +57,22 @@ export class BackendSrv {
} }
if (err.status === 422) { if (err.status === 422) {
this.alertSrv.set("Validation failed", data.message, "warning", 4000); this.alertSrv.set('Validation failed', data.message, 'warning', 4000);
throw data; throw data;
} }
data.severity = 'error'; data.severity = 'error';
if (err.status < 500) { if (err.status < 500) {
data.severity = "warning"; data.severity = 'warning';
} }
if (data.message) { if (data.message) {
let description = ""; let description = '';
let message = data.message; let message = data.message;
if (message.length > 80) { if (message.length > 80) {
description = message; description = message;
message = "Error"; message = 'Error';
} }
this.alertSrv.set(message, description, data.severity, 10000); this.alertSrv.set(message, description, data.severity, 10000);
} }
@@ -86,32 +91,39 @@ export class BackendSrv {
options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
} }
if (options.url.indexOf("/") === 0) { if (options.url.indexOf('/') === 0) {
options.url = options.url.substring(1); options.url = options.url.substring(1);
} }
} }
return this.$http(options).then(results => { return this.$http(options).then(
if (options.method !== 'GET') { results => {
if (results && results.data.message) { if (options.method !== 'GET') {
if (options.showSuccessAlert !== false) { if (results && results.data.message) {
this.alertSrv.set(results.data.message, '', 'success', 3000); if (options.showSuccessAlert !== false) {
this.alertSrv.set(results.data.message, '', 'success', 3000);
}
} }
} }
} return results.data;
return results.data; },
}, err => { err => {
// handle unauthorized // handle unauthorized
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) { if (
return this.loginPing().then(() => { err.status === 401 &&
options.retry = 1; this.contextSrv.user.isSignedIn &&
return this.request(options); firstAttempt
}); ) {
} return this.loginPing().then(() => {
options.retry = 1;
return this.request(options);
});
}
this.$timeout(this.requestErrorHandler.bind(this, err), 50); this.$timeout(this.requestErrorHandler.bind(this, err), 50);
throw err; throw err;
}); }
);
} }
addCanceler(requestId, canceler) { addCanceler(requestId, canceler) {
@@ -154,7 +166,7 @@ export class BackendSrv {
options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId; options.headers['X-Grafana-Org-Id'] = this.contextSrv.user.orgId;
} }
if (options.url.indexOf("/") === 0) { if (options.url.indexOf('/') === 0) {
options.url = options.url.substring(1); options.url = options.url.substring(1);
} }
@@ -168,51 +180,53 @@ export class BackendSrv {
} }
} }
return this.$http(options).then(response => { return this.$http(options)
appEvents.emit('ds-request-response', response); .then(response => {
return response; appEvents.emit('ds-request-response', response);
}).catch(err => { return response;
if (err.status === this.HTTP_REQUEST_CANCELLED) { })
throw {err, cancelled: true}; .catch(err => {
} if (err.status === this.HTTP_REQUEST_CANCELLED) {
throw { err, cancelled: true };
}
// handle unauthorized for backend requests // handle unauthorized for backend requests
if (requestIsLocal && firstAttempt && err.status === 401) { if (requestIsLocal && firstAttempt && err.status === 401) {
return this.loginPing().then(() => { return this.loginPing().then(() => {
options.retry = 1; options.retry = 1;
if (canceler) { if (canceler) {
canceler.resolve(); canceler.resolve();
} }
return this.datasourceRequest(options); return this.datasourceRequest(options);
}); });
} }
// populate error obj on Internal Error // populate error obj on Internal Error
if (_.isString(err.data) && err.status === 500) { if (_.isString(err.data) && err.status === 500) {
err.data = { err.data = {
error: err.statusText, error: err.statusText,
response: err.data, response: err.data,
}; };
} }
// for Prometheus // for Prometheus
if (err.data && !err.data.message && _.isString(err.data.error)) { if (err.data && !err.data.message && _.isString(err.data.error)) {
err.data.message = err.data.error; err.data.message = err.data.error;
} }
appEvents.emit('ds-request-error', err); appEvents.emit('ds-request-error', err);
throw err; throw err;
})
}).finally(() => { .finally(() => {
// clean up // clean up
if (options.requestId) { if (options.requestId) {
this.inFlightRequests[options.requestId].shift(); this.inFlightRequests[options.requestId].shift();
} }
}); });
} }
loginPing() { loginPing() {
return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); return this.request({ url: '/api/login/ping', method: 'GET', retry: 1 });
} }
search(query) { search(query) {
@@ -224,7 +238,7 @@ export class BackendSrv {
} }
saveDashboard(dash, options) { saveDashboard(dash, options) {
options = (options || {}); options = options || {};
return this.post('/api/dashboards/db/', { return this.post('/api/dashboards/db/', {
dashboard: dash, dashboard: dash,
@@ -237,13 +251,16 @@ export class BackendSrv {
createDashboardFolder(name) { createDashboardFolder(name) {
const dash = { const dash = {
schemaVersion: 16, schemaVersion: 16,
title: name, title: name.trim(),
editable: true, editable: true,
panels: [] panels: [],
}; };
return this.post('/api/dashboards/db/', {dashboard: dash, isFolder: true, overwrite: false}) return this.post('/api/dashboards/db/', {
.then(res => { dashboard: dash,
isFolder: true,
overwrite: false,
}).then(res => {
return this.getDashboard('db', res.slug); return this.getDashboard('db', res.slug);
}); });
} }
@@ -251,15 +268,15 @@ export class BackendSrv {
deleteDashboard(slug) { deleteDashboard(slug) {
let deferred = this.$q.defer(); let deferred = this.$q.defer();
this.getDashboard('db', slug) this.getDashboard('db', slug).then(fullDash => {
.then(fullDash => { this.delete(`/api/dashboards/db/${slug}`)
this.delete(`/api/dashboards/db/${slug}`) .then(() => {
.then(() => { deferred.resolve(fullDash);
deferred.resolve(fullDash); })
}).catch(err => { .catch(err => {
deferred.reject(err); deferred.reject(err);
}); });
}); });
return deferred.promise; return deferred.promise;
} }
@@ -278,17 +295,19 @@ export class BackendSrv {
const tasks = []; const tasks = [];
for (let slug of dashboardSlugs) { for (let slug of dashboardSlugs) {
tasks.push(this.createTask(this.moveDashboard.bind(this), true, slug, toFolder)); tasks.push(
this.createTask(this.moveDashboard.bind(this), true, slug, toFolder)
);
} }
return this.executeInOrder(tasks, []) return this.executeInOrder(tasks, []).then(result => {
.then(result => { return {
return { totalCount: result.length,
totalCount: result.length, successCount: _.filter(result, { succeeded: true }).length,
successCount: _.filter(result, { succeeded: true }).length, alreadyInFolderCount: _.filter(result, { alreadyInFolder: true })
alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length .length,
}; };
}); });
} }
private moveDashboard(slug, toFolder) { private moveDashboard(slug, toFolder) {
@@ -297,9 +316,11 @@ export class BackendSrv {
this.getDashboard('db', slug).then(fullDash => { this.getDashboard('db', slug).then(fullDash => {
const model = new DashboardModel(fullDash.dashboard, fullDash.meta); const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
if ((!fullDash.meta.folderId && toFolder.id === 0) || if (
fullDash.meta.folderId === toFolder.id) { (!fullDash.meta.folderId && toFolder.id === 0) ||
deferred.resolve({alreadyInFolder: true}); fullDash.meta.folderId === toFolder.id
) {
deferred.resolve({ alreadyInFolder: true });
return; return;
} }
@@ -310,19 +331,21 @@ export class BackendSrv {
this.saveDashboard(clone, {}) this.saveDashboard(clone, {})
.then(() => { .then(() => {
deferred.resolve({succeeded: true}); deferred.resolve({ succeeded: true });
}).catch(err => { })
if (err.data && err.data.status === "plugin-dashboard") { .catch(err => {
if (err.data && err.data.status === 'plugin-dashboard') {
err.isHandled = true; err.isHandled = true;
this.saveDashboard(clone, {overwrite: true}) this.saveDashboard(clone, { overwrite: true })
.then(() => { .then(() => {
deferred.resolve({succeeded: true}); deferred.resolve({ succeeded: true });
}).catch(err => { })
deferred.resolve({succeeded: false}); .catch(err => {
deferred.resolve({ succeeded: false });
}); });
} else { } else {
deferred.resolve({succeeded: false}); deferred.resolve({ succeeded: false });
} }
}); });
}); });
@@ -331,11 +354,13 @@ export class BackendSrv {
} }
private createTask(fn, ignoreRejections, ...args: any[]) { private createTask(fn, ignoreRejections, ...args: any[]) {
return (result) => { return result => {
return fn.apply(null, args) return fn
.apply(null, args)
.then(res => { .then(res => {
return Array.prototype.concat(result, [res]); return Array.prototype.concat(result, [res]);
}).catch(err => { })
.catch(err => {
if (ignoreRejections) { if (ignoreRejections) {
return result; return result;
} }
@@ -350,5 +375,4 @@ export class BackendSrv {
} }
} }
coreModule.service('backendSrv', BackendSrv); coreModule.service('backendSrv', BackendSrv);
+6 -3
View File
@@ -36,7 +36,7 @@ export class ContextSrv {
config.buildInfo = {}; config.buildInfo = {};
} }
if (!config.bootData) { if (!config.bootData) {
config.bootData = {user: {}, settings: {}}; config.bootData = { user: {}, settings: {} };
} }
this.version = config.buildInfo.version; this.version = config.buildInfo.version;
@@ -51,7 +51,10 @@ export class ContextSrv {
} }
isGrafanaVisible() { isGrafanaVisible() {
return !!(document.visibilityState === undefined || document.visibilityState === 'visible'); return !!(
document.visibilityState === undefined ||
document.visibilityState === 'visible'
);
} }
toggleSideMenu() { toggleSideMenu() {
@@ -61,7 +64,7 @@ export class ContextSrv {
} }
var contextSrv = new ContextSrv(); var contextSrv = new ContextSrv();
export {contextSrv}; export { contextSrv };
coreModule.factory('contextSrv', function() { coreModule.factory('contextSrv', function() {
return contextSrv; return contextSrv;
@@ -4,7 +4,6 @@ import angular from 'angular';
import coreModule from '../core_module'; import coreModule from '../core_module';
class DynamicDirectiveSrv { class DynamicDirectiveSrv {
/** @ngInject */ /** @ngInject */
constructor(private $compile, private $rootScope) {} constructor(private $compile, private $rootScope) {}
@@ -17,22 +16,31 @@ class DynamicDirectiveSrv {
} }
link(scope, elem, attrs, options) { link(scope, elem, attrs, options) {
options.directive(scope).then(directiveInfo => { options
if (!directiveInfo || !directiveInfo.fn) { .directive(scope)
elem.empty(); .then(directiveInfo => {
return; if (!directiveInfo || !directiveInfo.fn) {
} elem.empty();
return;
}
if (!directiveInfo.fn.registered) { if (!directiveInfo.fn.registered) {
coreModule.directive(attrs.$normalize(directiveInfo.name), directiveInfo.fn); coreModule.directive(
directiveInfo.fn.registered = true; attrs.$normalize(directiveInfo.name),
} directiveInfo.fn
);
directiveInfo.fn.registered = true;
}
this.addDirective(elem, directiveInfo.name, scope); this.addDirective(elem, directiveInfo.name, scope);
}).catch(err => { })
console.log('Plugin load:', err); .catch(err => {
this.$rootScope.appEvent('alert-error', ['Plugin error', err.toString()]); console.log('Plugin load:', err);
}); this.$rootScope.appEvent('alert-error', [
'Plugin error',
err.toString(),
]);
});
} }
create(options) { create(options) {
@@ -52,7 +60,7 @@ class DynamicDirectiveSrv {
} else { } else {
this.link(scope, elem, attrs, options); this.link(scope, elem, attrs, options);
} }
} },
}; };
return directiveDef; return directiveDef;
@@ -60,5 +68,3 @@ class DynamicDirectiveSrv {
} }
coreModule.service('dynamicDirectiveSrv', DynamicDirectiveSrv); coreModule.service('dynamicDirectiveSrv', DynamicDirectiveSrv);
+28 -4
View File
@@ -1,19 +1,43 @@
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import config from 'app/core/config';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
// This service is for registering global events. // This service is for registering global events.
// Good for communication react > angular and vice verse // Good for communication react > angular and vice verse
export class GlobalEventSrv { export class GlobalEventSrv {
private appSubUrl;
private fullPageReloadRoutes;
/** @ngInject */ /** @ngInject */
constructor(private $location, private $timeout) { constructor(private $location, private $timeout, private $window) {
this.appSubUrl = config.appSubUrl;
this.fullPageReloadRoutes = ['/logout'];
}
// Angular's $location does not like <base href...> and absolute urls
stripBaseFromUrl(url = '') {
const appSubUrl = this.appSubUrl;
const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0;
const urlWithoutBase =
url.length > 0 && url.indexOf(appSubUrl) === 0
? url.slice(appSubUrl.length - stripExtraChars)
: url;
return urlWithoutBase;
} }
init() { init() {
appEvents.on('location-change', payload => { appEvents.on('location-change', payload => {
this.$timeout(() => { // A hack to use timeout when we're changing things (in this case the url) from outside of Angular. const urlWithoutBase = this.stripBaseFromUrl(payload.href);
this.$location.path(payload.href); if (this.fullPageReloadRoutes.indexOf(urlWithoutBase) > -1) {
}); this.$window.location.href = payload.href;
return;
}
this.$timeout(() => {
// A hack to use timeout when we're changing things (in this case the url) from outside of Angular.
this.$location.url(urlWithoutBase);
});
}); });
} }
} }
+3 -3
View File
@@ -15,7 +15,7 @@ export class ImpressionSrv {
} }
} }
impressions = impressions.filter((imp) => { impressions = impressions.filter(imp => {
return dashboardId !== imp; return dashboardId !== imp;
}); });
@@ -28,7 +28,7 @@ export class ImpressionSrv {
} }
getDashboardOpened() { getDashboardOpened() {
var impressions = store.get(this.impressionKey(config)) || "[]"; var impressions = store.get(this.impressionKey(config)) || '[]';
impressions = JSON.parse(impressions); impressions = JSON.parse(impressions);
@@ -40,7 +40,7 @@ export class ImpressionSrv {
} }
impressionKey(config) { impressionKey(config) {
return "dashboard_impressions-" + config.bootData.user.orgId; return 'dashboard_impressions-' + config.bootData.user.orgId;
} }
} }
+28 -27
View File
@@ -10,10 +10,7 @@ export class KeybindingSrv {
helpModal: boolean; helpModal: boolean;
/** @ngInject */ /** @ngInject */
constructor( constructor(private $rootScope, private $location) {
private $rootScope,
private $location) {
// clear out all shortcuts on route change // clear out all shortcuts on route change
$rootScope.$on('$routeChangeSuccess', () => { $rootScope.$on('$routeChangeSuccess', () => {
Mousetrap.reset(); Mousetrap.reset();
@@ -26,54 +23,58 @@ export class KeybindingSrv {
setupGlobal() { setupGlobal() {
this.bind(['?', 'h'], this.showHelpModal); this.bind(['?', 'h'], this.showHelpModal);
this.bind("g h", this.goToHome); this.bind('g h', this.goToHome);
this.bind("g a", this.openAlerting); this.bind('g a', this.openAlerting);
this.bind("g p", this.goToProfile); this.bind('g p', this.goToProfile);
this.bind("s s", this.openSearchStarred); this.bind('s s', this.openSearchStarred);
this.bind('s o', this.openSearch); this.bind('s o', this.openSearch);
this.bind('s t', this.openSearchTags); this.bind('s t', this.openSearchTags);
this.bind('f', this.openSearch); this.bind('f', this.openSearch);
} }
openSearchStarred() { openSearchStarred() {
this.$rootScope.appEvent('show-dash-search', {starred: true}); appEvents.emit('show-dash-search', { starred: true });
} }
openSearchTags() { openSearchTags() {
this.$rootScope.appEvent('show-dash-search', {tagsMode: true}); appEvents.emit('show-dash-search', { tagsMode: true });
} }
openSearch() { openSearch() {
this.$rootScope.appEvent('show-dash-search'); appEvents.emit('show-dash-search');
} }
openAlerting() { openAlerting() {
this.$location.url("/alerting"); this.$location.url('/alerting');
} }
goToHome() { goToHome() {
this.$location.url("/"); this.$location.url('/');
} }
goToProfile() { goToProfile() {
this.$location.url("/profile"); this.$location.url('/profile');
} }
showHelpModal() { showHelpModal() {
appEvents.emit('show-modal', {templateHtml: '<help-modal></help-modal>'}); appEvents.emit('show-modal', { templateHtml: '<help-modal></help-modal>' });
} }
bind(keyArg, fn) { bind(keyArg, fn) {
Mousetrap.bind(keyArg, evt => { Mousetrap.bind(
evt.preventDefault(); keyArg,
evt.stopPropagation(); evt => {
evt.returnValue = false; evt.preventDefault();
return this.$rootScope.$apply(fn.bind(this)); evt.stopPropagation();
}, 'keydown'); evt.returnValue = false;
return this.$rootScope.$apply(fn.bind(this));
},
'keydown'
);
} }
showDashEditView() { showDashEditView() {
var search = _.extend(this.$location.search(), {editview: 'settings'}); var search = _.extend(this.$location.search(), { editview: 'settings' });
this.$location.search(search); this.$location.search(search);
} }
@@ -111,7 +112,7 @@ export class KeybindingSrv {
fullscreen: true, fullscreen: true,
edit: true, edit: true,
panelId: dashboard.meta.focusPanelId, panelId: dashboard.meta.focusPanelId,
toggle: true toggle: true,
}); });
} }
}); });
@@ -140,14 +141,14 @@ export class KeybindingSrv {
// share panel // share panel
this.bind('p s', () => { this.bind('p s', () => {
if (dashboard.meta.focusPanelId) { if (dashboard.meta.focusPanelId) {
var shareScope = scope.$new(); var shareScope = scope.$new();
var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId); var panelInfo = dashboard.getPanelInfoById(dashboard.meta.focusPanelId);
shareScope.panel = panelInfo.panel; shareScope.panel = panelInfo.panel;
shareScope.dashboard = dashboard; shareScope.dashboard = dashboard;
appEvents.emit('show-modal', { appEvents.emit('show-modal', {
src: 'public/app/features/dashboard/partials/shareModal.html', src: 'public/app/features/dashboard/partials/shareModal.html',
scope: shareScope scope: shareScope,
}); });
} }
}); });
@@ -185,7 +186,7 @@ export class KeybindingSrv {
}); });
this.bind('d n', e => { this.bind('d n', e => {
this.$location.url("/dashboard/new"); this.$location.url('/dashboard/new');
}); });
this.bind('d r', () => { this.bind('d r', () => {
@@ -211,7 +212,7 @@ export class KeybindingSrv {
} }
scope.appEvent('hide-modal'); scope.appEvent('hide-modal');
scope.appEvent('panel-change-view', {fullscreen: false, edit: false}); scope.appEvent('panel-change-view', { fullscreen: false, edit: false });
// close settings view // close settings view
var search = this.$location.search(); var search = this.$location.search();
+39 -19
View File
@@ -2,7 +2,6 @@
// This is using ng-react with this PR applied https://github.com/ngReact/ngReact/pull/199 // This is using ng-react with this PR applied https://github.com/ngReact/ngReact/pull/199
// //
// # ngReact // # ngReact
// ### Use React Components inside of your Angular applications // ### Use React Components inside of your Angular applications
// //
@@ -85,24 +84,27 @@ function applyFunctions(obj, scope, propsConfig?) {
var value = obj[key]; var value = obj[key];
var config = (propsConfig || {})[key] || {}; var config = (propsConfig || {})[key] || {};
/** /**
* wrap functions in a function that ensures they are scope.$applied * wrap functions in a function that ensures they are scope.$applied
* ensures that when function is called from a React component * ensures that when function is called from a React component
* the Angular digest cycle is run * the Angular digest cycle is run
*/ */
prev[key] = angular.isFunction(value) && config.wrapApply !== false ? applied(value, scope) : value; prev[key] =
angular.isFunction(value) && config.wrapApply !== false
? applied(value, scope)
: value;
return prev; return prev;
}, {}); }, {});
} }
/** /**
* *
* @param watchDepth (value of HTML watch-depth attribute) * @param watchDepth (value of HTML watch-depth attribute)
* @param scope (angular scope) * @param scope (angular scope)
* *
* Uses the watchDepth attribute to determine how to watch props on scope. * Uses the watchDepth attribute to determine how to watch props on scope.
* If watchDepth attribute is NOT reference or collection, watchDepth defaults to deep watching by value * If watchDepth attribute is NOT reference or collection, watchDepth defaults to deep watching by value
*/ */
function watchProps(watchDepth, scope, watchExpressions, listener) { function watchProps(watchDepth, scope, watchExpressions, listener) {
var supportsWatchCollection = angular.isFunction(scope.$watchCollection); var supportsWatchCollection = angular.isFunction(scope.$watchCollection);
var supportsWatchGroup = angular.isFunction(scope.$watchGroup); var supportsWatchGroup = angular.isFunction(scope.$watchGroup);
@@ -165,7 +167,8 @@ function findAttribute(attrs, propName) {
// get watch depth of prop (string or array) // get watch depth of prop (string or array)
function getPropWatchDepth(defaultWatch, prop) { function getPropWatchDepth(defaultWatch, prop) {
var customWatchDepth = Array.isArray(prop) && angular.isObject(prop[1]) && prop[1].watchDepth; var customWatchDepth =
Array.isArray(prop) && angular.isObject(prop[1]) && prop[1].watchDepth;
return customWatchDepth || defaultWatch; return customWatchDepth || defaultWatch;
} }
@@ -202,7 +205,9 @@ var reactComponent = function($injector) {
}; };
// If there are props, re-render when they change // If there are props, re-render when they change
attrs.props ? watchProps(attrs.watchDepth, scope, [attrs.props], renderMyComponent) : renderMyComponent(); attrs.props
? watchProps(attrs.watchDepth, scope, [attrs.props], renderMyComponent)
: renderMyComponent();
// cleanup when scope is destroyed // cleanup when scope is destroyed
scope.$on('$destroy', function() { scope.$on('$destroy', function() {
@@ -210,7 +215,10 @@ var reactComponent = function($injector) {
ReactDOM.unmountComponentAtNode(elem[0]); ReactDOM.unmountComponentAtNode(elem[0]);
} else { } else {
scope.$eval(attrs.onScopeDestroy, { scope.$eval(attrs.onScopeDestroy, {
unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]), unmountComponent: ReactDOM.unmountComponentAtNode.bind(
this,
elem[0]
),
}); });
} }
}); });
@@ -274,11 +282,20 @@ var reactDirective = function($injector) {
// watch each property name and trigger an update whenever something changes, // watch each property name and trigger an update whenever something changes,
// to update scope.props with new values // to update scope.props with new values
var propExpressions = props.map(function(prop) { var propExpressions = props.map(function(prop) {
return Array.isArray(prop) ? [attrs[getPropName(prop)], getPropConfig(prop)] : attrs[prop]; return Array.isArray(prop)
? [attrs[getPropName(prop)], getPropConfig(prop)]
: attrs[prop];
}); });
// If we don't have any props, then our watch statement won't fire. // If we don't have any props, then our watch statement won't fire.
props.length ? watchProps(attrs.watchDepth, scope, propExpressions, renderMyComponent) : renderMyComponent(); props.length
? watchProps(
attrs.watchDepth,
scope,
propExpressions,
renderMyComponent
)
: renderMyComponent();
// cleanup when scope is destroyed // cleanup when scope is destroyed
scope.$on('$destroy', function() { scope.$on('$destroy', function() {
@@ -286,7 +303,10 @@ var reactDirective = function($injector) {
ReactDOM.unmountComponentAtNode(elem[0]); ReactDOM.unmountComponentAtNode(elem[0]);
} else { } else {
scope.$eval(attrs.onScopeDestroy, { scope.$eval(attrs.onScopeDestroy, {
unmountComponent: ReactDOM.unmountComponentAtNode.bind(this, elem[0]), unmountComponent: ReactDOM.unmountComponentAtNode.bind(
this,
elem[0]
),
}); });
} }
}); });
+2 -2
View File
@@ -57,8 +57,8 @@ function popoverSrv($compile, $rootScope, $timeout) {
openOn: options.openOn, openOn: options.openOn,
hoverCloseDelay: 200, hoverCloseDelay: 200,
tetherOptions: { tetherOptions: {
constraints: [{to: 'scrollParent', attachment: "none both"}] constraints: [{ to: 'scrollParent', attachment: 'together' }],
} },
}); });
drop.on('close', () => { drop.on('close', () => {
+17 -11
View File
@@ -18,7 +18,7 @@ export class SearchSrv {
return this.queryForRecentDashboards().then(result => { return this.queryForRecentDashboards().then(result => {
if (result.length > 0) { if (result.length > 0) {
sections['recent'] = { sections['recent'] = {
title: 'Recent Boards', title: 'Recent',
icon: 'fa fa-clock-o', icon: 'fa fa-clock-o',
score: -1, score: -1,
removable: true, removable: true,
@@ -37,9 +37,11 @@ export class SearchSrv {
} }
return this.backendSrv.search({ dashboardIds: dashIds }).then(result => { return this.backendSrv.search({ dashboardIds: dashIds }).then(result => {
return dashIds.map(orderId => { return dashIds
return _.find(result, { id: orderId }); .map(orderId => {
}).filter(hit => hit && !hit.isStarred) return _.find(result, { id: orderId });
})
.filter(hit => hit && !hit.isStarred)
.map(hit => { .map(hit => {
return this.transformToViewModel(hit); return this.transformToViewModel(hit);
}); });
@@ -71,10 +73,10 @@ export class SearchSrv {
return Promise.resolve(); return Promise.resolve();
} }
return this.backendSrv.search({starred: true, limit: 5}).then(result => { return this.backendSrv.search({ starred: true, limit: 5 }).then(result => {
if (result.length > 0) { if (result.length > 0) {
sections['starred'] = { sections['starred'] = {
title: 'Starred Boards', title: 'Starred',
icon: 'fa fa-star-o', icon: 'fa fa-star-o',
score: -2, score: -2,
expanded: this.starredIsOpen, expanded: this.starredIsOpen,
@@ -94,8 +96,10 @@ export class SearchSrv {
let sections: any = {}; let sections: any = {};
let promises = []; let promises = [];
let query = _.clone(options); let query = _.clone(options);
let hasFilters = options.query || let hasFilters =
(options.tag && options.tag.length > 0) || options.starred || options.query ||
(options.tag && options.tag.length > 0) ||
options.starred ||
(options.folderIds && options.folderIds.length > 0); (options.folderIds && options.folderIds.length > 0);
if (!options.skipRecent && !hasFilters) { if (!options.skipRecent && !hasFilters) {
@@ -111,9 +115,11 @@ export class SearchSrv {
query.folderIds = [0]; query.folderIds = [0];
} }
promises.push(this.backendSrv.search(query).then(results => { promises.push(
return this.handleSearchResult(sections, results); this.backendSrv.search(query).then(results => {
})); return this.handleSearchResult(sections, results);
})
);
return this.$q.all(promises).then(() => { return this.$q.all(promises).then(() => {
return _.sortBy(_.values(sections), 'score'); return _.sortBy(_.values(sections), 'score');
+1 -2
View File
@@ -7,8 +7,7 @@ export class Timer {
timers = []; timers = [];
/** @ngInject */ /** @ngInject */
constructor(private $timeout) { constructor(private $timeout) {}
}
register(promise) { register(promise) {
this.timers.push(promise); this.timers.push(promise);
+2 -3
View File
@@ -7,8 +7,7 @@ export class UtilSrv {
modalScope: any; modalScope: any;
/** @ngInject */ /** @ngInject */
constructor(private $rootScope, private $modal) { constructor(private $rootScope, private $modal) {}
}
init() { init() {
appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope); appEvents.on('show-modal', this.showModal.bind(this), this.$rootScope);
@@ -43,7 +42,7 @@ export class UtilSrv {
show: false, show: false,
scope: this.modalScope, scope: this.modalScope,
keyboard: false, keyboard: false,
backdrop: options.backdrop backdrop: options.backdrop,
}); });
Promise.resolve(modal).then(function(modalEl) { Promise.resolve(modal).then(function(modalEl) {
+21 -11
View File
@@ -1,4 +1,10 @@
import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common'; import {
describe,
beforeEach,
it,
expect,
angularMocks,
} from 'test/lib/common';
import 'app/core/services/backend_srv'; import 'app/core/services/backend_srv';
describe('backend_srv', function() { describe('backend_srv', function() {
@@ -7,20 +13,24 @@ describe('backend_srv', function() {
beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.core'));
beforeEach(angularMocks.module('grafana.services')); beforeEach(angularMocks.module('grafana.services'));
beforeEach(angularMocks.inject(function ($httpBackend, $http, backendSrv) { beforeEach(
_httpBackend = $httpBackend; angularMocks.inject(function($httpBackend, $http, backendSrv) {
_backendSrv = backendSrv; _httpBackend = $httpBackend;
})); _backendSrv = backendSrv;
})
);
describe('when handling errors', function() { describe('when handling errors', function() {
it('should return the http status code', function(done) { it('should return the http status code', function(done) {
_httpBackend.whenGET('gateway-error').respond(502); _httpBackend.whenGET('gateway-error').respond(502);
_backendSrv.datasourceRequest({ _backendSrv
url: 'gateway-error' .datasourceRequest({
}).catch(function(err) { url: 'gateway-error',
expect(err.status).to.be(502); })
done(); .catch(function(err) {
}); expect(err.status).to.be(502);
done();
});
_httpBackend.flush(); _httpBackend.flush();
}); });
}); });
+38 -20
View File
@@ -4,9 +4,9 @@ import * as dateMath from 'app/core/utils/datemath';
import moment from 'moment'; import moment from 'moment';
import _ from 'lodash'; import _ from 'lodash';
describe("DateMath", () => { describe('DateMath', () => {
var spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y']; var spans = ['s', 'm', 'h', 'd', 'w', 'M', 'y'];
var anchor = '2014-01-01T06:06:06.666Z'; var anchor = '2014-01-01T06:06:06.666Z';
var unix = moment(anchor).valueOf(); var unix = moment(anchor).valueOf();
var format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; var format = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
var clock; var clock;
@@ -20,9 +20,12 @@ describe("DateMath", () => {
expect(dateMath.parse('now&1d')).toBe(undefined); expect(dateMath.parse('now&1d')).toBe(undefined);
}); });
it('should return undefined if I pass a unit besides' + spans.toString(), () => { it(
expect(dateMath.parse('now+5f')).toBe(undefined); 'should return undefined if I pass a unit besides' + spans.toString(),
}); () => {
expect(dateMath.parse('now+5f')).toBe(undefined);
}
);
it('should return undefined if rounding unit is not 1', () => { it('should return undefined if rounding unit is not 1', () => {
expect(dateMath.parse('now/2y')).toBe(undefined); expect(dateMath.parse('now/2y')).toBe(undefined);
@@ -35,7 +38,7 @@ describe("DateMath", () => {
}); });
}); });
it("now/d should set to start of current day", () => { it('now/d should set to start of current day', () => {
var expected = new Date(); var expected = new Date();
expected.setHours(0); expected.setHours(0);
expected.setMinutes(0); expected.setMinutes(0);
@@ -46,9 +49,19 @@ describe("DateMath", () => {
expect(startOfDay).toBe(expected.getTime()); expect(startOfDay).toBe(expected.getTime());
}); });
it("now/d on a utc dashboard should be start of the current day in UTC time", () => { it('now/d on a utc dashboard should be start of the current day in UTC time', () => {
var today = new Date(); var today = new Date();
var expected = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), 0, 0, 0, 0)); var expected = new Date(
Date.UTC(
today.getUTCFullYear(),
today.getUTCMonth(),
today.getUTCDate(),
0,
0,
0,
0
)
);
var startOfDay = dateMath.parse('now/d', false, 'utc').valueOf(); var startOfDay = dateMath.parse('now/d', false, 'utc').valueOf();
expect(startOfDay).toBe(expected.getTime()); expect(startOfDay).toBe(expected.getTime());
@@ -64,16 +77,20 @@ describe("DateMath", () => {
anchored = moment(anchor); anchored = moment(anchor);
}); });
_.each(spans, (span) => { _.each(spans, span => {
var nowEx = 'now-5' + span; var nowEx = 'now-5' + span;
var thenEx = anchor + '||-5' + span; var thenEx = anchor + '||-5' + span;
it('should return 5' + span + ' ago', () => { it('should return 5' + span + ' ago', () => {
expect(dateMath.parse(nowEx).format(format)).toEqual(now.subtract(5, span).format(format)); expect(dateMath.parse(nowEx).format(format)).toEqual(
now.subtract(5, span).format(format)
);
}); });
it('should return 5' + span + ' before ' + anchor, () => { it('should return 5' + span + ' before ' + anchor, () => {
expect(dateMath.parse(thenEx).format(format)).toEqual(anchored.subtract(5, span).format(format)); expect(dateMath.parse(thenEx).format(format)).toEqual(
anchored.subtract(5, span).format(format)
);
}); });
}); });
@@ -90,13 +107,17 @@ describe("DateMath", () => {
now = moment(); now = moment();
}); });
_.each(spans, (span) => { _.each(spans, span => {
it('should round now to the beginning of the ' + span, function () { it('should round now to the beginning of the ' + span, function() {
expect(dateMath.parse('now/' + span).format(format)).toEqual(now.startOf(span).format(format)); expect(dateMath.parse('now/' + span).format(format)).toEqual(
now.startOf(span).format(format)
);
}); });
it('should round now to the end of the ' + span, function () { it('should round now to the end of the ' + span, function() {
expect(dateMath.parse('now/' + span, true).format(format)).toEqual(now.endOf(span).format(format)); expect(dateMath.parse('now/' + span, true).format(format)).toEqual(
now.endOf(span).format(format)
);
}); });
}); });
@@ -130,7 +151,4 @@ describe("DateMath", () => {
expect(date).toEqual(undefined); expect(date).toEqual(undefined);
}); });
}); });
}); });
+9 -9
View File
@@ -1,9 +1,7 @@
import {Emitter} from '../utils/emitter'; import { Emitter } from '../utils/emitter';
describe("Emitter", () => {
describe('Emitter', () => {
describe('given 2 subscribers', () => { describe('given 2 subscribers', () => {
it('should notfiy subscribers', () => { it('should notfiy subscribers', () => {
var events = new Emitter(); var events = new Emitter();
var sub1Called = false; var sub1Called = false;
@@ -45,20 +43,22 @@ describe("Emitter", () => {
events.on('test', () => { events.on('test', () => {
sub1Called++; sub1Called++;
throw {message: "hello"}; throw { message: 'hello' };
}); });
events.on('test', () => { events.on('test', () => {
sub2Called++; sub2Called++;
}); });
try { events.emit('test', null); } catch (_) { } try {
try { events.emit('test', null); } catch (_) {} events.emit('test', null);
} catch (_) {}
try {
events.emit('test', null);
} catch (_) {}
expect(sub1Called).toBe(2); expect(sub1Called).toBe(2);
expect(sub2Called).toBe(0); expect(sub2Called).toBe(0);
}); });
}); });
}); });
+12 -12
View File
@@ -1,22 +1,22 @@
import flatten from 'app/core/utils/flatten'; import flatten from 'app/core/utils/flatten';
describe("flatten", () => { describe('flatten', () => {
it('should return flatten object', () => { it('should return flatten object', () => {
var flattened = flatten({ var flattened = flatten(
level1: 'level1-value', {
deeper: { level1: 'level1-value',
level2: 'level2-value',
deeper: { deeper: {
level3: 'level3-value' level2: 'level2-value',
} deeper: {
} level3: 'level3-value',
}, null); },
},
},
null
);
expect(flattened['level1']).toBe('level1-value'); expect(flattened['level1']).toBe('level1-value');
expect(flattened['deeper.level2']).toBe('level2-value'); expect(flattened['deeper.level2']).toBe('level2-value');
expect(flattened['deeper.deeper.level3']).toBe('level3-value'); expect(flattened['deeper.deeper.level3']).toBe('level3-value');
}); });
}); });
@@ -0,0 +1,23 @@
import { GlobalEventSrv } from 'app/core/services/global_event_srv';
import { beforeEach } from 'test/lib/common';
jest.mock('app/core/config', () => {
return {
appSubUrl: '/subUrl',
};
});
describe('GlobalEventSrv', () => {
let searchSrv;
beforeEach(() => {
searchSrv = new GlobalEventSrv(null, null, null);
});
describe('With /subUrl as appSubUrl', () => {
it('/subUrl should be stripped', () => {
const urlWithoutMaster = searchSrv.stripBaseFromUrl('/subUrl/grafana/');
expect(urlWithoutMaster).toBe('/grafana/');
});
});
});
+43 -35
View File
@@ -5,9 +5,7 @@ import moment from 'moment';
describe('unit format menu', function() { describe('unit format menu', function() {
var menu = kbn.getUnitFormats(); var menu = kbn.getUnitFormats();
menu.map(function(submenu) { menu.map(function(submenu) {
describe('submenu ' + submenu.text, function() { describe('submenu ' + submenu.text, function() {
it('should have a title', function() { it('should have a title', function() {
expect(typeof submenu.text).toBe('string'); expect(typeof submenu.text).toBe('string');
}); });
@@ -18,8 +16,12 @@ describe('unit format menu', function() {
submenu.submenu.map(function(entry) { submenu.submenu.map(function(entry) {
describe('entry ' + entry.text, function() { describe('entry ' + entry.text, function() {
it('should have a title', function() { expect(typeof entry.text).toBe('string'); }); it('should have a title', function() {
it('should have a format', function() { expect(typeof entry.value).toBe('string'); }); expect(typeof entry.text).toBe('string');
});
it('should have a format', function() {
expect(typeof entry.value).toBe('string');
});
it('should have a valid format', function() { it('should have a valid format', function() {
expect(typeof kbn.valueFormats[entry.value]).toBe('function'); expect(typeof kbn.valueFormats[entry.value]).toBe('function');
}); });
@@ -30,15 +32,14 @@ describe('unit format menu', function() {
}); });
function describeValueFormat(desc, value, tickSize, tickDecimals, result) { function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
describe('value format: ' + desc, function() { describe('value format: ' + desc, function() {
it('should translate ' + value + ' as ' + result, function() { it('should translate ' + value + ' as ' + result, function() {
var scaledDecimals = tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10); var scaledDecimals =
tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10);
var str = kbn.valueFormats[desc](value, tickDecimals, scaledDecimals); var str = kbn.valueFormats[desc](value, tickDecimals, scaledDecimals);
expect(str).toBe(result); expect(str).toBe(result);
}); });
}); });
} }
describeValueFormat('ms', 0.0024, 0.0005, 4, '0.0024 ms'); describeValueFormat('ms', 0.0024, 0.0005, 4, '0.0024 ms');
@@ -53,7 +54,7 @@ describeValueFormat('none', 2.75e-10, 0, 10, '3e-10');
describeValueFormat('none', 0, 0, 2, '0'); describeValueFormat('none', 0, 0, 2, '0');
describeValueFormat('dB', 10, 1000, 2, '10.00 dB'); describeValueFormat('dB', 10, 1000, 2, '10.00 dB');
describeValueFormat('percent', 0, 0, 0, '0%'); describeValueFormat('percent', 0, 0, 0, '0%');
describeValueFormat('percent', 53, 0, 1, '53.0%'); describeValueFormat('percent', 53, 0, 1, '53.0%');
describeValueFormat('percentunit', 0.0, 0, 0, '0%'); describeValueFormat('percentunit', 0.0, 0, 0, '0%');
describeValueFormat('percentunit', 0.278, 0, 1, '27.8%'); describeValueFormat('percentunit', 0.278, 0, 1, '27.8%');
@@ -63,7 +64,7 @@ describeValueFormat('currencyUSD', 7.42, 10000, 2, '$7.42');
describeValueFormat('currencyUSD', 1532.82, 1000, 1, '$1.53K'); describeValueFormat('currencyUSD', 1532.82, 1000, 1, '$1.53K');
describeValueFormat('currencyUSD', 18520408.7, 10000000, 0, '$19M'); describeValueFormat('currencyUSD', 18520408.7, 10000000, 0, '$19M');
describeValueFormat('bytes', -1.57e+308, -1.57e+308, 2, 'NA'); describeValueFormat('bytes', -1.57e308, -1.57e308, 2, 'NA');
describeValueFormat('ns', 25, 1, 0, '25 ns'); describeValueFormat('ns', 25, 1, 0, '25 ns');
describeValueFormat('ns', 2558, 50, 0, '2.56 µs'); describeValueFormat('ns', 2558, 50, 0, '2.56 µs');
@@ -109,7 +110,7 @@ describe('date time formats', function() {
it('should format as iso date and skip date when today', function() { it('should format as iso date and skip date when today', function() {
var now = moment(); var now = moment();
var str = kbn.valueFormats.dateTimeAsIso(now.valueOf(), 1); var str = kbn.valueFormats.dateTimeAsIso(now.valueOf(), 1);
expect(str).toBe(now.format("HH:mm:ss")); expect(str).toBe(now.format('HH:mm:ss'));
}); });
it('should format as US date', function() { it('should format as US date', function() {
@@ -120,7 +121,7 @@ describe('date time formats', function() {
it('should format as US date and skip date when today', function() { it('should format as US date and skip date when today', function() {
var now = moment(); var now = moment();
var str = kbn.valueFormats.dateTimeAsUS(now.valueOf(), 1); var str = kbn.valueFormats.dateTimeAsUS(now.valueOf(), 1);
expect(str).toBe(now.format("h:mm:ss a")); expect(str).toBe(now.format('h:mm:ss a'));
}); });
it('should format as from now with days', function() { it('should format as from now with days', function() {
@@ -190,7 +191,7 @@ describe('calculateInterval', function() {
}); });
it('fixed user min interval', function() { it('fixed user min interval', function() {
var range = {from: dateMath.parse('now-10m'), to: dateMath.parse('now')}; var range = { from: dateMath.parse('now-10m'), to: dateMath.parse('now') };
var res = kbn.calculateInterval(range, 1600, '10s'); var res = kbn.calculateInterval(range, 1600, '10s');
expect(res.interval).toBe('10s'); expect(res.interval).toBe('10s');
expect(res.intervalMs).toBe(10000); expect(res.intervalMs).toBe(10000);
@@ -203,7 +204,7 @@ describe('calculateInterval', function() {
}); });
it('large time range and user low limit', function() { it('large time range and user low limit', function() {
var range = {from: dateMath.parse('now-14d'), to: dateMath.parse('now')}; var range = { from: dateMath.parse('now-14d'), to: dateMath.parse('now') };
var res = kbn.calculateInterval(range, 1000, '>10s'); var res = kbn.calculateInterval(range, 1000, '>10s');
expect(res.interval).toBe('20m'); expect(res.interval).toBe('20m');
}); });
@@ -222,7 +223,10 @@ describe('calculateInterval', function() {
}); });
it('86399s 1 resolution', function() { it('86399s 1 resolution', function() {
var range = { from: dateMath.parse('now-86390s'), to: dateMath.parse('now') }; var range = {
from: dateMath.parse('now-86390s'),
to: dateMath.parse('now'),
};
var res = kbn.calculateInterval(range, 1, null); var res = kbn.calculateInterval(range, 1, null);
expect(res.interval).toBe('12h'); expect(res.interval).toBe('12h');
expect(res.intervalMs).toBe(43200000); expect(res.intervalMs).toBe(43200000);
@@ -254,11 +258,11 @@ describe('hex', function() {
describe('hex 0x', function() { describe('hex 0x', function() {
it('positive integeter', function() { it('positive integeter', function() {
var str = kbn.valueFormats.hex0x(7999,0); var str = kbn.valueFormats.hex0x(7999, 0);
expect(str).toBe('0x1F3F'); expect(str).toBe('0x1F3F');
}); });
it('negative integer', function() { it('negative integer', function() {
var str = kbn.valueFormats.hex0x(-584,0); var str = kbn.valueFormats.hex0x(-584, 0);
expect(str).toBe('-0x248'); expect(str).toBe('-0x248');
}); });
it('null', function() { it('null', function() {
@@ -277,71 +281,75 @@ describe('hex 0x', function() {
describe('duration', function() { describe('duration', function() {
it('null', function() { it('null', function() {
var str = kbn.toDuration(null, 0, "millisecond"); var str = kbn.toDuration(null, 0, 'millisecond');
expect(str).toBe(''); expect(str).toBe('');
}); });
it('0 milliseconds', function() { it('0 milliseconds', function() {
var str = kbn.toDuration(0, 0, "millisecond"); var str = kbn.toDuration(0, 0, 'millisecond');
expect(str).toBe('0 milliseconds'); expect(str).toBe('0 milliseconds');
}); });
it('1 millisecond', function() { it('1 millisecond', function() {
var str = kbn.toDuration(1, 0, "millisecond"); var str = kbn.toDuration(1, 0, 'millisecond');
expect(str).toBe('1 millisecond'); expect(str).toBe('1 millisecond');
}); });
it('-1 millisecond', function() { it('-1 millisecond', function() {
var str = kbn.toDuration(-1, 0, "millisecond"); var str = kbn.toDuration(-1, 0, 'millisecond');
expect(str).toBe('1 millisecond ago'); expect(str).toBe('1 millisecond ago');
}); });
it('seconds', function() { it('seconds', function() {
var str = kbn.toDuration(1, 0, "second"); var str = kbn.toDuration(1, 0, 'second');
expect(str).toBe('1 second'); expect(str).toBe('1 second');
}); });
it('minutes', function() { it('minutes', function() {
var str = kbn.toDuration(1, 0, "minute"); var str = kbn.toDuration(1, 0, 'minute');
expect(str).toBe('1 minute'); expect(str).toBe('1 minute');
}); });
it('hours', function() { it('hours', function() {
var str = kbn.toDuration(1, 0, "hour"); var str = kbn.toDuration(1, 0, 'hour');
expect(str).toBe('1 hour'); expect(str).toBe('1 hour');
}); });
it('days', function() { it('days', function() {
var str = kbn.toDuration(1, 0, "day"); var str = kbn.toDuration(1, 0, 'day');
expect(str).toBe('1 day'); expect(str).toBe('1 day');
}); });
it('weeks', function() { it('weeks', function() {
var str = kbn.toDuration(1, 0, "week"); var str = kbn.toDuration(1, 0, 'week');
expect(str).toBe('1 week'); expect(str).toBe('1 week');
}); });
it('months', function() { it('months', function() {
var str = kbn.toDuration(1, 0, "month"); var str = kbn.toDuration(1, 0, 'month');
expect(str).toBe('1 month'); expect(str).toBe('1 month');
}); });
it('years', function() { it('years', function() {
var str = kbn.toDuration(1, 0, "year"); var str = kbn.toDuration(1, 0, 'year');
expect(str).toBe('1 year'); expect(str).toBe('1 year');
}); });
it('decimal days', function() { it('decimal days', function() {
var str = kbn.toDuration(1.5, 2, "day"); var str = kbn.toDuration(1.5, 2, 'day');
expect(str).toBe('1 day, 12 hours, 0 minutes'); expect(str).toBe('1 day, 12 hours, 0 minutes');
}); });
it('decimal months', function() { it('decimal months', function() {
var str = kbn.toDuration(1.5, 3, "month"); var str = kbn.toDuration(1.5, 3, 'month');
expect(str).toBe('1 month, 2 weeks, 1 day, 0 hours'); expect(str).toBe('1 month, 2 weeks, 1 day, 0 hours');
}); });
it('no decimals', function() { it('no decimals', function() {
var str = kbn.toDuration(38898367008, 0, "millisecond"); var str = kbn.toDuration(38898367008, 0, 'millisecond');
expect(str).toBe('1 year'); expect(str).toBe('1 year');
}); });
it('1 decimal', function() { it('1 decimal', function() {
var str = kbn.toDuration(38898367008, 1, "millisecond"); var str = kbn.toDuration(38898367008, 1, 'millisecond');
expect(str).toBe('1 year, 2 months'); expect(str).toBe('1 year, 2 months');
}); });
it('too many decimals', function() { it('too many decimals', function() {
var str = kbn.toDuration(38898367008, 20, "millisecond"); var str = kbn.toDuration(38898367008, 20, 'millisecond');
expect(str).toBe('1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds'); expect(str).toBe(
'1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds'
);
}); });
it('floating point error', function() { it('floating point error', function() {
var str = kbn.toDuration(36993906007, 8, "millisecond"); var str = kbn.toDuration(36993906007, 8, 'millisecond');
expect(str).toBe('1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds'); expect(str).toBe(
'1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds'
);
}); });
}); });
+96 -128
View File
@@ -10,43 +10,43 @@ describe('ManageDashboards', () => {
const response = [ const response = [
{ {
id: 410, id: 410,
title: "afolder", title: 'afolder',
type: "dash-folder", type: 'dash-folder',
items: [ items: [
{ {
id: 399, id: 399,
title: "Dashboard Test", title: 'Dashboard Test',
url: "dashboard/db/dashboard-test", url: 'dashboard/db/dashboard-test',
icon: 'fa fa-folder', icon: 'fa fa-folder',
tags: [], tags: [],
isStarred: false, isStarred: false,
folderId: 410, folderId: 410,
folderTitle: "afolder", folderTitle: 'afolder',
folderSlug: "afolder" folderSlug: 'afolder',
} },
],
tags: [],
isStarred: false
},
{
id: 0,
title: "Root",
icon: 'fa fa-folder-open',
uri: "db/something-else",
type: "dash-db",
items: [
{
id: 500,
title: "Dashboard Test",
url: "dashboard/db/dashboard-test",
icon: 'fa fa-folder',
tags: [],
isStarred: false
}
], ],
tags: [], tags: [],
isStarred: false, isStarred: false,
} },
{
id: 0,
title: 'Root',
icon: 'fa fa-folder-open',
uri: 'db/something-else',
type: 'dash-db',
items: [
{
id: 500,
title: 'Dashboard Test',
url: 'dashboard/db/dashboard-test',
icon: 'fa fa-folder',
tags: [],
isStarred: false,
},
],
tags: [],
isStarred: false,
},
]; ];
ctrl = createCtrlWithStubs(response); ctrl = createCtrlWithStubs(response);
return ctrl.getDashboards(); return ctrl.getDashboards();
@@ -67,24 +67,24 @@ describe('ManageDashboards', () => {
const response = [ const response = [
{ {
id: 410, id: 410,
title: "afolder", title: 'afolder',
type: "dash-folder", type: 'dash-folder',
items: [ items: [
{ {
id: 399, id: 399,
title: "Dashboard Test", title: 'Dashboard Test',
url: "dashboard/db/dashboard-test", url: 'dashboard/db/dashboard-test',
icon: 'fa fa-folder', icon: 'fa fa-folder',
tags: [], tags: [],
isStarred: false, isStarred: false,
folderId: 410, folderId: 410,
folderTitle: "afolder", folderTitle: 'afolder',
folderSlug: "afolder" folderSlug: 'afolder',
} },
], ],
tags: [], tags: [],
isStarred: false isStarred: false,
} },
]; ];
ctrl = createCtrlWithStubs(response); ctrl = createCtrlWithStubs(response);
ctrl.folderId = 410; ctrl.folderId = 410;
@@ -106,26 +106,26 @@ describe('ManageDashboards', () => {
items: [ items: [
{ {
id: 399, id: 399,
title: "Dashboard Test", title: 'Dashboard Test',
url: "dashboard/db/dashboard-test", url: 'dashboard/db/dashboard-test',
icon: 'fa fa-folder', icon: 'fa fa-folder',
tags: [], tags: [],
isStarred: false, isStarred: false,
folderId: 410, folderId: 410,
folderTitle: "afolder", folderTitle: 'afolder',
folderSlug: "afolder" folderSlug: 'afolder',
}, },
{ {
id: 500, id: 500,
title: "Dashboard Test", title: 'Dashboard Test',
url: "dashboard/db/dashboard-test", url: 'dashboard/db/dashboard-test',
icon: 'fa fa-folder', icon: 'fa fa-folder',
tags: [], tags: [],
folderId: 499, folderId: 499,
isStarred: false isStarred: false,
} },
] ],
} },
]; ];
ctrl = createCtrlWithStubs(response); ctrl = createCtrlWithStubs(response);
@@ -261,18 +261,14 @@ describe('ManageDashboards', () => {
ctrl.sections = [ ctrl.sections = [
{ {
id: 1, id: 1,
items: [ items: [{ id: 2, checked: false }],
{ id: 2, checked: false } checked: false,
],
checked: false
}, },
{ {
id: 0, id: 0,
items: [ items: [{ id: 3, checked: false }],
{ id: 3, checked: false } checked: false,
], },
checked: false
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
}); });
@@ -313,18 +309,14 @@ describe('ManageDashboards', () => {
ctrl.sections = [ ctrl.sections = [
{ {
id: 1, id: 1,
items: [ items: [{ id: 2, checked: true }],
{ id: 2, checked: true } checked: true,
],
checked: true
}, },
{ {
id: 0, id: 0,
items: [ items: [{ id: 3, checked: true }],
{ id: 3, checked: true } checked: true,
], },
checked: true
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
}); });
@@ -366,19 +358,15 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: false }],
{ id: 2, checked: false } checked: false,
],
checked: false
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: true }],
{ id: 3, checked: true } checked: false,
], },
checked: false
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
}); });
@@ -398,19 +386,15 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: true }],
{ id: 2, checked: true } checked: false,
],
checked: false
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: false }],
{ id: 3, checked: false } checked: false,
], },
checked: false
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
@@ -431,19 +415,15 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: true }],
{ id: 2, checked: true } checked: false,
],
checked: false
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: true }],
{ id: 3, checked: true } checked: false,
], },
checked: false
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
@@ -464,27 +444,21 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: false }],
{ id: 2, checked: false } checked: true,
],
checked: true
}, },
{ {
id: 3, id: 3,
title: 'folder', title: 'folder',
items: [ items: [{ id: 4, checked: true }],
{ id: 4, checked: true } checked: false,
],
checked: false
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: false }],
{ id: 3, checked: false } checked: false,
], },
checked: false
}
]; ];
ctrl.selectionChanged(); ctrl.selectionChanged();
@@ -510,29 +484,23 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: true, slug: 'folder-dash' }],
{ id: 2, checked: true, slug: 'folder-dash' }
],
checked: true, checked: true,
slug: 'folder' slug: 'folder',
}, },
{ {
id: 3, id: 3,
title: 'folder-2', title: 'folder-2',
items: [ items: [{ id: 3, checked: true, slug: 'folder-2-dash' }],
{ id: 3, checked: true, slug: 'folder-2-dash' }
],
checked: false, checked: false,
slug: 'folder-2' slug: 'folder-2',
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: true, slug: 'root-dash' }],
{ id: 3, checked: true, slug: 'root-dash' } checked: true,
], },
checked: true
}
]; ];
toBeDeleted = ctrl.getFoldersAndDashboardsToDelete(); toBeDeleted = ctrl.getFoldersAndDashboardsToDelete();
@@ -567,20 +535,16 @@ describe('ManageDashboards', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, checked: true, slug: 'dash' }],
{ id: 2, checked: true, slug: 'dash' }
],
checked: false, checked: false,
slug: 'folder' slug: 'folder',
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, checked: true, slug: 'dash-2' }],
{ id: 3, checked: true, slug: 'dash-2' } checked: false,
], },
checked: false
}
]; ];
}); });
@@ -600,8 +564,12 @@ function createCtrlWithStubs(searchResponse: any, tags?: any) {
}, },
getDashboardTags: () => { getDashboardTags: () => {
return q.resolve(tags || []); return q.resolve(tags || []);
} },
}; };
return new ManageDashboardsCtrl({}, { getNav: () => { } }, <SearchSrv>searchSrvStub); return new ManageDashboardsCtrl(
{},
{ getNav: () => {} },
<SearchSrv>searchSrvStub
);
} }
+17 -10
View File
@@ -1,10 +1,10 @@
import {OrgSwitchCtrl} from '../components/org_switcher'; import { OrgSwitchCtrl } from '../components/org_switcher';
import q from 'q'; import q from 'q';
jest.mock('app/core/services/context_srv', () => ({ jest.mock('app/core/services/context_srv', () => ({
contextSrv: { contextSrv: {
user: {orgId: 1} user: { orgId: 1 },
} },
})); }));
describe('OrgSwitcher', () => { describe('OrgSwitcher', () => {
@@ -13,18 +13,23 @@ describe('OrgSwitcher', () => {
let expectedUsingUrl; let expectedUsingUrl;
beforeEach(() => { beforeEach(() => {
const backendSrvStub: any = { const backendSrvStub: any = {
get: (url) => { return q.resolve([]); }, get: url => {
post: (url) => { expectedUsingUrl = url; return q.resolve({}); } return q.resolve([]);
},
post: url => {
expectedUsingUrl = url;
return q.resolve({});
},
}; };
const orgSwitcherCtrl = new OrgSwitchCtrl(backendSrvStub); const orgSwitcherCtrl = new OrgSwitchCtrl(backendSrvStub);
orgSwitcherCtrl.getWindowLocationHref = () => 'http://localhost:3000?orgId=1&from=now-3h&to=now'; orgSwitcherCtrl.getWindowLocationHref = () =>
orgSwitcherCtrl.setWindowLocationHref = (href) => expectedHref = href; 'http://localhost:3000?orgId=1&from=now-3h&to=now';
orgSwitcherCtrl.setWindowLocationHref = href => (expectedHref = href);
return orgSwitcherCtrl.setUsingOrg({orgId: 2}); return orgSwitcherCtrl.setUsingOrg({ orgId: 2 });
}); });
it('should switch orgId in call to backend', () => { it('should switch orgId in call to backend', () => {
@@ -32,7 +37,9 @@ describe('OrgSwitcher', () => {
}); });
it('should switch orgId in url', () => { it('should switch orgId in url', () => {
expect(expectedHref).toBe('http://localhost:3000?orgId=2&from=now-3h&to=now'); expect(expectedHref).toBe(
'http://localhost:3000?orgId=2&from=now-3h&to=now'
);
}); });
}); });
}); });
+29 -17
View File
@@ -2,17 +2,19 @@ import * as rangeUtil from 'app/core/utils/rangeutil';
import _ from 'lodash'; import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
describe("rangeUtil", () => { describe('rangeUtil', () => {
describe('Can get range grouped list of ranges', () => {
describe("Can get range grouped list of ranges", () => {
it('when custom settings should return default range list', () => { it('when custom settings should return default range list', () => {
var groups = rangeUtil.getRelativeTimesList({time_options: []}, 'Last 5 minutes'); var groups = rangeUtil.getRelativeTimesList(
{ time_options: [] },
'Last 5 minutes'
);
expect(_.keys(groups).length).toBe(4); expect(_.keys(groups).length).toBe(4);
expect(groups[3][0].active).toBe(true); expect(groups[3][0].active).toBe(true);
}); });
}); });
describe("Can get range text described", () => { describe('Can get range text described', () => {
it('should handle simple old expression with only amount and unit', () => { it('should handle simple old expression with only amount and unit', () => {
var info = rangeUtil.describeTextRange('5m'); var info = rangeUtil.describeTextRange('5m');
expect(info.display).toBe('Last 5 minutes'); expect(info.display).toBe('Last 5 minutes');
@@ -57,52 +59,62 @@ describe("rangeUtil", () => {
}); });
}); });
describe("Can get date range described", () => { describe('Can get date range described', () => {
it('Date range with simple ranges', () => { it('Date range with simple ranges', () => {
var text = rangeUtil.describeTimeRange({from: 'now-1h', to: 'now'}); var text = rangeUtil.describeTimeRange({ from: 'now-1h', to: 'now' });
expect(text).toBe('Last 1 hour'); expect(text).toBe('Last 1 hour');
}); });
it('Date range with rounding ranges', () => { it('Date range with rounding ranges', () => {
var text = rangeUtil.describeTimeRange({from: 'now/d+6h', to: 'now'}); var text = rangeUtil.describeTimeRange({ from: 'now/d+6h', to: 'now' });
expect(text).toBe('now/d+6h to now'); expect(text).toBe('now/d+6h to now');
}); });
it('Date range with absolute to now', () => { it('Date range with absolute to now', () => {
var text = rangeUtil.describeTimeRange({from: moment([2014,10,10,2,3,4]), to: 'now'}); var text = rangeUtil.describeTimeRange({
from: moment([2014, 10, 10, 2, 3, 4]),
to: 'now',
});
expect(text).toBe('Nov 10, 2014 02:03:04 to a few seconds ago'); expect(text).toBe('Nov 10, 2014 02:03:04 to a few seconds ago');
}); });
it('Date range with absolute to relative', () => { it('Date range with absolute to relative', () => {
var text = rangeUtil.describeTimeRange({from: moment([2014,10,10,2,3,4]), to: 'now-1d'}); var text = rangeUtil.describeTimeRange({
from: moment([2014, 10, 10, 2, 3, 4]),
to: 'now-1d',
});
expect(text).toBe('Nov 10, 2014 02:03:04 to a day ago'); expect(text).toBe('Nov 10, 2014 02:03:04 to a day ago');
}); });
it('Date range with relative to absolute', () => { it('Date range with relative to absolute', () => {
var text = rangeUtil.describeTimeRange({from: 'now-7d', to: moment([2014,10,10,2,3,4])}); var text = rangeUtil.describeTimeRange({
from: 'now-7d',
to: moment([2014, 10, 10, 2, 3, 4]),
});
expect(text).toBe('7 days ago to Nov 10, 2014 02:03:04'); expect(text).toBe('7 days ago to Nov 10, 2014 02:03:04');
}); });
it('Date range with non matching default ranges', () => { it('Date range with non matching default ranges', () => {
var text = rangeUtil.describeTimeRange({from: 'now-13h', to: 'now'}); var text = rangeUtil.describeTimeRange({ from: 'now-13h', to: 'now' });
expect(text).toBe('Last 13 hours'); expect(text).toBe('Last 13 hours');
}); });
it('Date range with from and to both are in now-* format', () => { it('Date range with from and to both are in now-* format', () => {
var text = rangeUtil.describeTimeRange({from: 'now-6h', to: 'now-3h'}); var text = rangeUtil.describeTimeRange({ from: 'now-6h', to: 'now-3h' });
expect(text).toBe('now-6h to now-3h'); expect(text).toBe('now-6h to now-3h');
}); });
it('Date range with from and to both are either in now-* or now/* format', () => { it('Date range with from and to both are either in now-* or now/* format', () => {
var text = rangeUtil.describeTimeRange({from: 'now/d+6h', to: 'now-3h'}); var text = rangeUtil.describeTimeRange({
from: 'now/d+6h',
to: 'now-3h',
});
expect(text).toBe('now/d+6h to now-3h'); expect(text).toBe('now/d+6h to now-3h');
}); });
it('Date range with from and to both are either in now-* or now+* format', () => { it('Date range with from and to both are either in now-* or now+* format', () => {
var text = rangeUtil.describeTimeRange({from: 'now-6h', to: 'now+1h'}); var text = rangeUtil.describeTimeRange({ from: 'now-6h', to: 'now+1h' });
expect(text).toBe('now-6h to now+1h'); expect(text).toBe('now-6h to now+1h');
}); });
}); });
}); });
+19 -26
View File
@@ -4,9 +4,14 @@ import { SearchSrv } from '../services/search_srv';
describe('SearchCtrl', () => { describe('SearchCtrl', () => {
const searchSrvStub = { const searchSrvStub = {
search: (options: any) => {}, search: (options: any) => {},
getDashboardTags: () => {} getDashboardTags: () => {},
}; };
let ctrl = new SearchCtrl({}, {}, {}, <SearchSrv>searchSrvStub, { onAppEvent: () => { } }); let ctrl = new SearchCtrl(
{ $on: () => {} },
{},
{},
<SearchSrv>searchSrvStub
);
describe('Given an empty result', () => { describe('Given an empty result', () => {
beforeEach(() => { beforeEach(() => {
@@ -45,19 +50,16 @@ describe('SearchCtrl', () => {
items: [], items: [],
selected: true, selected: true,
expanded: false, expanded: false,
toggle: (i) => i.expanded = !i.expanded toggle: i => (i.expanded = !i.expanded),
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, selected: false }, { id: 5, selected: false }],
{ id: 3, selected: false },
{ id: 5, selected: false }
],
selected: false, selected: false,
expanded: true, expanded: true,
toggle: (i) => i.expanded = !i.expanded toggle: i => (i.expanded = !i.expanded),
} },
]; ];
}); });
@@ -142,25 +144,19 @@ describe('SearchCtrl', () => {
{ {
id: 1, id: 1,
title: 'folder', title: 'folder',
items: [ items: [{ id: 2, selected: false }, { id: 4, selected: false }],
{ id: 2, selected: false },
{ id: 4, selected: false }
],
selected: true, selected: true,
expanded: false, expanded: false,
toggle: (i) => i.expanded = !i.expanded toggle: i => (i.expanded = !i.expanded),
}, },
{ {
id: 0, id: 0,
title: 'Root', title: 'Root',
items: [ items: [{ id: 3, selected: false }, { id: 5, selected: false }],
{ id: 3, selected: false },
{ id: 5, selected: false }
],
selected: false, selected: false,
expanded: true, expanded: true,
toggle: (i) => i.expanded = !i.expanded toggle: i => (i.expanded = !i.expanded),
} },
]; ];
}); });
@@ -252,14 +248,11 @@ describe('SearchCtrl', () => {
ctrl.results = [ ctrl.results = [
{ {
hideHeader: true, hideHeader: true,
items: [ items: [{ id: 3, selected: true }, { id: 5, selected: false }],
{ id: 3, selected: true },
{ id: 5, selected: false }
],
selected: false, selected: false,
expanded: true, expanded: true,
toggle: (i) => i.expanded = !i.expanded toggle: i => (i.expanded = !i.expanded),
} },
]; ];
}); });
+57 -10
View File
@@ -1,15 +1,23 @@
import { SearchResultsCtrl } from '../components/search/search_results'; import { SearchResultsCtrl } from '../components/search/search_results';
import { beforeEach, afterEach } from 'test/lib/common';
import appEvents from 'app/core/app_events';
jest.mock('app/core/app_events', () => {
return {
emit: jest.fn<any>(),
};
});
describe('SearchResultsCtrl', () => { describe('SearchResultsCtrl', () => {
let ctrl; let ctrl;
describe('when checking an item that is not checked', () => { describe('when checking an item that is not checked', () => {
let item = {checked: false}; let item = { checked: false };
let selectionChanged = false; let selectionChanged = false;
beforeEach(() => { beforeEach(() => {
ctrl = new SearchResultsCtrl({}); ctrl = new SearchResultsCtrl({});
ctrl.onSelectionChanged = () => selectionChanged = true; ctrl.onSelectionChanged = () => (selectionChanged = true);
ctrl.toggleSelection(item); ctrl.toggleSelection(item);
}); });
@@ -23,12 +31,12 @@ describe('SearchResultsCtrl', () => {
}); });
describe('when checking an item that is checked', () => { describe('when checking an item that is checked', () => {
let item = {checked: true}; let item = { checked: true };
let selectionChanged = false; let selectionChanged = false;
beforeEach(() => { beforeEach(() => {
ctrl = new SearchResultsCtrl({}); ctrl = new SearchResultsCtrl({});
ctrl.onSelectionChanged = () => selectionChanged = true; ctrl.onSelectionChanged = () => (selectionChanged = true);
ctrl.toggleSelection(item); ctrl.toggleSelection(item);
}); });
@@ -46,12 +54,12 @@ describe('SearchResultsCtrl', () => {
beforeEach(() => { beforeEach(() => {
ctrl = new SearchResultsCtrl({}); ctrl = new SearchResultsCtrl({});
ctrl.onTagSelected = (tag) => selectedTag = tag; ctrl.onTagSelected = tag => (selectedTag = tag);
ctrl.selectTag('tag-test'); ctrl.selectTag('tag-test');
}); });
it('should trigger tag selected callback', () => { it('should trigger tag selected callback', () => {
expect(selectedTag["$tag"]).toBe('tag-test'); expect(selectedTag['$tag']).toBe('tag-test');
}); });
}); });
@@ -60,11 +68,13 @@ describe('SearchResultsCtrl', () => {
beforeEach(() => { beforeEach(() => {
ctrl = new SearchResultsCtrl({}); ctrl = new SearchResultsCtrl({});
ctrl.onFolderExpanding = () => { folderExpanded = true; }; ctrl.onFolderExpanding = () => {
folderExpanded = true;
};
let folder = { let folder = {
expanded: false, expanded: false,
toggle: () => Promise.resolve(folder) toggle: () => Promise.resolve(folder),
}; };
ctrl.toggleFolderExpand(folder); ctrl.toggleFolderExpand(folder);
@@ -80,11 +90,13 @@ describe('SearchResultsCtrl', () => {
beforeEach(() => { beforeEach(() => {
ctrl = new SearchResultsCtrl({}); ctrl = new SearchResultsCtrl({});
ctrl.onFolderExpanding = () => { folderExpanded = true; }; ctrl.onFolderExpanding = () => {
folderExpanded = true;
};
let folder = { let folder = {
expanded: true, expanded: true,
toggle: () => Promise.resolve(folder) toggle: () => Promise.resolve(folder),
}; };
ctrl.toggleFolderExpand(folder); ctrl.toggleFolderExpand(folder);
@@ -94,4 +106,39 @@ describe('SearchResultsCtrl', () => {
expect(folderExpanded).toBeFalsy(); expect(folderExpanded).toBeFalsy();
}); });
}); });
describe('when clicking on a link in search result', () => {
const dashPath = 'dashboard/path';
const $location = { path: () => dashPath };
const appEventsMock = appEvents as any;
describe('with the same url as current path', () => {
beforeEach(() => {
ctrl = new SearchResultsCtrl($location);
const item = { url: dashPath };
ctrl.onItemClick(item);
});
it('should close the search', () => {
expect(appEventsMock.emit.mock.calls.length).toBe(1);
expect(appEventsMock.emit.mock.calls[0][0]).toBe('hide-dash-search');
});
});
describe('with a different url than current path', () => {
beforeEach(() => {
ctrl = new SearchResultsCtrl($location);
const item = { url: 'another/path' };
ctrl.onItemClick(item);
});
it('should do nothing', () => {
expect(appEventsMock.emit.mock.calls.length).toBe(0);
});
});
afterEach(() => {
appEventsMock.emit.mockClear();
});
});
}); });
+25 -21
View File
@@ -35,7 +35,10 @@ describe('SearchSrv', () => {
backendSrvMock.search = jest backendSrvMock.search = jest
.fn() .fn()
.mockReturnValueOnce( .mockReturnValueOnce(
Promise.resolve([{ id: 2, title: 'second but first' }, { id: 1, title: 'first but second' }]), Promise.resolve([
{ id: 2, title: 'second but first' },
{ id: 1, title: 'first but second' },
])
) )
.mockReturnValue(Promise.resolve([])); .mockReturnValue(Promise.resolve([]));
@@ -47,7 +50,7 @@ describe('SearchSrv', () => {
}); });
it('should include recent dashboards section', () => { it('should include recent dashboards section', () => {
expect(results[0].title).toBe('Recent Boards'); expect(results[0].title).toBe('Recent');
}); });
it('should return order decided by impressions store not api', () => { it('should return order decided by impressions store not api', () => {
@@ -62,11 +65,13 @@ describe('SearchSrv', () => {
backendSrvMock.search = jest backendSrvMock.search = jest
.fn() .fn()
.mockReturnValueOnce( .mockReturnValueOnce(
Promise.resolve([{ id: 2, title: 'two' }, { id: 1, title: 'one' }]), Promise.resolve([{ id: 2, title: 'two' }, { id: 1, title: 'one' }])
) )
.mockReturnValue(Promise.resolve([])); .mockReturnValue(Promise.resolve([]));
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([4, 5, 1, 2, 3]); impressionSrv.getDashboardOpened = jest
.fn()
.mockReturnValue([4, 5, 1, 2, 3]);
return searchSrv.search({ query: '' }).then(res => { return searchSrv.search({ query: '' }).then(res => {
results = res; results = res;
@@ -87,9 +92,7 @@ describe('SearchSrv', () => {
beforeEach(() => { beforeEach(() => {
backendSrvMock.search = jest backendSrvMock.search = jest
.fn() .fn()
.mockReturnValue(Promise.resolve([ .mockReturnValue(Promise.resolve([{ id: 1, title: 'starred' }]));
{id: 1, title: 'starred'}
]));
return searchSrv.search({ query: '' }).then(res => { return searchSrv.search({ query: '' }).then(res => {
results = res; results = res;
@@ -97,7 +100,7 @@ describe('SearchSrv', () => {
}); });
it('should include starred dashboards section', () => { it('should include starred dashboards section', () => {
expect(results[0].title).toBe('Starred Boards'); expect(results[0].title).toBe('Starred');
expect(results[0].items.length).toBe(1); expect(results[0].items.length).toBe(1);
}); });
}); });
@@ -108,30 +111,31 @@ describe('SearchSrv', () => {
beforeEach(() => { beforeEach(() => {
backendSrvMock.search = jest backendSrvMock.search = jest
.fn() .fn()
.mockReturnValueOnce(Promise.resolve([ .mockReturnValueOnce(
{id: 1, title: 'starred and recent', isStarred: true}, Promise.resolve([
{id: 2, title: 'recent'} { id: 1, title: 'starred and recent', isStarred: true },
])) { id: 2, title: 'recent' },
.mockReturnValue(Promise.resolve([ ])
{id: 1, title: 'starred and recent'} )
])); .mockReturnValue(
Promise.resolve([{ id: 1, title: 'starred and recent' }])
);
impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1,2]); impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
return searchSrv.search({ query: '' }).then(res => { return searchSrv.search({ query: '' }).then(res => {
results = res; results = res;
}); });
}); });
it('should not show starred in recent', () => { it('should not show starred in recent', () => {
expect(results[1].title).toBe('Recent Boards'); expect(results[1].title).toBe('Recent');
expect(results[1].items[0].title).toBe('recent'); expect(results[1].items[0].title).toBe('recent');
}); });
it('should show starred', () => { it('should show starred', () => {
expect(results[0].title).toBe('Starred Boards'); expect(results[0].title).toBe('Starred');
expect(results[0].items[0].title).toBe('starred and recent'); expect(results[0].items[0].title).toBe('starred and recent');
}); });
}); });
describe('with no query string and dashboards with folders returned', () => { describe('with no query string and dashboards with folders returned', () => {
@@ -165,7 +169,7 @@ describe('SearchSrv', () => {
id: 4, id: 4,
folderId: 1, folderId: 1,
}, },
]), ])
); );
return searchSrv.search({ query: '' }).then(res => { return searchSrv.search({ query: '' }).then(res => {
@@ -202,7 +206,7 @@ describe('SearchSrv', () => {
folderId: 1, folderId: 1,
folderTitle: 'folder1', folderTitle: 'folder1',
}, },
]), ])
); );
return searchSrv.search({ query: 'search' }).then(res => { return searchSrv.search({ query: 'search' }).then(res => {

Some files were not shown because too many files have changed in this diff Show More