From ca6dd7392312fb05c15a18d71aba4df09133ab1c Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Thu, 25 Oct 2018 18:17:05 +0800
Subject: [PATCH 001/770] Add match values into Dingding notification message
---
pkg/services/alerting/notifiers/dingding.go | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index 1ef085c82f1..9ad85d55004 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -1,6 +1,8 @@
package notifiers
import (
+ "fmt"
+
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
@@ -61,6 +63,10 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
message = title
}
+ for i, match := range evalContext.EvalMatches {
+ message += fmt.Sprintf("\\n%2d. %s value %s", i+1, match.Metric, match.Value)
+ }
+
bodyJSON, err := simplejson.NewJson([]byte(`{
"msgtype": "link",
"link": {
From 7f45afac63b93bacd73d6ada811b5db0178b1723 Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Thu, 25 Oct 2018 18:24:04 +0800
Subject: [PATCH 002/770] Split text template into variable
---
pkg/services/alerting/notifiers/dingding.go | 23 ++++++++++++---------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index 9ad85d55004..bf1b721f753 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -10,19 +10,21 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
)
-func init() {
- alerting.RegisterNotifier(&alerting.NotifierPlugin{
- Type: "dingding",
- Name: "DingDing",
- Description: "Sends HTTP POST request to DingDing",
- Factory: NewDingDingNotifier,
- OptionsTemplate: `
+const DingdingOptionsTemplate = `
DingDing settings
Url
- `,
+`
+
+func init() {
+ alerting.RegisterNotifier(&alerting.NotifierPlugin{
+ Type: "dingding",
+ Name: "DingDing",
+ Description: "Sends HTTP POST request to DingDing",
+ Factory: NewDingDingNotifier,
+ OptionsTemplate: DingdingOptionsTemplate,
})
}
@@ -67,7 +69,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
message += fmt.Sprintf("\\n%2d. %s value %s", i+1, match.Metric, match.Value)
}
- bodyJSON, err := simplejson.NewJson([]byte(`{
+ bodyStr := `{
"msgtype": "link",
"link": {
"text": "` + message + `",
@@ -75,7 +77,8 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
"picUrl": "` + picUrl + `",
"messageUrl": "` + messageUrl + `"
}
- }`))
+ }`
+ bodyJSON, err := simplejson.NewJson([]byte(bodyStr))
if err != nil {
this.log.Error("Failed to create Json data", "error", err, "dingding", this.Name)
From cb86e386289a02f1a8638b2e84030b36c697efa9 Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Thu, 25 Oct 2018 18:29:47 +0800
Subject: [PATCH 003/770] Add Dingding message type to support mass text
notification
---
pkg/services/alerting/notifiers/dingding.go | 45 ++++++++++++++++-----
1 file changed, 34 insertions(+), 11 deletions(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index bf1b721f753..ce932b7a799 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -10,12 +10,17 @@ import (
"github.com/grafana/grafana/pkg/services/alerting"
)
+const DefaultDingdingMsgType = "link"
const DingdingOptionsTemplate = `
DingDing settings
Url
+
+ MessageType
+
+
`
func init() {
@@ -35,8 +40,11 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
}
+ msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType)
+
return &DingDingNotifier{
NotifierBase: NewNotifierBase(model),
+ MsgType: msgType,
Url: url,
log: log.New("alerting.notifier.dingding"),
}, nil
@@ -44,8 +52,9 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
type DingDingNotifier struct {
NotifierBase
- Url string
- log log.Logger
+ MsgType string
+ Url string
+ log log.Logger
}
func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -69,15 +78,29 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
message += fmt.Sprintf("\\n%2d. %s value %s", i+1, match.Metric, match.Value)
}
- bodyStr := `{
- "msgtype": "link",
- "link": {
- "text": "` + message + `",
- "title": "` + title + `",
- "picUrl": "` + picUrl + `",
- "messageUrl": "` + messageUrl + `"
- }
- }`
+ var bodyStr string
+ if this.MsgType == "actionCard" {
+ bodyStr = `{
+ "msgtype": "actionCard",
+ "actionCard": {
+ "text": "` + message + `",
+ "title": "` + title + `",
+ "singleTitle": "More",
+ "singleURL": "` + messageUrl + `"
+ }
+ }`
+ } else {
+ bodyStr = `{
+ "msgtype": "link",
+ "link": {
+ "text": "` + message + `",
+ "title": "` + title + `",
+ "picUrl": "` + picUrl + `",
+ "messageUrl": "` + messageUrl + `"
+ }
+ }`
+ }
+
bodyJSON, err := simplejson.NewJson([]byte(bodyStr))
if err != nil {
From 201dd6bf658501782180ce90111390d4970b16c8 Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Thu, 25 Oct 2018 18:53:45 +0800
Subject: [PATCH 004/770] Optimize the Dingding match values format
---
pkg/services/alerting/notifiers/dingding.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index ce932b7a799..94961e82025 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -75,7 +75,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
}
for i, match := range evalContext.EvalMatches {
- message += fmt.Sprintf("\\n%2d. %s value %s", i+1, match.Metric, match.Value)
+ message += fmt.Sprintf("\\n%2d. %s: %s", i+1, match.Metric, match.Value)
}
var bodyStr string
From b7787db34e2b71cdc59aef829ea1a3d69b0a1e3c Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Thu, 8 Nov 2018 18:44:00 +0800
Subject: [PATCH 005/770] Add new option to set where to open the message url
---
pkg/services/alerting/notifiers/dingding.go | 37 ++++++++++++++++-----
1 file changed, 29 insertions(+), 8 deletions(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index 94961e82025..af1063a4c70 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -2,6 +2,7 @@ package notifiers
import (
"fmt"
+ "net/url"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -15,12 +16,17 @@ const DingdingOptionsTemplate = `
DingDing settings
Url
-
+
MessageType
+
+ OpenInBrowser
+
+ Open the message url in browser instead of inside of Dingding
+
`
func init() {
@@ -41,20 +47,23 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType)
+ openInBrowser := model.Settings.Get("openInBrowser").MustBool(true)
return &DingDingNotifier{
- NotifierBase: NewNotifierBase(model),
- MsgType: msgType,
- Url: url,
- log: log.New("alerting.notifier.dingding"),
+ NotifierBase: NewNotifierBase(model),
+ OpenInBrowser: openInBrowser,
+ MsgType: msgType,
+ Url: url,
+ log: log.New("alerting.notifier.dingding"),
}, nil
}
type DingDingNotifier struct {
NotifierBase
- MsgType string
- Url string
- log log.Logger
+ MsgType string
+ OpenInBrowser bool //Set whether the message url will open outside of Dingding
+ Url string
+ log log.Logger
}
func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -65,6 +74,18 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Error("Failed to get messageUrl", "error", err, "dingding", this.Name)
messageUrl = ""
}
+
+ if this.OpenInBrowser {
+ q := url.Values{
+ "pc_slide": {"false"},
+ "url": {messageUrl},
+ }
+
+ // Use special link to auto open the message url outside of Dingding
+ // Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
+ messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode()
+ }
+
this.log.Info("messageUrl:" + messageUrl)
message := evalContext.Rule.Message
From 919d00437e21944d5feea3c6ac175a2d85736784 Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Mon, 12 Nov 2018 11:18:53 +0800
Subject: [PATCH 006/770] Add pic into actionCard message
---
pkg/services/alerting/notifiers/dingding.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index af1063a4c70..3514554a1db 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -101,6 +101,11 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
var bodyStr string
if this.MsgType == "actionCard" {
+ // Embed the pic into the markdown directly because actionCard doesn't have a picUrl field
+ if picUrl != "" {
+ message = "\\n\\n" + message
+ }
+
bodyStr = `{
"msgtype": "actionCard",
"actionCard": {
From 7db848f153f083b5efafc4c3bae177eeafb99f8b Mon Sep 17 00:00:00 2001
From: bugficks
Date: Tue, 15 Jan 2019 13:29:56 +0100
Subject: [PATCH 007/770] [Feature request] MySQL SSL CA in datasource
connector https://github.com/grafana/grafana/issues/8570
---
pkg/tsdb/mysql/mysql.go | 44 ++++++++++++
.../datasource/mysql/partials/config.html | 68 ++++++++++++++++++-
2 files changed, 110 insertions(+), 2 deletions(-)
diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go
index 35b03e489a0..e713b87e265 100644
--- a/pkg/tsdb/mysql/mysql.go
+++ b/pkg/tsdb/mysql/mysql.go
@@ -6,6 +6,10 @@ import (
"reflect"
"strconv"
"strings"
+ "errors"
+
+ "crypto/x509"
+ "crypto/tls"
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
@@ -32,6 +36,46 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
datasource.Url,
datasource.Database,
)
+
+ var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool
+ if datasource.JsonData != nil {
+ tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false)
+ tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false)
+ tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false)
+ }
+
+ if tlsAuth || tlsAuthWithCACert {
+
+ secureJsonData := datasource.SecureJsonData.Decrypt()
+ tlsConfig := tls.Config {
+ InsecureSkipVerify: tlsSkipVerify,
+ }
+
+ if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 {
+
+ caPool := x509.NewCertPool()
+ if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok {
+ return nil, errors.New("Failed to parse TLS CA PEM certificate")
+ }
+
+ tlsConfig.RootCAs = caPool
+ }
+
+ if tlsAuth {
+ certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"]))
+ if err != nil {
+ return nil, err
+ }
+ clientCert := make([]tls.Certificate, 0, 1)
+ clientCert = append(clientCert, certs)
+
+ tlsConfig.Certificates = clientCert
+ }
+
+ mysql.RegisterTLSConfig(datasource.Name, &tlsConfig)
+ cnnstr += "&tls=" + datasource.Name
+ }
+
logger.Debug("getEngine", "connection", cnnstr)
config := tsdb.SqlQueryEndpointConfiguration{
diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html
index a35633c626a..5f3ba5c1286 100644
--- a/public/app/plugins/datasource/mysql/partials/config.html
+++ b/public/app/plugins/datasource/mysql/partials/config.html
@@ -1,4 +1,3 @@
-
MySQL Connection
@@ -22,6 +21,72 @@
+
+
+
+
Connection limits
@@ -84,4 +149,3 @@
-
From f31fe495e977cd9fe1c585e25221a423ff9a7c71 Mon Sep 17 00:00:00 2001
From: bugficks
Date: Tue, 15 Jan 2019 13:54:25 +0100
Subject: [PATCH 008/770] fix go fmt
---
pkg/tsdb/mysql/mysql.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go
index e713b87e265..82e7cac27f0 100644
--- a/pkg/tsdb/mysql/mysql.go
+++ b/pkg/tsdb/mysql/mysql.go
@@ -2,14 +2,14 @@ package mysql
import (
"database/sql"
+ "errors"
"fmt"
"reflect"
"strconv"
"strings"
- "errors"
- "crypto/x509"
"crypto/tls"
+ "crypto/x509"
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
@@ -47,7 +47,7 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
if tlsAuth || tlsAuthWithCACert {
secureJsonData := datasource.SecureJsonData.Decrypt()
- tlsConfig := tls.Config {
+ tlsConfig := tls.Config{
InsecureSkipVerify: tlsSkipVerify,
}
From 7df5e3cebf06b39c0007bca76c9e86254fc7bc5a Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Mon, 28 Jan 2019 19:37:19 +0100
Subject: [PATCH 009/770] extract tls auth settings directive from datasource
http settings directive
---
public/app/features/all.ts | 1 +
.../datasources/partials/http_settings.html | 52 +-------------
.../partials/tls_auth_settings.html | 62 +++++++++++++++++
.../settings/TlsAuthSettingsCtrl.ts | 10 +++
.../datasource/mysql/partials/config.html | 68 +++----------------
5 files changed, 84 insertions(+), 109 deletions(-)
create mode 100644 public/app/features/datasources/partials/tls_auth_settings.html
create mode 100644 public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts
diff --git a/public/app/features/all.ts b/public/app/features/all.ts
index 83146596ea0..d5e684e4a4e 100644
--- a/public/app/features/all.ts
+++ b/public/app/features/all.ts
@@ -12,3 +12,4 @@ import './manage-dashboards';
import './teams/CreateTeamCtrl';
import './profile/all';
import './datasources/settings/HttpSettingsCtrl';
+import './datasources/settings/TlsAuthSettingsCtrl';
diff --git a/public/app/features/datasources/partials/http_settings.html b/public/app/features/datasources/partials/http_settings.html
index 521e2d3cdc6..b6f2c4fc0dd 100644
--- a/public/app/features/datasources/partials/http_settings.html
+++ b/public/app/features/datasources/partials/http_settings.html
@@ -101,53 +101,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/public/app/features/datasources/partials/tls_auth_settings.html b/public/app/features/datasources/partials/tls_auth_settings.html
new file mode 100644
index 00000000000..c852e8ec70c
--- /dev/null
+++ b/public/app/features/datasources/partials/tls_auth_settings.html
@@ -0,0 +1,62 @@
+
diff --git a/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts
new file mode 100644
index 00000000000..7c21fab404c
--- /dev/null
+++ b/public/app/features/datasources/settings/TlsAuthSettingsCtrl.ts
@@ -0,0 +1,10 @@
+import { coreModule } from 'app/core/core';
+
+coreModule.directive('datasourceTlsAuthSettings', () => {
+ return {
+ scope: {
+ current: '=',
+ },
+ templateUrl: 'public/app/features/datasources/partials/tls_auth_settings.html',
+ };
+});
diff --git a/public/app/plugins/datasource/mysql/partials/config.html b/public/app/plugins/datasource/mysql/partials/config.html
index 5f3ba5c1286..8221a06e1ee 100644
--- a/public/app/plugins/datasource/mysql/partials/config.html
+++ b/public/app/plugins/datasource/mysql/partials/config.html
@@ -24,70 +24,20 @@
-
-
+
+
Connection limits
From f157c19e16cdc970542867ec77eb5a61fe5f11ad Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Mon, 28 Jan 2019 19:38:56 +0100
Subject: [PATCH 010/770] extract parsing of datasource tls config to method
---
pkg/models/datasource_cache.go | 48 +++++++++++++++++++++-------------
pkg/tsdb/mysql/mysql.go | 43 ++++--------------------------
2 files changed, 35 insertions(+), 56 deletions(-)
diff --git a/pkg/models/datasource_cache.go b/pkg/models/datasource_cache.go
index 66ba66e4d39..1c895514ace 100644
--- a/pkg/models/datasource_cache.go
+++ b/pkg/models/datasource_cache.go
@@ -46,19 +46,16 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
return t.Transport, nil
}
- var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
- if ds.JsonData != nil {
- tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
- tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
- tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false)
+ tlsConfig, err := ds.GetTLSConfig()
+ if err != nil {
+ return nil, err
}
+ tlsConfig.Renegotiation = tls.RenegotiateFreelyAsClient
+
transport := &http.Transport{
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: tlsSkipVerify,
- Renegotiation: tls.RenegotiateFreelyAsClient,
- },
- Proxy: http.ProxyFromEnvironment,
+ TLSClientConfig: tlsConfig,
+ Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
@@ -70,6 +67,26 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
IdleConnTimeout: 90 * time.Second,
}
+ ptc.cache[ds.Id] = cachedTransport{
+ Transport: transport,
+ updated: ds.Updated,
+ }
+
+ return transport, nil
+}
+
+func (ds *DataSource) GetTLSConfig() (*tls.Config, error) {
+ var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
+ if ds.JsonData != nil {
+ tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
+ tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
+ tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false)
+ }
+
+ tlsConfig := &tls.Config{
+ InsecureSkipVerify: tlsSkipVerify,
+ }
+
if tlsClientAuth || tlsAuthWithCACert {
decrypted := ds.SecureJsonData.Decrypt()
if tlsAuthWithCACert && len(decrypted["tlsCACert"]) > 0 {
@@ -78,7 +95,7 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
if !ok {
return nil, errors.New("Failed to parse TLS CA PEM certificate")
}
- transport.TLSClientConfig.RootCAs = caPool
+ tlsConfig.RootCAs = caPool
}
if tlsClientAuth {
@@ -86,14 +103,9 @@ func (ds *DataSource) GetHttpTransport() (*http.Transport, error) {
if err != nil {
return nil, err
}
- transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
+ tlsConfig.Certificates = []tls.Certificate{cert}
}
}
- ptc.cache[ds.Id] = cachedTransport{
- Transport: transport,
- updated: ds.Updated,
- }
-
- return transport, nil
+ return tlsConfig, nil
}
diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go
index 82e7cac27f0..d451150f1de 100644
--- a/pkg/tsdb/mysql/mysql.go
+++ b/pkg/tsdb/mysql/mysql.go
@@ -2,15 +2,11 @@ package mysql
import (
"database/sql"
- "errors"
"fmt"
"reflect"
"strconv"
"strings"
- "crypto/tls"
- "crypto/x509"
-
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
"github.com/grafana/grafana/pkg/log"
@@ -37,42 +33,13 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
datasource.Database,
)
- var tlsSkipVerify, tlsAuth, tlsAuthWithCACert bool
- if datasource.JsonData != nil {
- tlsAuth = datasource.JsonData.Get("tlsAuth").MustBool(false)
- tlsAuthWithCACert = datasource.JsonData.Get("tlsAuthWithCACert").MustBool(false)
- tlsSkipVerify = datasource.JsonData.Get("tlsSkipVerify").MustBool(false)
+ tlsConfig, err := datasource.GetTLSConfig()
+ if err != nil {
+ return nil, err
}
- if tlsAuth || tlsAuthWithCACert {
-
- secureJsonData := datasource.SecureJsonData.Decrypt()
- tlsConfig := tls.Config{
- InsecureSkipVerify: tlsSkipVerify,
- }
-
- if tlsAuthWithCACert && len(secureJsonData["tlsCACert"]) > 0 {
-
- caPool := x509.NewCertPool()
- if ok := caPool.AppendCertsFromPEM([]byte(secureJsonData["tlsCACert"])); !ok {
- return nil, errors.New("Failed to parse TLS CA PEM certificate")
- }
-
- tlsConfig.RootCAs = caPool
- }
-
- if tlsAuth {
- certs, err := tls.X509KeyPair([]byte(secureJsonData["tlsClientCert"]), []byte(secureJsonData["tlsClientKey"]))
- if err != nil {
- return nil, err
- }
- clientCert := make([]tls.Certificate, 0, 1)
- clientCert = append(clientCert, certs)
-
- tlsConfig.Certificates = clientCert
- }
-
- mysql.RegisterTLSConfig(datasource.Name, &tlsConfig)
+ if tlsConfig.RootCAs != nil || len(tlsConfig.Certificates) > 0 {
+ mysql.RegisterTLSConfig(datasource.Name, tlsConfig)
cnnstr += "&tls=" + datasource.Name
}
From 43ac79685ad9fa16593c9b0ce103058292660a17 Mon Sep 17 00:00:00 2001
From: bergquist
Date: Thu, 31 Jan 2019 15:45:11 +0100
Subject: [PATCH 011/770] delete auth token on signout
---
pkg/api/common_test.go | 2 +-
pkg/middleware/middleware_test.go | 2 +-
pkg/services/auth/auth_token.go | 26 +++++++++++++++++++++++---
pkg/services/auth/auth_token_test.go | 25 +++++++++++++++++++++++++
4 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go
index eb1f89e3f22..f6c6e53e91d 100644
--- a/pkg/api/common_test.go
+++ b/pkg/api/common_test.go
@@ -149,4 +149,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
return nil
}
-func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) {}
+func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) error { return nil }
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 11740574d0b..9bb45062e00 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -602,4 +602,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
return nil
}
-func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) {}
+func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) error { return nil }
diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go
index 7e9433c2d70..d9c5e897f70 100644
--- a/pkg/services/auth/auth_token.go
+++ b/pkg/services/auth/auth_token.go
@@ -3,6 +3,7 @@ package auth
import (
"crypto/sha256"
"encoding/hex"
+ "errors"
"net/http"
"net/url"
"time"
@@ -31,7 +32,7 @@ var (
type UserAuthTokenService interface {
InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
- UserSignedOutHook(c *models.ReqContext)
+ UserSignedOutHook(c *models.ReqContext) error
}
type UserAuthTokenServiceImpl struct {
@@ -111,8 +112,27 @@ func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *m
return nil
}
-func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) {
- s.writeSessionCookie(c, "", -1)
+func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) error {
+ unhashedToken := c.GetCookie(s.Cfg.LoginCookieName)
+ if unhashedToken == "" {
+ return errors.New("cannot logout without session token")
+ }
+
+ hashedToken := hashToken(unhashedToken)
+
+ sql := `DELETE FROM user_auth_token WHERE auth_token = ?`
+ res, err := s.SQLStore.NewSession().Exec(sql, hashedToken)
+ if err != nil {
+ return err
+ }
+
+ affected, _ := res.RowsAffected()
+ if affected > 0 {
+ s.writeSessionCookie(c, "", -1)
+ return nil
+ }
+
+ return errors.New("failed to delete session")
}
func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go
index 2f75c660d9d..0114939ea48 100644
--- a/pkg/services/auth/auth_token_test.go
+++ b/pkg/services/auth/auth_token_test.go
@@ -1,10 +1,13 @@
package auth
import (
+ "net/http"
"testing"
"time"
+ "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
+ "gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/services/sqlstore"
@@ -46,6 +49,28 @@ func TestUserAuthToken(t *testing.T) {
So(err, ShouldEqual, ErrAuthTokenNotFound)
So(LookupToken, ShouldBeNil)
})
+
+ Convey("signing out should delete token and cookie if present", func() {
+ token, err := userAuthTokenService.CreateToken(userID, "192.168.1.1:1234", "some user agent2")
+ So(err, ShouldBeNil)
+ So(token, ShouldNotBeNil)
+
+ httpreq := &http.Request{Header: make(http.Header)}
+ httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: token.AuthToken})
+
+ ctx := &models.ReqContext{Context: &macaron.Context{Req: macaron.Request{Request: httpreq}}}
+
+ err = userAuthTokenService.UserSignedOutHook(ctx)
+ So(err, ShouldBeNil)
+
+ // makes sure we tell the browser to overwrite the cookie
+ So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, "")
+
+ // lookedUp, err = userAuthTokenService.LookupToken(token.UnhashedToken)
+ // So(err, ShouldBeNil)
+ // So(lookedUp, ShouldNotBeNil)
+
+ })
})
Convey("expires correctly", func() {
From 88ca54eba96195d1fc0e0138c17d8c6991deb938 Mon Sep 17 00:00:00 2001
From: bergquist
Date: Thu, 31 Jan 2019 16:22:40 +0100
Subject: [PATCH 012/770] renames signout function
---
pkg/api/common_test.go | 2 +-
pkg/api/login.go | 2 +-
pkg/middleware/middleware_test.go | 2 +-
pkg/services/auth/auth_token.go | 4 ++--
pkg/services/auth/auth_token_test.go | 11 +++--------
5 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go
index f6c6e53e91d..fe02c94e277 100644
--- a/pkg/api/common_test.go
+++ b/pkg/api/common_test.go
@@ -149,4 +149,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
return nil
}
-func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) error { return nil }
+func (s *fakeUserAuthTokenService) SignOutUser(c *m.ReqContext) error { return nil }
diff --git a/pkg/api/login.go b/pkg/api/login.go
index 50c62e0835a..49da147724e 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -136,7 +136,7 @@ func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
}
func (hs *HTTPServer) Logout(c *m.ReqContext) {
- hs.AuthTokenService.UserSignedOutHook(c)
+ hs.AuthTokenService.SignOutUser(c)
if setting.SignoutRedirectUrl != "" {
c.Redirect(setting.SignoutRedirectUrl)
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 9bb45062e00..4679c449853 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -602,4 +602,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
return nil
}
-func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) error { return nil }
+func (s *fakeUserAuthTokenService) SignOutUser(c *m.ReqContext) error { return nil }
diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go
index d9c5e897f70..5f8f36fc373 100644
--- a/pkg/services/auth/auth_token.go
+++ b/pkg/services/auth/auth_token.go
@@ -32,7 +32,7 @@ var (
type UserAuthTokenService interface {
InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
- UserSignedOutHook(c *models.ReqContext) error
+ SignOutUser(c *models.ReqContext) error
}
type UserAuthTokenServiceImpl struct {
@@ -112,7 +112,7 @@ func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *m
return nil
}
-func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) error {
+func (s *UserAuthTokenServiceImpl) SignOutUser(c *models.ReqContext) error {
unhashedToken := c.GetCookie(s.Cfg.LoginCookieName)
if unhashedToken == "" {
return errors.New("cannot logout without session token")
diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go
index 0114939ea48..47afe627479 100644
--- a/pkg/services/auth/auth_token_test.go
+++ b/pkg/services/auth/auth_token_test.go
@@ -51,7 +51,7 @@ func TestUserAuthToken(t *testing.T) {
})
Convey("signing out should delete token and cookie if present", func() {
- token, err := userAuthTokenService.CreateToken(userID, "192.168.1.1:1234", "some user agent2")
+ token, err := userAuthTokenService.CreateToken(userID, "192.168.1.1:1234", "user agent")
So(err, ShouldBeNil)
So(token, ShouldNotBeNil)
@@ -60,16 +60,11 @@ func TestUserAuthToken(t *testing.T) {
ctx := &models.ReqContext{Context: &macaron.Context{Req: macaron.Request{Request: httpreq}}}
- err = userAuthTokenService.UserSignedOutHook(ctx)
+ err = userAuthTokenService.SignOutUser(ctx)
So(err, ShouldBeNil)
// makes sure we tell the browser to overwrite the cookie
- So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, "")
-
- // lookedUp, err = userAuthTokenService.LookupToken(token.UnhashedToken)
- // So(err, ShouldBeNil)
- // So(lookedUp, ShouldNotBeNil)
-
+ //So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, "")
})
})
From 0442a86400552f461f7c8a807a1f2fe609fd5e98 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Thu, 31 Jan 2019 19:22:25 +0100
Subject: [PATCH 013/770] tailing grafana logs and temporaily using an older
build
---
devenv/docker/blocks/loki/config.yaml | 27 +++++++++++++++++++
devenv/docker/blocks/loki/docker-compose.yaml | 6 +++--
2 files changed, 31 insertions(+), 2 deletions(-)
create mode 100644 devenv/docker/blocks/loki/config.yaml
diff --git a/devenv/docker/blocks/loki/config.yaml b/devenv/docker/blocks/loki/config.yaml
new file mode 100644
index 00000000000..9451b6ba79b
--- /dev/null
+++ b/devenv/docker/blocks/loki/config.yaml
@@ -0,0 +1,27 @@
+server:
+ http_listen_port: 9080
+ grpc_listen_port: 0
+
+positions:
+ filename: /tmp/positions.yaml
+
+client:
+ url: http://loki:3100/api/prom/push
+
+scrape_configs:
+- job_name: system
+ entry_parser: raw
+ static_configs:
+ - targets:
+ - localhost
+ labels:
+ job: varlogs
+ __path__: /var/log/*log
+- job_name: grafana
+ entry_parser: raw
+ static_configs:
+ - targets:
+ - localhost
+ labels:
+ job: grafana
+ __path__: /var/log/grafana/*log
diff --git a/devenv/docker/blocks/loki/docker-compose.yaml b/devenv/docker/blocks/loki/docker-compose.yaml
index d6cf21f7856..c2fee15b0bb 100644
--- a/devenv/docker/blocks/loki/docker-compose.yaml
+++ b/devenv/docker/blocks/loki/docker-compose.yaml
@@ -5,7 +5,7 @@ networks:
services:
loki:
- image: grafana/loki:master
+ image: grafana/loki:master-3e6a75e
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
@@ -13,9 +13,11 @@ services:
- loki
promtail:
- image: grafana/promtail:master
+ image: grafana/promtail:master-3e6a75e
volumes:
+ - ./docker/blocks/loki/config.yaml:/etc/promtail/docker-config.yaml
- /var/log:/var/log
+ - ../data/log:/var/log/grafana
command:
-config.file=/etc/promtail/docker-config.yaml
networks:
From f9bab9585a68bb65201a9f7e5c15052601546e22 Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Thu, 31 Jan 2019 19:38:49 +0100
Subject: [PATCH 014/770] wip
---
public/app/core/utils/explore.test.ts | 9 ++++++++-
public/app/core/utils/explore.ts | 19 ++++++++++++++++---
public/app/types/explore.ts | 7 +++++++
3 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts
index 32135eab90a..d818b2ef090 100644
--- a/public/app/core/utils/explore.test.ts
+++ b/public/app/core/utils/explore.test.ts
@@ -13,6 +13,11 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
datasource: null,
queries: [],
range: DEFAULT_RANGE,
+ ui: {
+ showingGraph: true,
+ showingTable: true,
+ showingLogs: true,
+ }
};
describe('state functions', () => {
@@ -69,9 +74,11 @@ describe('state functions', () => {
to: 'now',
},
};
+
expect(serializeStateToUrlParam(state)).toBe(
'{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
- '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}'
+ '{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
+ '"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
);
});
diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts
index 7a9f54a0cae..07c8cf1d24b 100644
--- a/public/app/core/utils/explore.ts
+++ b/public/app/core/utils/explore.ts
@@ -20,6 +20,7 @@ import {
ResultType,
QueryIntervals,
QueryOptions,
+ ExploreUrlUIState,
} from 'app/types/explore';
export const DEFAULT_RANGE = {
@@ -27,6 +28,12 @@ export const DEFAULT_RANGE = {
to: 'now',
};
+export const DEFAULT_UI_STATE = {
+ showingTable: true,
+ showingGraph: true,
+ showingLogs: true,
+};
+
const MAX_HISTORY_ITEMS = 100;
export const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
@@ -151,6 +158,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
if (initial) {
try {
const parsed = JSON.parse(decodeURI(initial));
+ // debugger
if (Array.isArray(parsed)) {
if (parsed.length <= 3) {
throw new Error('Error parsing compact URL state for Explore.');
@@ -161,19 +169,24 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
};
const datasource = parsed[2];
const queries = parsed.slice(3);
- return { datasource, queries, range };
+ return { datasource, queries, range, ui: DEFAULT_UI_STATE };
}
return parsed;
} catch (e) {
console.error(e);
}
}
- return { datasource: null, queries: [], range: DEFAULT_RANGE };
+ return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: DEFAULT_UI_STATE };
}
+const serializeUIState = (state: ExploreUrlUIState) => {
+ return Object.keys(state).map((key) => ({ [key]: state[key] }));
+};
+
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
+
if (compact) {
- return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
+ return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries, ...serializeUIState(urlState.ui)]);
}
return JSON.stringify(urlState);
}
diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts
index 34b7ff08c99..d035b60d86a 100644
--- a/public/app/types/explore.ts
+++ b/public/app/types/explore.ts
@@ -231,10 +231,17 @@ export interface ExploreItemState {
tableResult?: TableModel;
}
+export interface ExploreUrlUIState {
+ showingTable: boolean;
+ showingGraph: boolean;
+ showingLogs: boolean;
+}
+
export interface ExploreUrlState {
datasource: string;
queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense
range: RawTimeRange;
+ ui: ExploreUrlUIState;
}
export interface HistoryItem {
From 91bd908e03ecdfbc691c13e66cb9512535ca78fb Mon Sep 17 00:00:00 2001
From: bergquist
Date: Thu, 31 Jan 2019 22:24:04 +0100
Subject: [PATCH 015/770] adds more tests signing out session
---
pkg/services/auth/auth_token.go | 2 +-
pkg/services/auth/auth_token_test.go | 35 +++++++++++++++++++++-------
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go
index 5f8f36fc373..deb3c1a5bba 100644
--- a/pkg/services/auth/auth_token.go
+++ b/pkg/services/auth/auth_token.go
@@ -86,7 +86,7 @@ func (s *UserAuthTokenServiceImpl) InitContextWithToken(ctx *models.ReqContext,
func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
if setting.Env == setting.DEV {
- ctx.Logger.Info("new token", "unhashed token", value)
+ ctx.Logger.Debug("new token", "unhashed token", value)
}
ctx.Resp.Header().Del("Set-Cookie")
diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go
index 47afe627479..e58fe795b4a 100644
--- a/pkg/services/auth/auth_token_test.go
+++ b/pkg/services/auth/auth_token_test.go
@@ -1,13 +1,15 @@
package auth
import (
+ "fmt"
"net/http"
+ "net/http/httptest"
"testing"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
- "gopkg.in/macaron.v1"
+ macaron "gopkg.in/macaron.v1"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/services/sqlstore"
@@ -51,20 +53,37 @@ func TestUserAuthToken(t *testing.T) {
})
Convey("signing out should delete token and cookie if present", func() {
- token, err := userAuthTokenService.CreateToken(userID, "192.168.1.1:1234", "user agent")
- So(err, ShouldBeNil)
- So(token, ShouldNotBeNil)
-
httpreq := &http.Request{Header: make(http.Header)}
- httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: token.AuthToken})
+ httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: token.UnhashedToken})
- ctx := &models.ReqContext{Context: &macaron.Context{Req: macaron.Request{Request: httpreq}}}
+ ctx := &models.ReqContext{Context: &macaron.Context{
+ Req: macaron.Request{Request: httpreq},
+ Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
+ },
+ Logger: log.New("fakelogger"),
+ }
err = userAuthTokenService.SignOutUser(ctx)
So(err, ShouldBeNil)
// makes sure we tell the browser to overwrite the cookie
- //So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, "")
+ cookieHeader := fmt.Sprintf("%s=; Path=/; Max-Age=0; HttpOnly", userAuthTokenService.Cfg.LoginCookieName)
+ So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, cookieHeader)
+ })
+
+ Convey("signing out an none existing session should return an error", func() {
+ httpreq := &http.Request{Header: make(http.Header)}
+ httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: "missing-session-cookie"})
+
+ ctx := &models.ReqContext{Context: &macaron.Context{
+ Req: macaron.Request{Request: httpreq},
+ Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
+ },
+ Logger: log.New("fakelogger"),
+ }
+
+ err = userAuthTokenService.SignOutUser(ctx)
+ So(err, ShouldNotBeNil)
})
})
From dd5a8275f107dbc54160fc271c3230096ba3707b Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Fri, 1 Feb 2019 01:21:23 +0100
Subject: [PATCH 016/770] must return json response from /api/login/ping
Even though http error 401 was returned, the result was still a http 200
---
pkg/api/login.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/pkg/api/login.go b/pkg/api/login.go
index 50c62e0835a..3f2d82a6c0f 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -78,12 +78,13 @@ func tryOAuthAutoLogin(c *m.ReqContext) bool {
return false
}
-func (hs *HTTPServer) LoginAPIPing(c *m.ReqContext) Response {
- if c.IsSignedIn || c.IsAnonymous {
- return JSON(200, "Logged in")
+func (hs *HTTPServer) LoginAPIPing(c *m.ReqContext) {
+ if c.IsSignedIn || (c.AllowAnonymous && c.IsAnonymous) {
+ c.JsonOK("Logged in")
+ return
}
- return Error(401, "Unauthorized", nil)
+ c.JsonApiErr(401, "Unauthorized", nil)
}
func (hs *HTTPServer) LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
From bd830780250880f3c9920a5c1f5c467b3e5bdb2a Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Fri, 1 Feb 2019 01:22:56 +0100
Subject: [PATCH 017/770] signout user if /api/login/ping returns 401
unauthorized
---
public/app/core/services/backend_srv.ts | 37 +++++++++++++++++--------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts
index 38d7f2b76cb..c73cc7661f5 100644
--- a/public/app/core/services/backend_srv.ts
+++ b/public/app/core/services/backend_srv.ts
@@ -1,6 +1,7 @@
import _ from 'lodash';
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
+import config from 'app/core/config';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
export class BackendSrv {
@@ -103,10 +104,17 @@ export class BackendSrv {
err => {
// handle unauthorized
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
- return this.loginPing().then(() => {
- options.retry = 1;
- return this.request(options);
- });
+ return this.loginPing()
+ .then(() => {
+ options.retry = 1;
+ return this.request(options);
+ })
+ .catch(err => {
+ if (err.status === 401) {
+ window.location.href = config.appSubUrl + '/logout';
+ throw err;
+ }
+ });
}
this.$timeout(this.requestErrorHandler.bind(this, err), 50);
@@ -184,13 +192,20 @@ export class BackendSrv {
// handle unauthorized for backend requests
if (requestIsLocal && firstAttempt && err.status === 401) {
- return this.loginPing().then(() => {
- options.retry = 1;
- if (canceler) {
- canceler.resolve();
- }
- return this.datasourceRequest(options);
- });
+ return this.loginPing()
+ .then(() => {
+ options.retry = 1;
+ if (canceler) {
+ canceler.resolve();
+ }
+ return this.datasourceRequest(options);
+ })
+ .catch(err => {
+ if (err.status === 401) {
+ window.location.href = config.appSubUrl + '/logout';
+ throw err;
+ }
+ });
}
// populate error obj on Internal Error
From a1b3986532dbbb51145471304fccc6f254899bfe Mon Sep 17 00:00:00 2001
From: bergquist
Date: Fri, 1 Feb 2019 09:59:53 +0100
Subject: [PATCH 018/770] always delete session cookie even if db delete fails
---
devenv/docker/blocks/loki/docker-compose.yaml | 2 ++
pkg/services/auth/auth_token.go | 14 +++-----------
pkg/services/auth/auth_token_test.go | 2 +-
3 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/devenv/docker/blocks/loki/docker-compose.yaml b/devenv/docker/blocks/loki/docker-compose.yaml
index d6cf21f7856..bd4f8d3c728 100644
--- a/devenv/docker/blocks/loki/docker-compose.yaml
+++ b/devenv/docker/blocks/loki/docker-compose.yaml
@@ -20,3 +20,5 @@ services:
-config.file=/etc/promtail/docker-config.yaml
networks:
- loki
+ depends_on:
+ - loki
diff --git a/pkg/services/auth/auth_token.go b/pkg/services/auth/auth_token.go
index deb3c1a5bba..5cb43974d34 100644
--- a/pkg/services/auth/auth_token.go
+++ b/pkg/services/auth/auth_token.go
@@ -121,18 +121,10 @@ func (s *UserAuthTokenServiceImpl) SignOutUser(c *models.ReqContext) error {
hashedToken := hashToken(unhashedToken)
sql := `DELETE FROM user_auth_token WHERE auth_token = ?`
- res, err := s.SQLStore.NewSession().Exec(sql, hashedToken)
- if err != nil {
- return err
- }
+ _, err := s.SQLStore.NewSession().Exec(sql, hashedToken)
- affected, _ := res.RowsAffected()
- if affected > 0 {
- s.writeSessionCookie(c, "", -1)
- return nil
- }
-
- return errors.New("failed to delete session")
+ s.writeSessionCookie(c, "", -1)
+ return err
}
func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
diff --git a/pkg/services/auth/auth_token_test.go b/pkg/services/auth/auth_token_test.go
index e58fe795b4a..312e53a3970 100644
--- a/pkg/services/auth/auth_token_test.go
+++ b/pkg/services/auth/auth_token_test.go
@@ -73,7 +73,7 @@ func TestUserAuthToken(t *testing.T) {
Convey("signing out an none existing session should return an error", func() {
httpreq := &http.Request{Header: make(http.Header)}
- httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: "missing-session-cookie"})
+ httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: ""})
ctx := &models.ReqContext{Context: &macaron.Context{
Req: macaron.Request{Request: httpreq},
From ce2209585c1e1e885e1f02c29d004e8abde61c84 Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Fri, 1 Feb 2019 14:32:40 +0300
Subject: [PATCH 019/770] Remove version.ts
---
.../config_ctrl.ts | 2 +-
.../version.test.ts | 53 -------------------
.../version.ts | 34 ------------
3 files changed, 1 insertion(+), 88 deletions(-)
delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts
delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts
index 98fe5a87a56..4ee5c94fad6 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/config_ctrl.ts
@@ -1,6 +1,6 @@
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
import config from 'app/core/config';
-import { isVersionGtOrEq } from './version';
+import { isVersionGtOrEq } from 'app/core/utils/version';
export class AzureMonitorConfigCtrl {
static templateUrl = 'public/app/plugins/datasource/grafana-azure-monitor-datasource/partials/config.html';
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts
deleted file mode 100644
index 17a6ce9bb0b..00000000000
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.test.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { SemVersion, isVersionGtOrEq } from './version';
-
-describe('SemVersion', () => {
- let version = '1.0.0-alpha.1';
-
- describe('parsing', () => {
- it('should parse version properly', () => {
- const semver = new SemVersion(version);
- expect(semver.major).toBe(1);
- expect(semver.minor).toBe(0);
- expect(semver.patch).toBe(0);
- expect(semver.meta).toBe('alpha.1');
- });
- });
-
- describe('comparing', () => {
- beforeEach(() => {
- version = '3.4.5';
- });
-
- it('should detect greater version properly', () => {
- const semver = new SemVersion(version);
- const cases = [
- { value: '3.4.5', expected: true },
- { value: '3.4.4', expected: true },
- { value: '3.4.6', expected: false },
- { value: '4', expected: false },
- { value: '3.5', expected: false },
- ];
- cases.forEach(testCase => {
- expect(semver.isGtOrEq(testCase.value)).toBe(testCase.expected);
- });
- });
- });
-
- describe('isVersionGtOrEq', () => {
- it('should compare versions properly (a >= b)', () => {
- const cases = [
- { values: ['3.4.5', '3.4.5'], expected: true },
- { values: ['3.4.5', '3.4.4'], expected: true },
- { values: ['3.4.5', '3.4.6'], expected: false },
- { values: ['3.4', '3.4.0'], expected: true },
- { values: ['3', '3.0.0'], expected: true },
- { values: ['3.1.1-beta1', '3.1'], expected: true },
- { values: ['3.4.5', '4'], expected: false },
- { values: ['3.4.5', '3.5'], expected: false },
- ];
- cases.forEach(testCase => {
- expect(isVersionGtOrEq(testCase.values[0], testCase.values[1])).toBe(testCase.expected);
- });
- });
- });
-});
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts
deleted file mode 100644
index 1131e1d2ab8..00000000000
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/version.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import _ from 'lodash';
-
-const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/;
-
-export class SemVersion {
- major: number;
- minor: number;
- patch: number;
- meta: string;
-
- constructor(version: string) {
- const match = versionPattern.exec(version);
- if (match) {
- this.major = Number(match[1]);
- this.minor = Number(match[2] || 0);
- this.patch = Number(match[3] || 0);
- this.meta = match[4];
- }
- }
-
- isGtOrEq(version: string): boolean {
- const compared = new SemVersion(version);
- return !(this.major < compared.major || this.minor < compared.minor || this.patch < compared.patch);
- }
-
- isValid(): boolean {
- return _.isNumber(this.major);
- }
-}
-
-export function isVersionGtOrEq(a: string, b: string): boolean {
- const aSemver = new SemVersion(a);
- return aSemver.isGtOrEq(b);
-}
From 6ab9355146193941c4e719e8cfa43af7f382179e Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Fri, 1 Feb 2019 12:33:15 +0100
Subject: [PATCH 020/770] Restoring explore panels state from URL
---
public/app/core/utils/explore.test.ts | 23 +++++-
public/app/core/utils/explore.ts | 38 +++++++---
public/app/features/explore/Explore.tsx | 10 ++-
.../app/features/explore/state/actionTypes.ts | 2 +
public/app/features/explore/state/actions.ts | 73 ++++++++++++-------
public/app/features/explore/state/reducers.ts | 3 +-
public/app/types/explore.ts | 4 +-
7 files changed, 109 insertions(+), 44 deletions(-)
diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts
index d818b2ef090..1c00142c3b8 100644
--- a/public/app/core/utils/explore.test.ts
+++ b/public/app/core/utils/explore.test.ts
@@ -100,7 +100,7 @@ describe('state functions', () => {
},
};
expect(serializeStateToUrlParam(state, true)).toBe(
- '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]'
+ '["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]'
);
});
});
@@ -125,7 +125,28 @@ describe('state functions', () => {
};
const serialized = serializeStateToUrlParam(state);
const parsed = parseUrlState(serialized);
+ expect(state).toMatchObject(parsed);
+ });
+ it('can parse the compact serialized state into the original state', () => {
+ const state = {
+ ...DEFAULT_EXPLORE_STATE,
+ datasource: 'foo',
+ queries: [
+ {
+ expr: 'metric{test="a/b"}',
+ },
+ {
+ expr: 'super{foo="x/z"}',
+ },
+ ],
+ range: {
+ from: 'now - 5h',
+ to: 'now',
+ },
+ };
+ const serialized = serializeStateToUrlParam(state, true);
+ const parsed = parseUrlState(serialized);
expect(state).toMatchObject(parsed);
});
});
diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts
index 07c8cf1d24b..7128019b1fb 100644
--- a/public/app/core/utils/explore.ts
+++ b/public/app/core/utils/explore.ts
@@ -20,7 +20,6 @@ import {
ResultType,
QueryIntervals,
QueryOptions,
- ExploreUrlUIState,
} from 'app/types/explore';
export const DEFAULT_RANGE = {
@@ -154,11 +153,13 @@ export function buildQueryTransaction(
export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
+const isMetricSegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('expr');
+const isUISegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('ui');
+
export function parseUrlState(initial: string | undefined): ExploreUrlState {
if (initial) {
try {
const parsed = JSON.parse(decodeURI(initial));
- // debugger
if (Array.isArray(parsed)) {
if (parsed.length <= 3) {
throw new Error('Error parsing compact URL state for Explore.');
@@ -168,8 +169,24 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
to: parsed[1],
};
const datasource = parsed[2];
- const queries = parsed.slice(3);
- return { datasource, queries, range, ui: DEFAULT_UI_STATE };
+ let queries = [],
+ ui;
+
+ parsed.slice(3).forEach(segment => {
+ if (isMetricSegment(segment)) {
+ queries = [...queries, segment];
+ }
+
+ if (isUISegment(segment)) {
+ ui = {
+ showingGraph: segment.ui[0],
+ showingLogs: segment.ui[1],
+ showingTable: segment.ui[2],
+ };
+ }
+ });
+
+ return { datasource, queries, range, ui };
}
return parsed;
} catch (e) {
@@ -179,14 +196,15 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: DEFAULT_UI_STATE };
}
-const serializeUIState = (state: ExploreUrlUIState) => {
- return Object.keys(state).map((key) => ({ [key]: state[key] }));
-};
-
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
-
if (compact) {
- return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries, ...serializeUIState(urlState.ui)]);
+ return JSON.stringify([
+ urlState.range.from,
+ urlState.range.to,
+ urlState.datasource,
+ ...urlState.queries,
+ { ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] },
+ ]);
}
return JSON.stringify(urlState);
}
diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx
index 909c4e81b8b..d08243c7118 100644
--- a/public/app/features/explore/Explore.tsx
+++ b/public/app/features/explore/Explore.tsx
@@ -32,7 +32,7 @@ import {
import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui';
import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore';
import { StoreState } from 'app/types';
-import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
+import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore';
import { Emitter } from 'app/core/utils/emitter';
import { ExploreToolbar } from './ExploreToolbar';
@@ -61,7 +61,7 @@ interface ExploreProps {
supportsGraph: boolean | null;
supportsLogs: boolean | null;
supportsTable: boolean | null;
- urlState: ExploreUrlState;
+ urlState?: ExploreUrlState;
}
/**
@@ -107,18 +107,20 @@ export class Explore extends React.PureComponent {
// Don't initialize on split, but need to initialize urlparameters when present
if (!initialized) {
// Load URL state and parse range
- const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState;
+ const { datasource, queries, range = DEFAULT_RANGE, ui = DEFAULT_UI_STATE } = (urlState || {}) as ExploreUrlState;
const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
const initialQueries: DataQuery[] = ensureQueries(queries);
const initialRange = { from: parseTime(range.from), to: parseTime(range.to) };
const width = this.el ? this.el.offsetWidth : 0;
+
this.props.initializeExplore(
exploreId,
initialDatasource,
initialQueries,
initialRange,
width,
- this.exploreEvents
+ this.exploreEvents,
+ ui
);
}
}
diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts
index be7d5754bbe..3a0a564b651 100644
--- a/public/app/features/explore/state/actionTypes.ts
+++ b/public/app/features/explore/state/actionTypes.ts
@@ -8,6 +8,7 @@ import {
RangeScanner,
ResultType,
QueryTransaction,
+ ExploreUIState,
} from 'app/types/explore';
export enum ActionTypes {
@@ -106,6 +107,7 @@ export interface InitializeExploreAction {
exploreDatasources: DataSourceSelectItem[];
queries: DataQuery[];
range: RawTimeRange;
+ ui: ExploreUIState;
};
}
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index 1a11b7fcac9..02502a1d94c 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -38,6 +38,7 @@ import {
ResultType,
QueryOptions,
QueryTransaction,
+ ExploreUIState,
} from 'app/types/explore';
import {
@@ -154,7 +155,8 @@ export function initializeExplore(
queries: DataQuery[],
range: RawTimeRange,
containerWidth: number,
- eventBridge: Emitter
+ eventBridge: Emitter,
+ ui: ExploreUIState
): ThunkResult {
return async dispatch => {
const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv()
@@ -175,6 +177,7 @@ export function initializeExplore(
exploreDatasources,
queries,
range,
+ ui,
},
});
@@ -258,10 +261,7 @@ export const queriesImported = (exploreId: ExploreId, queries: DataQuery[]): Que
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
* e.g., Prometheus -> Loki queries.
*/
-export const loadDatasourceSuccess = (
- exploreId: ExploreId,
- instance: any,
-): LoadDatasourceSuccessAction => {
+export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): LoadDatasourceSuccessAction => {
// Capabilities
const supportsGraph = instance.meta.metrics;
const supportsLogs = instance.meta.logs;
@@ -766,6 +766,11 @@ export function stateSave() {
datasource: left.datasourceInstance.name,
queries: left.modifiedQueries.map(clearQueryKeys),
range: left.range,
+ ui: {
+ showingGraph: left.showingGraph,
+ showingLogs: left.showingLogs,
+ showingTable: left.showingTable,
+ },
};
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
if (split) {
@@ -773,48 +778,64 @@ export function stateSave() {
datasource: right.datasourceInstance.name,
queries: right.modifiedQueries.map(clearQueryKeys),
range: right.range,
+ ui: {
+ showingGraph: right.showingGraph,
+ showingLogs: right.showingLogs,
+ showingTable: right.showingTable,
+ },
};
+
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
}
+
dispatch(updateLocation({ query: urlStates }));
};
}
/**
- * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
+ * Creates action to collapse graph/logs/table panel. When panel is collapsed,
+ * queries won't be run
*/
-export function toggleGraph(exploreId: ExploreId): ThunkResult {
+const togglePanelActionCreator = (type: ActionTypes.ToggleGraph | ActionTypes.ToggleTable | ActionTypes.ToggleLogs) => (
+ exploreId: ExploreId
+) => {
return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleGraph, payload: { exploreId } });
- if (getState().explore[exploreId].showingGraph) {
+ let shouldRunQueries;
+ dispatch({ type, payload: { exploreId } });
+ dispatch(stateSave());
+
+ switch (type) {
+ case ActionTypes.ToggleGraph:
+ shouldRunQueries = getState().explore[exploreId].showingGraph;
+ break;
+ case ActionTypes.ToggleLogs:
+ shouldRunQueries = getState().explore[exploreId].showingLogs;
+ break;
+ case ActionTypes.ToggleTable:
+ shouldRunQueries = getState().explore[exploreId].showingTable;
+ break;
+ }
+
+ if (shouldRunQueries) {
dispatch(runQueries(exploreId));
}
};
-}
+};
+
+/**
+ * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
+ */
+export const toggleGraph = togglePanelActionCreator(ActionTypes.ToggleGraph);
/**
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
*/
-export function toggleLogs(exploreId: ExploreId): ThunkResult {
- return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleLogs, payload: { exploreId } });
- if (getState().explore[exploreId].showingLogs) {
- dispatch(runQueries(exploreId));
- }
- };
-}
+export const toggleLogs = togglePanelActionCreator(ActionTypes.ToggleLogs);
/**
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
-export function toggleTable(exploreId: ExploreId): ThunkResult {
- return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleTable, payload: { exploreId } });
- if (getState().explore[exploreId].showingTable) {
- dispatch(runQueries(exploreId));
- }
- };
-}
+export const toggleTable = togglePanelActionCreator(ActionTypes.ToggleTable);
/**
* Resets state for explore.
diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts
index eb67beee3b3..4ad07ddfc88 100644
--- a/public/app/features/explore/state/reducers.ts
+++ b/public/app/features/explore/state/reducers.ts
@@ -163,7 +163,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
}
case ActionTypes.InitializeExplore: {
- const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload;
+ const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload;
return {
...state,
containerWidth,
@@ -173,6 +173,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
initialQueries: queries,
initialized: true,
modifiedQueries: queries.slice(),
+ ...ui,
};
}
diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts
index d035b60d86a..3abbc652c0d 100644
--- a/public/app/types/explore.ts
+++ b/public/app/types/explore.ts
@@ -231,7 +231,7 @@ export interface ExploreItemState {
tableResult?: TableModel;
}
-export interface ExploreUrlUIState {
+export interface ExploreUIState {
showingTable: boolean;
showingGraph: boolean;
showingLogs: boolean;
@@ -241,7 +241,7 @@ export interface ExploreUrlState {
datasource: string;
queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense
range: RawTimeRange;
- ui: ExploreUrlUIState;
+ ui: ExploreUIState;
}
export interface HistoryItem {
From cf60ae79c31d6c0745b47e77e62e2d0605a19b73 Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Fri, 1 Feb 2019 14:47:17 +0300
Subject: [PATCH 021/770] Move prism to app/features/explore
---
.../editor => features/explore}/slate-plugins/prism/index.tsx | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename public/app/{plugins/datasource/grafana-azure-monitor-datasource/editor => features/explore}/slate-plugins/prism/index.tsx (100%)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/prism/index.tsx b/public/app/features/explore/slate-plugins/prism/index.tsx
similarity index 100%
rename from public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/prism/index.tsx
rename to public/app/features/explore/slate-plugins/prism/index.tsx
From bdd59de877f677d8831f64d438a146408097faed Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Fri, 1 Feb 2019 14:47:33 +0300
Subject: [PATCH 022/770] Remove newline && runner plugins
---
.../editor/slate-plugins/newline.ts | 35 -------------------
.../editor/slate-plugins/runner.ts | 14 --------
2 files changed, 49 deletions(-)
delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts
delete mode 100644 public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts
deleted file mode 100644
index d484d93a542..00000000000
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/newline.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-function getIndent(text) {
- let offset = text.length - text.trimLeft().length;
- if (offset) {
- let indent = text[0];
- while (--offset) {
- indent += text[0];
- }
- return indent;
- }
- return '';
-}
-
-export default function NewlinePlugin() {
- return {
- onKeyDown(event, change) {
- const { value } = change;
- if (!value.isCollapsed) {
- return undefined;
- }
-
- if (event.key === 'Enter' && !event.shiftKey) {
- event.preventDefault();
-
- const { startBlock } = value;
- const currentLineText = startBlock.text;
- const indent = getIndent(currentLineText);
-
- return change
- .splitBlock()
- .insertText(indent)
- .focus();
- }
- },
- };
-}
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts
deleted file mode 100644
index 068bd9f0ad1..00000000000
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/slate-plugins/runner.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export default function RunnerPlugin({ handler }) {
- return {
- onKeyDown(event) {
- // Handle enter
- if (handler && event.key === 'Enter' && event.shiftKey) {
- // Submit on Enter
- event.preventDefault();
- handler(event);
- return true;
- }
- return undefined;
- },
- };
-}
From 9a3f4def98fcb89855ec278c924f1897d1b7357e Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Fri, 1 Feb 2019 14:49:04 +0300
Subject: [PATCH 023/770] Use slate-plugins from app/features/explore
---
.../editor/query_field.tsx | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
index 1c883a40c31..f93912f069e 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
@@ -1,12 +1,9 @@
-import PluginPrism from './slate-plugins/prism';
-// import PluginPrism from 'slate-prism';
-// import Prism from 'prismjs';
+import PluginPrism from 'app/features/explore/slate-plugins/prism';
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
import ClearPlugin from 'app/features/explore/slate-plugins/clear';
-// Custom plugins (new line on Enter and run on Shift+Enter)
-import NewlinePlugin from './slate-plugins/newline';
-import RunnerPlugin from './slate-plugins/runner';
+import NewlinePlugin from 'app/features/explore/slate-plugins/newline';
+import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
import Typeahead from './typeahead';
From 6d03766acecab8914d344a929708a22470838a43 Mon Sep 17 00:00:00 2001
From: corpglory-dev
Date: Fri, 1 Feb 2019 14:54:38 +0300
Subject: [PATCH 024/770] Remove extra newline
---
.../grafana-azure-monitor-datasource/editor/query_field.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
index f93912f069e..400126f7e55 100644
--- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
+++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/editor/query_field.tsx
@@ -1,5 +1,4 @@
import PluginPrism from 'app/features/explore/slate-plugins/prism';
-
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
import ClearPlugin from 'app/features/explore/slate-plugins/clear';
import NewlinePlugin from 'app/features/explore/slate-plugins/newline';
From 2ddccb4a214a1828bde0ffb3c0d0191773d8146a Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Fri, 1 Feb 2019 12:57:09 +0100
Subject: [PATCH 025/770] Temporarily run queries independently from UI state
of explore panels
---
public/app/features/explore/state/actions.ts | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index 02502a1d94c..b24532c23f4 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -577,9 +577,9 @@ export function runQueries(exploreId: ExploreId) {
const {
datasourceInstance,
modifiedQueries,
- showingLogs,
- showingGraph,
- showingTable,
+ // showingLogs,
+ // showingGraph,
+ // showingTable,
supportsGraph,
supportsLogs,
supportsTable,
@@ -596,7 +596,7 @@ export function runQueries(exploreId: ExploreId) {
const interval = datasourceInstance.interval;
// Keep table queries first since they need to return quickly
- if (showingTable && supportsTable) {
+ if (/*showingTable &&*/ supportsTable) {
dispatch(
runQueriesForType(
exploreId,
@@ -611,7 +611,7 @@ export function runQueries(exploreId: ExploreId) {
)
);
}
- if (showingGraph && supportsGraph) {
+ if (/*showingGraph &&*/ supportsGraph) {
dispatch(
runQueriesForType(
exploreId,
@@ -625,7 +625,7 @@ export function runQueries(exploreId: ExploreId) {
)
);
}
- if (showingLogs && supportsLogs) {
+ if (/*showingLogs &&*/ supportsLogs) {
dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
}
dispatch(stateSave());
From 3c358e406e20f3a47b9d49fecadd53a6a2259843 Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Fri, 1 Feb 2019 14:56:54 +0100
Subject: [PATCH 026/770] Make runQueries action independent from datasource
loading
---
public/app/features/explore/state/actions.ts | 40 ++++++++++++++------
1 file changed, 28 insertions(+), 12 deletions(-)
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index b24532c23f4..c7b47d1c3c7 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -79,7 +79,15 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance));
dispatch(updateDatasourceInstance(exploreId, newDataSourceInstance));
- dispatch(loadDatasource(exploreId, newDataSourceInstance));
+
+ try {
+ await dispatch(loadDatasource(exploreId, newDataSourceInstance));
+ } catch (error) {
+ console.error(error);
+ return;
+ }
+
+ dispatch(runQueries(exploreId));
};
}
@@ -197,7 +205,14 @@ export function initializeExplore(
}
dispatch(updateDatasourceInstance(exploreId, instance));
- dispatch(loadDatasource(exploreId, instance));
+
+ try {
+ await dispatch(loadDatasource(exploreId, instance));
+ } catch (error) {
+ console.error(error);
+ return;
+ }
+ dispatch(runQueries(exploreId, true));
} else {
dispatch(loadDatasourceMissing(exploreId));
}
@@ -343,8 +358,8 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
// Keep ID to track selection
dispatch(loadDatasourcePending(exploreId, datasourceName));
-
let datasourceError = null;
+
try {
const testResult = await instance.testDatasource();
datasourceError = testResult.status === 'success' ? null : testResult.message;
@@ -354,7 +369,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
if (datasourceError) {
dispatch(loadDatasourceFailure(exploreId, datasourceError));
- return;
+ return Promise.reject(`${datasourceName} loading failed`);
}
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
@@ -372,7 +387,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
}
dispatch(loadDatasourceSuccess(exploreId, instance));
- dispatch(runQueries(exploreId));
+ return Promise.resolve();
};
}
@@ -572,14 +587,14 @@ export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult
/**
* Main action to run queries and dispatches sub-actions based on which result viewers are active
*/
-export function runQueries(exploreId: ExploreId) {
+export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
return (dispatch, getState) => {
const {
datasourceInstance,
modifiedQueries,
- // showingLogs,
- // showingGraph,
- // showingTable,
+ showingLogs,
+ showingGraph,
+ showingTable,
supportsGraph,
supportsLogs,
supportsTable,
@@ -596,7 +611,7 @@ export function runQueries(exploreId: ExploreId) {
const interval = datasourceInstance.interval;
// Keep table queries first since they need to return quickly
- if (/*showingTable &&*/ supportsTable) {
+ if ((ignoreUIState || showingTable) && supportsTable) {
dispatch(
runQueriesForType(
exploreId,
@@ -611,7 +626,7 @@ export function runQueries(exploreId: ExploreId) {
)
);
}
- if (/*showingGraph &&*/ supportsGraph) {
+ if ((ignoreUIState || showingGraph) && supportsGraph) {
dispatch(
runQueriesForType(
exploreId,
@@ -625,9 +640,10 @@ export function runQueries(exploreId: ExploreId) {
)
);
}
- if (/*showingLogs &&*/ supportsLogs) {
+ if ((ignoreUIState || showingLogs) && supportsLogs) {
dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
}
+
dispatch(stateSave());
};
}
From 1a0b21b8d1e2dc13037a908e7bbb2deba327acfb Mon Sep 17 00:00:00 2001
From: Dominik Prokop
Date: Fri, 1 Feb 2019 15:27:02 +0100
Subject: [PATCH 027/770] Minor post review changes
---
public/app/core/utils/explore.ts | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts
index 7128019b1fb..faf46118718 100644
--- a/public/app/core/utils/explore.ts
+++ b/public/app/core/utils/explore.ts
@@ -157,6 +157,8 @@ const isMetricSegment = (segment: { [key: string]: string }) => segment.hasOwnPr
const isUISegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('ui');
export function parseUrlState(initial: string | undefined): ExploreUrlState {
+ let uiState = DEFAULT_UI_STATE;
+
if (initial) {
try {
const parsed = JSON.parse(decodeURI(initial));
@@ -169,8 +171,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
to: parsed[1],
};
const datasource = parsed[2];
- let queries = [],
- ui;
+ let queries = [];
parsed.slice(3).forEach(segment => {
if (isMetricSegment(segment)) {
@@ -178,7 +179,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
}
if (isUISegment(segment)) {
- ui = {
+ uiState = {
showingGraph: segment.ui[0],
showingLogs: segment.ui[1],
showingTable: segment.ui[2],
@@ -186,14 +187,14 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
}
});
- return { datasource, queries, range, ui };
+ return { datasource, queries, range, ui: uiState };
}
return parsed;
} catch (e) {
console.error(e);
}
}
- return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: DEFAULT_UI_STATE };
+ return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: uiState };
}
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
From bd6fed54de73d195df50a7e02666a454e3120726 Mon Sep 17 00:00:00 2001
From: Peter Holmberg
Date: Fri, 1 Feb 2019 15:45:47 +0100
Subject: [PATCH 028/770] first stuff
---
.../ValueMappingsEditor.story.tsx | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
new file mode 100644
index 00000000000..31ba6454753
--- /dev/null
+++ b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+import { ValueMappingsEditor } from './ValueMappingsEditor';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+
+const ValueMappingsEditorStories = storiesOf('UI/ValueMappingsEditor', module);
+
+ValueMappingsEditorStories.addDecorator(withCenteredStory);
+
+ValueMappingsEditorStories.add('default', () => {
+ return (
+ {
+ action('Mapping changed');
+ }}
+ />
+ );
+});
From 9ac960a80380b6b7612c443c041c0055bbd95759 Mon Sep 17 00:00:00 2001
From: Peter Holmberg
Date: Sat, 2 Feb 2019 00:48:13 +0100
Subject: [PATCH 029/770] did not add file, removing centerered
---
.../ValueMappingsEditor/ValueMappingsEditor.story.tsx | 3 ---
1 file changed, 3 deletions(-)
diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
index 31ba6454753..d6c8cec8c1e 100644
--- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
+++ b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
@@ -2,12 +2,9 @@ import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { ValueMappingsEditor } from './ValueMappingsEditor';
-import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
const ValueMappingsEditorStories = storiesOf('UI/ValueMappingsEditor', module);
-ValueMappingsEditorStories.addDecorator(withCenteredStory);
-
ValueMappingsEditorStories.add('default', () => {
return (
Date: Sat, 2 Feb 2019 13:35:17 +0800
Subject: [PATCH 030/770] Remove option used to control within browser
---
pkg/services/alerting/notifiers/dingding.go | 38 ++++++++-------------
1 file changed, 14 insertions(+), 24 deletions(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index 3514554a1db..a3934903bd6 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -22,11 +22,6 @@ const DingdingOptionsTemplate = `
MessageType
-
- OpenInBrowser
-
- Open the message url in browser instead of inside of Dingding
-
`
func init() {
@@ -47,23 +42,20 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType)
- openInBrowser := model.Settings.Get("openInBrowser").MustBool(true)
return &DingDingNotifier{
- NotifierBase: NewNotifierBase(model),
- OpenInBrowser: openInBrowser,
- MsgType: msgType,
- Url: url,
- log: log.New("alerting.notifier.dingding"),
+ NotifierBase: NewNotifierBase(model),
+ MsgType: msgType,
+ Url: url,
+ log: log.New("alerting.notifier.dingding"),
}, nil
}
type DingDingNotifier struct {
NotifierBase
- MsgType string
- OpenInBrowser bool //Set whether the message url will open outside of Dingding
- Url string
- log log.Logger
+ MsgType string
+ Url string
+ log log.Logger
}
func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -75,17 +67,15 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
messageUrl = ""
}
- if this.OpenInBrowser {
- q := url.Values{
- "pc_slide": {"false"},
- "url": {messageUrl},
- }
-
- // Use special link to auto open the message url outside of Dingding
- // Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
- messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode()
+ q := url.Values{
+ "pc_slide": {"false"},
+ "url": {messageUrl},
}
+ // Use special link to auto open the message url outside of Dingding
+ // Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
+ messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode()
+
this.log.Info("messageUrl:" + messageUrl)
message := evalContext.Rule.Message
From 70b23ab73bfbf364c97da23fe9a829ae9099731c Mon Sep 17 00:00:00 2001
From: Athurg Feng
Date: Sat, 2 Feb 2019 13:36:10 +0800
Subject: [PATCH 031/770] Add string quote func
---
pkg/services/alerting/notifiers/dingding.go | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go
index a3934903bd6..3e3496622b7 100644
--- a/pkg/services/alerting/notifiers/dingding.go
+++ b/pkg/services/alerting/notifiers/dingding.go
@@ -3,6 +3,7 @@ package notifiers
import (
"fmt"
"net/url"
+ "strings"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -99,8 +100,8 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyStr = `{
"msgtype": "actionCard",
"actionCard": {
- "text": "` + message + `",
- "title": "` + title + `",
+ "text": "` + strings.Replace(message, `"`, "'", -1) + `",
+ "title": "` + strings.Replace(title, `"`, "'", -1) + `",
"singleTitle": "More",
"singleURL": "` + messageUrl + `"
}
From 60f700a1d217593ee1bca29d0be12328b21e0fde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sat, 2 Feb 2019 19:23:19 +0100
Subject: [PATCH 032/770] wip: dashboard react
---
.../dashboard/containers/DashboardCtrl.ts | 6 -
.../dashboard/containers/DashboardPage.tsx | 138 ++++++++++++++++++
.../features/dashboard/state/initDashboard.ts | 5 +
public/app/routes/routes.ts | 9 +-
4 files changed, 149 insertions(+), 9 deletions(-)
create mode 100644 public/app/features/dashboard/containers/DashboardPage.tsx
create mode 100644 public/app/features/dashboard/state/initDashboard.ts
diff --git a/public/app/features/dashboard/containers/DashboardCtrl.ts b/public/app/features/dashboard/containers/DashboardCtrl.ts
index 74795315504..0151f8f7331 100644
--- a/public/app/features/dashboard/containers/DashboardCtrl.ts
+++ b/public/app/features/dashboard/containers/DashboardCtrl.ts
@@ -31,12 +31,6 @@ export class DashboardCtrl {
// temp hack due to way dashboards are loaded
// can't use controllerAs on route yet
$scope.ctrl = this;
-
- // TODO: break out settings view to separate view & controller
- this.editTab = 0;
-
- // funcs called from React component bindings and needs this binding
- this.getPanelContainer = this.getPanelContainer.bind(this);
}
setupDashboard(data) {
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
new file mode 100644
index 00000000000..54eed34fc29
--- /dev/null
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -0,0 +1,138 @@
+// Libraries
+import React, { Component } from 'react';
+import { hot } from 'react-hot-loader';
+import { connect } from 'react-redux';
+
+// Utils & Services
+import locationUtil from 'app/core/utils/location_util';
+import { getBackendSrv } from 'app/core/services/backend_srv';
+import { createErrorNotification } from 'app/core/copy/appNotification';
+
+// Components
+import { LoadingPlaceholder } from '@grafana/ui';
+
+// Redux
+import { updateLocation } from 'app/core/actions';
+import { notifyApp } from 'app/core/actions';
+
+// Types
+import { StoreState } from 'app/types';
+import { DashboardModel } from 'app/features/dashboard/state';
+
+interface Props {
+ panelId: string;
+ urlUid?: string;
+ urlSlug?: string;
+ urlType?: string;
+ $scope: any;
+ $injector: any;
+ updateLocation: typeof updateLocation;
+ notifyApp: typeof notifyApp;
+}
+
+interface State {
+ dashboard: DashboardModel | null;
+ notFound: boolean;
+}
+
+export class DashboardPage extends Component {
+ state: State = {
+ dashboard: null,
+ notFound: false,
+ };
+
+ async componentDidMount() {
+ const { $injector, urlUid, urlType, urlSlug } = this.props;
+
+ // handle old urls with no uid
+ if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) {
+ this.redirectToNewUrl();
+ return;
+ }
+
+ const loaderSrv = $injector.get('dashboardLoaderSrv');
+ const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
+
+ try {
+ this.initDashboard(dashDTO);
+ } catch (err) {
+ this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString()));
+ console.log(err);
+ }
+ }
+
+ redirectToNewUrl() {
+ getBackendSrv()
+ .getDashboardBySlug(this.props.urlSlug)
+ .then(res => {
+ if (res) {
+ const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
+ this.props.updateLocation(url);
+ }
+ });
+ }
+
+ initDashboard(dashDTO: any) {
+ const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
+
+ // init services
+ this.timeSrv.init(dashboard);
+ this.annotationsSrv.init(dashboard);
+
+ // template values service needs to initialize completely before
+ // the rest of the dashboard can load
+ this.variableSrv
+ .init(dashboard)
+ // template values failes are non fatal
+ .catch(this.onInitFailed.bind(this, 'Templating init failed', false))
+ // continue
+ .finally(() => {
+ this.dashboard = dashboard;
+ this.dashboard.processRepeats();
+ this.dashboard.updateSubmenuVisibility();
+ this.dashboard.autoFitPanels(window.innerHeight);
+
+ this.unsavedChangesSrv.init(dashboard, this.$scope);
+
+ // TODO refactor ViewStateSrv
+ this.$scope.dashboard = dashboard;
+ this.dashboardViewState = this.dashboardViewStateSrv.create(this.$scope);
+
+ this.keybindingSrv.setupDashboardBindings(this.$scope, dashboard);
+ this.setWindowTitleAndTheme();
+
+ appEvents.emit('dashboard-initialized', dashboard);
+ })
+ .catch(this.onInitFailed.bind(this, 'Dashboard init failed', true));
+
+ this.setState({ dashboard });
+ }
+
+ render() {
+ const { notFound, dashboard } = this.state;
+
+ if (notFound) {
+ return Dashboard not found
;
+ }
+
+ if (!dashboard) {
+ return ;
+ }
+
+ return title: {dashboard.title}
;
+ }
+}
+
+const mapStateToProps = (state: StoreState) => ({
+ urlUid: state.location.routeParams.uid,
+ urlSlug: state.location.routeParams.slug,
+ urlType: state.location.routeParams.type,
+ panelId: state.location.query.panelId,
+});
+
+const mapDispatchToProps = {
+ updateLocation,
+ notifyApp,
+};
+
+export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage));
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
new file mode 100644
index 00000000000..3b2307b3ccc
--- /dev/null
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -0,0 +1,5 @@
+
+
+export function initDashboard(dashboard: DashboardModel, $injector: any, $scope: any) {
+
+}
diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts
index 0f4c09a9c77..cdd9ed89a08 100644
--- a/public/app/routes/routes.ts
+++ b/public/app/routes/routes.ts
@@ -20,6 +20,7 @@ import DataSourceDashboards from 'app/features/datasources/DataSourceDashboards'
import DataSourceSettingsPage from '../features/datasources/settings/DataSourceSettingsPage';
import OrgDetailsPage from '../features/org/OrgDetailsPage';
import SoloPanelPage from '../features/dashboard/containers/SoloPanelPage';
+import DashboardPage from '../features/dashboard/containers/DashboardPage';
import config from 'app/core/config';
/** @ngInject */
@@ -34,10 +35,12 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
pageClass: 'page-dashboard',
})
.when('/d/:uid/:slug', {
- templateUrl: 'public/app/partials/dashboard.html',
- controller: 'LoadDashboardCtrl',
- reloadOnSearch: false,
+ template: ' ',
pageClass: 'page-dashboard',
+ reloadOnSearch: false,
+ resolve: {
+ component: () => DashboardPage,
+ },
})
.when('/d/:uid', {
templateUrl: 'public/app/partials/dashboard.html',
From d86e773c756806bb826af50c347709bce265a65f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sat, 2 Feb 2019 22:43:19 +0100
Subject: [PATCH 033/770] wip: minor progress
---
.../dashboard/containers/DashboardPage.tsx | 128 +++++++-----------
.../app/features/dashboard/state/actions.ts | 41 +++---
.../features/dashboard/state/initDashboard.ts | 67 ++++++++-
.../features/dashboard/state/reducers.test.ts | 4 +-
.../app/features/dashboard/state/reducers.ts | 33 +++--
public/app/types/dashboard.ts | 17 ++-
6 files changed, 171 insertions(+), 119 deletions(-)
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 54eed34fc29..0e3e1058660 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -3,21 +3,16 @@ import React, { Component } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
-// Utils & Services
-import locationUtil from 'app/core/utils/location_util';
-import { getBackendSrv } from 'app/core/services/backend_srv';
-import { createErrorNotification } from 'app/core/copy/appNotification';
-
// Components
import { LoadingPlaceholder } from '@grafana/ui';
// Redux
-import { updateLocation } from 'app/core/actions';
-import { notifyApp } from 'app/core/actions';
+import { initDashboard } from '../state/initDashboard';
// Types
import { StoreState } from 'app/types';
import { DashboardModel } from 'app/features/dashboard/state';
+import { DashboardLoadingState } from 'app/types/dashboard';
interface Props {
panelId: string;
@@ -26,8 +21,9 @@ interface Props {
urlType?: string;
$scope: any;
$injector: any;
- updateLocation: typeof updateLocation;
- notifyApp: typeof notifyApp;
+ initDashboard: typeof initDashboard;
+ loadingState: DashboardLoadingState;
+ dashboard: DashboardModel;
}
interface State {
@@ -42,81 +38,54 @@ export class DashboardPage extends Component {
};
async componentDidMount() {
- const { $injector, urlUid, urlType, urlSlug } = this.props;
+ this.props.initDashboard({
+ injector: this.props.$injector,
+ scope: this.props.$scope,
+ urlSlug: this.props.urlSlug,
+ urlUid: this.props.urlUid,
+ urlType: this.props.urlType,
+ })
- // handle old urls with no uid
- if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) {
- this.redirectToNewUrl();
- return;
- }
-
- const loaderSrv = $injector.get('dashboardLoaderSrv');
- const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
-
- try {
- this.initDashboard(dashDTO);
- } catch (err) {
- this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString()));
- console.log(err);
- }
+ // const { $injector, urlUid, urlType, urlSlug } = this.props;
+ //
+ // // handle old urls with no uid
+ // if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) {
+ // this.redirectToNewUrl();
+ // return;
+ // }
+ //
+ // const loaderSrv = $injector.get('dashboardLoaderSrv');
+ // const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
+ //
+ // try {
+ // this.initDashboard(dashDTO);
+ // } catch (err) {
+ // this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString()));
+ // console.log(err);
+ // }
}
- redirectToNewUrl() {
- getBackendSrv()
- .getDashboardBySlug(this.props.urlSlug)
- .then(res => {
- if (res) {
- const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
- this.props.updateLocation(url);
- }
- });
- }
-
- initDashboard(dashDTO: any) {
- const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
-
- // init services
- this.timeSrv.init(dashboard);
- this.annotationsSrv.init(dashboard);
-
- // template values service needs to initialize completely before
- // the rest of the dashboard can load
- this.variableSrv
- .init(dashboard)
- // template values failes are non fatal
- .catch(this.onInitFailed.bind(this, 'Templating init failed', false))
- // continue
- .finally(() => {
- this.dashboard = dashboard;
- this.dashboard.processRepeats();
- this.dashboard.updateSubmenuVisibility();
- this.dashboard.autoFitPanels(window.innerHeight);
-
- this.unsavedChangesSrv.init(dashboard, this.$scope);
-
- // TODO refactor ViewStateSrv
- this.$scope.dashboard = dashboard;
- this.dashboardViewState = this.dashboardViewStateSrv.create(this.$scope);
-
- this.keybindingSrv.setupDashboardBindings(this.$scope, dashboard);
- this.setWindowTitleAndTheme();
-
- appEvents.emit('dashboard-initialized', dashboard);
- })
- .catch(this.onInitFailed.bind(this, 'Dashboard init failed', true));
-
- this.setState({ dashboard });
- }
+ // redirectToNewUrl() {
+ // getBackendSrv()
+ // .getDashboardBySlug(this.props.urlSlug)
+ // .then(res => {
+ // if (res) {
+ // const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
+ // this.props.updateLocation(url);
+ // }
+ // });
+ // }
+ //
+ // initDashboard(dashDTO: any) {
+ // const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
+ // this.setState({ dashboard });
+ // }
render() {
- const { notFound, dashboard } = this.state;
-
- if (notFound) {
- return Dashboard not found
;
- }
+ const { loadingState, dashboard } = this.props;
if (!dashboard) {
- return ;
+ return ;
}
return title: {dashboard.title}
;
@@ -128,11 +97,12 @@ const mapStateToProps = (state: StoreState) => ({
urlSlug: state.location.routeParams.slug,
urlType: state.location.routeParams.type,
panelId: state.location.query.panelId,
+ loadingState: state.dashboard.loadingState,
+ dashboard: state.dashboard as DashboardModel,
});
const mapDispatchToProps = {
- updateLocation,
- notifyApp,
+ initDashboard
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage));
diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts
index 4dcf0a925b7..1bb29dc3ad5 100644
--- a/public/app/features/dashboard/state/actions.ts
+++ b/public/app/features/dashboard/state/actions.ts
@@ -1,8 +1,18 @@
+// Libaries
import { StoreState } from 'app/types';
import { ThunkAction } from 'redux-thunk';
+
+// Services & Utils
import { getBackendSrv } from 'app/core/services/backend_srv';
-import appEvents from 'app/core/app_events';
+import { actionCreatorFactory } from 'app/core/redux';
+import { ActionOf } from 'app/core/redux/actionCreatorFactory';
+import { createSuccessNotification } from 'app/core/copy/appNotification';
+
+// Actions
import { loadPluginDashboards } from '../../plugins/state/actions';
+import { notifyApp } from 'app/core/actions';
+
+// Types
import {
DashboardAcl,
DashboardAclDTO,
@@ -10,30 +20,14 @@ import {
DashboardAclUpdateDTO,
NewDashboardAclItem,
} from 'app/types/acl';
+import { DashboardLoadingState } from 'app/types/dashboard';
-export enum ActionTypes {
- LoadDashboardPermissions = 'LOAD_DASHBOARD_PERMISSIONS',
- LoadStarredDashboards = 'LOAD_STARRED_DASHBOARDS',
-}
+export const loadDashboardPermissions = actionCreatorFactory('LOAD_DASHBOARD_PERMISSIONS').create();
+export const setDashboardLoadingState = actionCreatorFactory('SET_DASHBOARD_LOADING_STATE').create();
-export interface LoadDashboardPermissionsAction {
- type: ActionTypes.LoadDashboardPermissions;
- payload: DashboardAcl[];
-}
+export type Action = ActionOf;
-export interface LoadStarredDashboardsAction {
- type: ActionTypes.LoadStarredDashboards;
- payload: DashboardAcl[];
-}
-
-export type Action = LoadDashboardPermissionsAction | LoadStarredDashboardsAction;
-
-type ThunkResult = ThunkAction;
-
-export const loadDashboardPermissions = (items: DashboardAclDTO[]): LoadDashboardPermissionsAction => ({
- type: ActionTypes.LoadDashboardPermissions,
- payload: items,
-});
+export type ThunkResult = ThunkAction;
export function getDashboardPermissions(id: number): ThunkResult {
return async dispatch => {
@@ -124,7 +118,7 @@ export function addDashboardPermission(dashboardId: number, newItem: NewDashboar
export function importDashboard(data, dashboardTitle: string): ThunkResult {
return async dispatch => {
await getBackendSrv().post('/api/dashboards/import', data);
- appEvents.emit('alert-success', ['Dashboard Imported', dashboardTitle]);
+ dispatch(notifyApp(createSuccessNotification('Dashboard Imported', dashboardTitle)));
dispatch(loadPluginDashboards());
};
}
@@ -135,3 +129,4 @@ export function removeDashboard(uri: string): ThunkResult {
dispatch(loadPluginDashboards());
};
}
+
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 3b2307b3ccc..124d03eee4a 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -1,5 +1,70 @@
+// Libaries
+import { StoreState } from 'app/types';
+import { ThunkAction } from 'redux-thunk';
+// Services & Utils
+import { getBackendSrv } from 'app/core/services/backend_srv';
+import { createErrorNotification } from 'app/core/copy/appNotification';
-export function initDashboard(dashboard: DashboardModel, $injector: any, $scope: any) {
+// Actions
+import { updateLocation } from 'app/core/actions';
+import { notifyApp } from 'app/core/actions';
+import locationUtil from 'app/core/utils/location_util';
+import { setDashboardLoadingState, ThunkResult } from './actions';
+// Types
+import { DashboardLoadingState } from 'app/types/dashboard';
+import { DashboardModel } from './DashboardModel';
+
+export interface InitDashboardArgs {
+ injector: any;
+ scope: any;
+ urlUid?: string;
+ urlSlug?: string;
+ urlType?: string;
+}
+
+export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: InitDashboardArgs): ThunkResult {
+ return async dispatch => {
+ const loaderSrv = injector.get('dashboardLoaderSrv');
+
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching));
+
+ try {
+ // fetch dashboard from api
+ const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
+ // set initializing state
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Initializing));
+ // create model
+ const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
+ // init services
+
+ injector.get('timeSrv').init(dashboard);
+ injector.get('annotationsSrv').init(dashboard);
+
+ // template values service needs to initialize completely before
+ // the rest of the dashboard can load
+ injector.get('variableSrv').init(dashboard)
+ .catch(err => {
+ dispatch(notifyApp(createErrorNotification('Templating init failed')));
+ })
+ .finally(() => {
+
+ dashboard.processRepeats();
+ dashboard.updateSubmenuVisibility();
+ dashboard.autoFitPanels(window.innerHeight);
+
+ injector.get('unsavedChangesSrv').init(dashboard, scope);
+
+ scope.dashboard = dashboard;
+ injector.get('dashboardViewStateSrv').create(scope);
+ injector.get('keybindingSrv').setupDashboardBindings(scope, dashboard);
+ })
+ .catch(err => {
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
+ });
+ } catch (err) {
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
+ }
+ };
}
diff --git a/public/app/features/dashboard/state/reducers.test.ts b/public/app/features/dashboard/state/reducers.test.ts
index ced8866aad8..ea3353ce741 100644
--- a/public/app/features/dashboard/state/reducers.test.ts
+++ b/public/app/features/dashboard/state/reducers.test.ts
@@ -1,4 +1,4 @@
-import { Action, ActionTypes } from './actions';
+import { Action } from './actions';
import { OrgRole, PermissionLevel, DashboardState } from 'app/types';
import { initialState, dashboardReducer } from './reducers';
@@ -8,7 +8,7 @@ describe('dashboard reducer', () => {
beforeEach(() => {
const action: Action = {
- type: ActionTypes.LoadDashboardPermissions,
+ type: 'LOAD_DASHBOARD_PERMISSIONS',
payload: [
{ id: 2, dashboardId: 1, role: OrgRole.Viewer, permission: PermissionLevel.View },
{ id: 3, dashboardId: 1, role: OrgRole.Editor, permission: PermissionLevel.Edit },
diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts
index 8a79a6c9f77..bd13446b090 100644
--- a/public/app/features/dashboard/state/reducers.ts
+++ b/public/app/features/dashboard/state/reducers.ts
@@ -1,21 +1,30 @@
-import { DashboardState } from 'app/types';
-import { Action, ActionTypes } from './actions';
+import { DashboardState, DashboardLoadingState } from 'app/types/dashboard';
+import { loadDashboardPermissions, setDashboardLoadingState } from './actions';
+import { reducerFactory } from 'app/core/redux';
import { processAclItems } from 'app/core/utils/acl';
export const initialState: DashboardState = {
+ loadingState: DashboardLoadingState.NotStarted,
+ dashboard: null,
permissions: [],
};
-export const dashboardReducer = (state = initialState, action: Action): DashboardState => {
- switch (action.type) {
- case ActionTypes.LoadDashboardPermissions:
- return {
- ...state,
- permissions: processAclItems(action.payload),
- };
- }
- return state;
-};
+export const dashboardReducer = reducerFactory(initialState)
+ .addMapper({
+ filter: loadDashboardPermissions,
+ mapper: (state, action) => ({
+ ...state,
+ permissions: processAclItems(action.payload),
+ }),
+ })
+ .addMapper({
+ filter: setDashboardLoadingState,
+ mapper: (state, action) => ({
+ ...state,
+ loadingState: action.payload
+ }),
+ })
+ .create()
export default {
dashboard: dashboardReducer,
diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts
index d33405c985e..df9a2e53548 100644
--- a/public/app/types/dashboard.ts
+++ b/public/app/types/dashboard.ts
@@ -1,5 +1,18 @@
import { DashboardAcl } from './acl';
-export interface DashboardState {
- permissions: DashboardAcl[];
+export interface Dashboard {
+}
+
+export enum DashboardLoadingState {
+ NotStarted = 'Not started',
+ Fetching = 'Fetching',
+ Initializing = 'Initializing',
+ Error = 'Error',
+ Done = 'Done',
+}
+
+export interface DashboardState {
+ dashboard: Dashboard | null;
+ loadingState: DashboardLoadingState;
+ permissions: DashboardAcl[] | null;
}
From 83937f59c008343e7e1d000a088807bccb476e4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sat, 2 Feb 2019 23:01:48 +0100
Subject: [PATCH 034/770] wip: dashboard in react starting to work
---
.../dashboard/containers/DashboardPage.tsx | 40 +--------
.../app/features/dashboard/state/actions.ts | 3 +-
.../features/dashboard/state/initDashboard.ts | 86 +++++++++++--------
.../app/features/dashboard/state/reducers.ts | 11 ++-
public/app/types/dashboard.ts | 4 +-
5 files changed, 66 insertions(+), 78 deletions(-)
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 0e3e1058660..c0d5c4d4730 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
// Components
import { LoadingPlaceholder } from '@grafana/ui';
+import { DashboardGrid } from '../dashgrid/DashboardGrid';
// Redux
import { initDashboard } from '../state/initDashboard';
@@ -45,42 +46,8 @@ export class DashboardPage extends Component {
urlUid: this.props.urlUid,
urlType: this.props.urlType,
})
-
- // const { $injector, urlUid, urlType, urlSlug } = this.props;
- //
- // // handle old urls with no uid
- // if (!urlUid && !(urlType === 'script' || urlType === 'snapshot')) {
- // this.redirectToNewUrl();
- // return;
- // }
- //
- // const loaderSrv = $injector.get('dashboardLoaderSrv');
- // const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
- //
- // try {
- // this.initDashboard(dashDTO);
- // } catch (err) {
- // this.props.notifyApp(createErrorNotification('Failed to init dashboard', err.toString()));
- // console.log(err);
- // }
}
- // redirectToNewUrl() {
- // getBackendSrv()
- // .getDashboardBySlug(this.props.urlSlug)
- // .then(res => {
- // if (res) {
- // const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
- // this.props.updateLocation(url);
- // }
- // });
- // }
- //
- // initDashboard(dashDTO: any) {
- // const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
- // this.setState({ dashboard });
- // }
-
render() {
const { loadingState, dashboard } = this.props;
@@ -88,7 +55,8 @@ export class DashboardPage extends Component {
return ;
}
- return title: {dashboard.title}
;
+ console.log(dashboard);
+ return
}
}
@@ -98,7 +66,7 @@ const mapStateToProps = (state: StoreState) => ({
urlType: state.location.routeParams.type,
panelId: state.location.query.panelId,
loadingState: state.dashboard.loadingState,
- dashboard: state.dashboard as DashboardModel,
+ dashboard: state.dashboard.model as DashboardModel,
});
const mapDispatchToProps = {
diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts
index 1bb29dc3ad5..14721cdbe96 100644
--- a/public/app/features/dashboard/state/actions.ts
+++ b/public/app/features/dashboard/state/actions.ts
@@ -20,10 +20,11 @@ import {
DashboardAclUpdateDTO,
NewDashboardAclItem,
} from 'app/types/acl';
-import { DashboardLoadingState } from 'app/types/dashboard';
+import { DashboardLoadingState, MutableDashboard } from 'app/types/dashboard';
export const loadDashboardPermissions = actionCreatorFactory('LOAD_DASHBOARD_PERMISSIONS').create();
export const setDashboardLoadingState = actionCreatorFactory('SET_DASHBOARD_LOADING_STATE').create();
+export const setDashboardModel = actionCreatorFactory('SET_DASHBOARD_MODEL').create();
export type Action = ActionOf;
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 124d03eee4a..d20f9ae1cf8 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -1,16 +1,11 @@
-// Libaries
-import { StoreState } from 'app/types';
-import { ThunkAction } from 'redux-thunk';
-
// Services & Utils
-import { getBackendSrv } from 'app/core/services/backend_srv';
import { createErrorNotification } from 'app/core/copy/appNotification';
// Actions
import { updateLocation } from 'app/core/actions';
import { notifyApp } from 'app/core/actions';
import locationUtil from 'app/core/utils/location_util';
-import { setDashboardLoadingState, ThunkResult } from './actions';
+import { setDashboardLoadingState, ThunkResult, setDashboardModel } from './actions';
// Types
import { DashboardLoadingState } from 'app/types/dashboard';
@@ -30,41 +25,58 @@ export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: Ini
dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching));
+ let dashDTO = null;
+
try {
// fetch dashboard from api
- const dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
- // set initializing state
- dispatch(setDashboardLoadingState(DashboardLoadingState.Initializing));
- // create model
- const dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
- // init services
-
- injector.get('timeSrv').init(dashboard);
- injector.get('annotationsSrv').init(dashboard);
-
- // template values service needs to initialize completely before
- // the rest of the dashboard can load
- injector.get('variableSrv').init(dashboard)
- .catch(err => {
- dispatch(notifyApp(createErrorNotification('Templating init failed')));
- })
- .finally(() => {
-
- dashboard.processRepeats();
- dashboard.updateSubmenuVisibility();
- dashboard.autoFitPanels(window.innerHeight);
-
- injector.get('unsavedChangesSrv').init(dashboard, scope);
-
- scope.dashboard = dashboard;
- injector.get('dashboardViewStateSrv').create(scope);
- injector.get('keybindingSrv').setupDashboardBindings(scope, dashboard);
- })
- .catch(err => {
- dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
- });
+ dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
} catch (err) {
dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
+ console.log(err);
+ return;
}
+
+ // set initializing state
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Initializing));
+
+ // create model
+ let dashboard: DashboardModel;
+ try {
+ dashboard = new DashboardModel(dashDTO.dashboard, dashDTO.meta);
+ } catch (err) {
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
+ console.log(err);
+ return;
+ }
+
+ // init services
+ injector.get('timeSrv').init(dashboard);
+ injector.get('annotationsSrv').init(dashboard);
+
+ // template values service needs to initialize completely before
+ // the rest of the dashboard can load
+ try {
+ await injector.get('variableSrv').init(dashboard);
+ } catch (err) {
+ dispatch(notifyApp(createErrorNotification('Templating init failed', err.toString())));
+ console.log(err);
+ }
+
+ try {
+ dashboard.processRepeats();
+ dashboard.updateSubmenuVisibility();
+ dashboard.autoFitPanels(window.innerHeight);
+
+ injector.get('unsavedChangesSrv').init(dashboard, scope);
+
+ scope.dashboard = dashboard;
+ injector.get('dashboardViewStateSrv').create(scope);
+ injector.get('keybindingSrv').setupDashboardBindings(scope, dashboard);
+ } catch (err) {
+ dispatch(notifyApp(createErrorNotification('Dashboard init failed', err.toString())));
+ console.log(err);
+ }
+
+ dispatch(setDashboardModel(dashboard));
};
}
diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts
index bd13446b090..5cfc879a1a4 100644
--- a/public/app/features/dashboard/state/reducers.ts
+++ b/public/app/features/dashboard/state/reducers.ts
@@ -1,11 +1,11 @@
import { DashboardState, DashboardLoadingState } from 'app/types/dashboard';
-import { loadDashboardPermissions, setDashboardLoadingState } from './actions';
+import { loadDashboardPermissions, setDashboardLoadingState, setDashboardModel } from './actions';
import { reducerFactory } from 'app/core/redux';
import { processAclItems } from 'app/core/utils/acl';
export const initialState: DashboardState = {
loadingState: DashboardLoadingState.NotStarted,
- dashboard: null,
+ model: null,
permissions: [],
};
@@ -24,6 +24,13 @@ export const dashboardReducer = reducerFactory(initialState)
loadingState: action.payload
}),
})
+ .addMapper({
+ filter: setDashboardModel,
+ mapper: (state, action) => ({
+ ...state,
+ model: action.payload
+ }),
+ })
.create()
export default {
diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts
index df9a2e53548..bdea5b04bac 100644
--- a/public/app/types/dashboard.ts
+++ b/public/app/types/dashboard.ts
@@ -1,6 +1,6 @@
import { DashboardAcl } from './acl';
-export interface Dashboard {
+export interface MutableDashboard {
}
export enum DashboardLoadingState {
@@ -12,7 +12,7 @@ export enum DashboardLoadingState {
}
export interface DashboardState {
- dashboard: Dashboard | null;
+ model: MutableDashboard | null;
loadingState: DashboardLoadingState;
permissions: DashboardAcl[] | null;
}
From 8dec74689d2361febda5647d29f3ff5f46e0007c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 10:55:58 +0100
Subject: [PATCH 035/770] Dashboard settings starting to work
---
public/app/core/app_events.ts | 3 +-
.../dashboard/components/DashNav/DashNav.tsx | 144 ++++++++++++++++++
.../dashboard/components/DashNav/index.ts | 2 +
.../DashboardSettings/DashboardSettings.tsx | 36 +++++
.../components/DashboardSettings/index.ts | 1 +
.../dashboard/containers/DashboardPage.tsx | 100 ++++++++++--
.../features/dashboard/state/initDashboard.ts | 1 +
.../app/features/dashboard/state/reducers.ts | 2 +-
.../sass/components/_dashboard_settings.scss | 3 +
9 files changed, 276 insertions(+), 16 deletions(-)
create mode 100644 public/app/features/dashboard/components/DashNav/DashNav.tsx
create mode 100644 public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx
diff --git a/public/app/core/app_events.ts b/public/app/core/app_events.ts
index 6af7913167b..1951fd87001 100644
--- a/public/app/core/app_events.ts
+++ b/public/app/core/app_events.ts
@@ -1,4 +1,5 @@
import { Emitter } from './utils/emitter';
-const appEvents = new Emitter();
+export const appEvents = new Emitter();
+
export default appEvents;
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
new file mode 100644
index 00000000000..e1fb70e5d68
--- /dev/null
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -0,0 +1,144 @@
+// Libaries
+import React, { PureComponent } from 'react';
+import { connect } from 'react-redux';
+
+// Utils & Services
+import { appEvents } from 'app/core/app_events';
+
+// State
+import { updateLocation } from 'app/core/actions';
+
+// Types
+import { DashboardModel } from '../../state/DashboardModel';
+
+export interface Props {
+ dashboard: DashboardModel | null;
+ updateLocation: typeof updateLocation;
+}
+
+export class DashNav extends PureComponent {
+ onOpenSearch = () => {
+ appEvents.emit('show-dash-search');
+ };
+
+ onAddPanel = () => {};
+ onOpenSettings = () => {
+ this.props.updateLocation({
+ query: {
+ editview: 'settings',
+ },
+ partial: true,
+ })
+ };
+
+ renderLoadingState() {
+ return (
+
+ );
+ }
+
+ render() {
+ let { dashboard } = this.props;
+
+ if (!dashboard) {
+ return this.renderLoadingState();
+ }
+
+ const haveFolder = dashboard.meta.folderId > 0;
+ const { canEdit, canSave, folderTitle, showSettings } = dashboard.meta;
+
+ return (
+
+
+
+
+ {/*
+
+ */}
+
+
+ {canEdit && (
+
+
+
+ )}
+
+ {showSettings && (
+
+
+
+ )}
+
+ {
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ }
+
+
+ );
+ }
+}
+
+const mapStateToProps = () => ({
+});
+
+const mapDispatchToProps = {
+ updateLocation
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(DashNav);
diff --git a/public/app/features/dashboard/components/DashNav/index.ts b/public/app/features/dashboard/components/DashNav/index.ts
index 854e32b24d2..cfa9003cd8a 100644
--- a/public/app/features/dashboard/components/DashNav/index.ts
+++ b/public/app/features/dashboard/components/DashNav/index.ts
@@ -1 +1,3 @@
export { DashNavCtrl } from './DashNavCtrl';
+import DashNav from './DashNav';
+export { DashNav };
diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx
new file mode 100644
index 00000000000..8a92c0d69eb
--- /dev/null
+++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx
@@ -0,0 +1,36 @@
+// Libaries
+import React, { PureComponent } from 'react';
+
+// Utils & Services
+import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
+
+// Types
+import { DashboardModel } from '../../state/DashboardModel';
+
+export interface Props {
+ dashboard: DashboardModel | null;
+}
+
+export class DashboardSettings extends PureComponent {
+ element: HTMLElement;
+ angularCmp: AngularComponent;
+
+ componentDidMount() {
+ const loader = getAngularLoader();
+
+ const template = ' ';
+ const scopeProps = { dashboard: this.props.dashboard };
+
+ this.angularCmp = loader.load(this.element, scopeProps, template);
+ }
+
+ componentWillUnmount() {
+ if (this.angularCmp) {
+ this.angularCmp.destroy();
+ }
+ }
+
+ render() {
+ return this.element = element} />;
+ }
+}
diff --git a/public/app/features/dashboard/components/DashboardSettings/index.ts b/public/app/features/dashboard/components/DashboardSettings/index.ts
index f81b8cdbc67..0a89feada33 100644
--- a/public/app/features/dashboard/components/DashboardSettings/index.ts
+++ b/public/app/features/dashboard/components/DashboardSettings/index.ts
@@ -1 +1,2 @@
export { SettingsCtrl } from './SettingsCtrl';
+export { DashboardSettings } from './DashboardSettings';
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index c0d5c4d4730..9b088b4735f 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -1,14 +1,19 @@
// Libraries
-import React, { Component } from 'react';
+import $ from 'jquery';
+import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
+import classNames from 'classnames';
// Components
import { LoadingPlaceholder } from '@grafana/ui';
import { DashboardGrid } from '../dashgrid/DashboardGrid';
+import { DashNav } from '../components/DashNav';
+import { DashboardSettings } from '../components/DashboardSettings';
// Redux
import { initDashboard } from '../state/initDashboard';
+import { setDashboardModel } from '../state/actions';
// Types
import { StoreState } from 'app/types';
@@ -20,22 +25,23 @@ interface Props {
urlUid?: string;
urlSlug?: string;
urlType?: string;
+ editview: string;
$scope: any;
$injector: any;
initDashboard: typeof initDashboard;
+ setDashboardModel: typeof setDashboardModel;
loadingState: DashboardLoadingState;
dashboard: DashboardModel;
}
interface State {
- dashboard: DashboardModel | null;
- notFound: boolean;
+ isSettingsOpening: boolean;
}
-export class DashboardPage extends Component
{
+export class DashboardPage extends PureComponent {
state: State = {
- dashboard: null,
- notFound: false,
+ isSettingsOpening: false,
+ isSettingsOpen: false,
};
async componentDidMount() {
@@ -45,18 +51,82 @@ export class DashboardPage extends Component {
urlSlug: this.props.urlSlug,
urlUid: this.props.urlUid,
urlType: this.props.urlType,
- })
+ });
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ const { dashboard, editview } = this.props;
+
+ // when dashboard has loaded subscribe to somme events
+ if (prevProps.dashboard === null && dashboard) {
+ dashboard.events.on('view-mode-changed', this.onViewModeChanged);
+
+ // set initial fullscreen class state
+ this.setPanelFullscreenClass();
+ }
+
+ if (!prevProps.editview && editview) {
+ this.setState({ isSettingsOpening: true });
+ setTimeout(() => {
+ this.setState({ isSettingsOpening: false});
+ }, 10);
+ }
+ }
+
+ onViewModeChanged = () => {
+ this.setPanelFullscreenClass();
+ };
+
+ setPanelFullscreenClass() {
+ $('body').toggleClass('panel-in-fullscreen', this.props.dashboard.meta.fullscreen === true);
+ }
+
+ componentWillUnmount() {
+ if (this.props.dashboard) {
+ this.props.dashboard.destroy();
+ this.props.setDashboardModel(null);
+ }
+ }
+
+ renderLoadingState() {
+ return ;
+ }
+
+ renderDashboard() {
+ const { dashboard, editview } = this.props;
+
+ const classes = classNames({
+ 'dashboard-container': true,
+ 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled
+ });
+
+ return (
+
+ {dashboard && editview &&
}
+
+
+
+
+
+ );
}
render() {
- const { loadingState, dashboard } = this.props;
+ const { dashboard, editview } = this.props;
+ const { isSettingsOpening } = this.state;
- if (!dashboard) {
- return ;
- }
+ const classes = classNames({
+ 'dashboard-page--settings-opening': isSettingsOpening,
+ 'dashboard-page--settings-open': !isSettingsOpening && editview,
+ });
- console.log(dashboard);
- return
+ return (
+
+
+ {!dashboard && this.renderLoadingState()}
+ {dashboard && this.renderDashboard()}
+
+ );
}
}
@@ -65,12 +135,14 @@ const mapStateToProps = (state: StoreState) => ({
urlSlug: state.location.routeParams.slug,
urlType: state.location.routeParams.type,
panelId: state.location.query.panelId,
+ editview: state.location.query.editview,
loadingState: state.dashboard.loadingState,
dashboard: state.dashboard.model as DashboardModel,
});
const mapDispatchToProps = {
- initDashboard
+ initDashboard,
+ setDashboardModel
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage));
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index d20f9ae1cf8..10d7164fbff 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -67,6 +67,7 @@ export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: Ini
dashboard.updateSubmenuVisibility();
dashboard.autoFitPanels(window.innerHeight);
+ // init unsaved changes tracking
injector.get('unsavedChangesSrv').init(dashboard, scope);
scope.dashboard = dashboard;
diff --git a/public/app/features/dashboard/state/reducers.ts b/public/app/features/dashboard/state/reducers.ts
index 5cfc879a1a4..2f4e5df5c14 100644
--- a/public/app/features/dashboard/state/reducers.ts
+++ b/public/app/features/dashboard/state/reducers.ts
@@ -31,7 +31,7 @@ export const dashboardReducer = reducerFactory(initialState)
model: action.payload
}),
})
- .create()
+ .create();
export default {
dashboard: dashboardReducer,
diff --git a/public/sass/components/_dashboard_settings.scss b/public/sass/components/_dashboard_settings.scss
index 5e17e025196..38883b7c80e 100644
--- a/public/sass/components/_dashboard_settings.scss
+++ b/public/sass/components/_dashboard_settings.scss
@@ -16,6 +16,9 @@
opacity: 1;
transition: opacity 300ms ease-in-out;
}
+ .dashboard-container {
+ display: none;
+ }
}
.dashboard-settings__content {
From cba2ca55319cfb7a3bb009548b8f87c839a7e4b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 12:29:47 +0100
Subject: [PATCH 036/770] Url state -> dashboard model state sync starting to
work
---
.../dashboard/components/DashNav/DashNav.tsx | 3 +
.../components/DashNav/template.html | 2 -
.../dashboard/containers/DashboardPage.tsx | 124 +++++++++++++-----
.../dashboard/dashgrid/DashboardGrid.tsx | 41 ++++--
.../services/DashboardViewStateSrv.ts | 30 -----
public/app/routes/GrafanaCtrl.ts | 2 +
public/app/types/dashboard.ts | 4 +
public/views/index-template.html | 2 +-
8 files changed, 128 insertions(+), 80 deletions(-)
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index e1fb70e5d68..f9df483bf5f 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -13,6 +13,9 @@ import { DashboardModel } from '../../state/DashboardModel';
export interface Props {
dashboard: DashboardModel | null;
+ editview: string;
+ isEditing: boolean;
+ isFullscreen: boolean;
updateLocation: typeof updateLocation;
}
diff --git a/public/app/features/dashboard/components/DashNav/template.html b/public/app/features/dashboard/components/DashNav/template.html
index e50a8cd0bff..7e53267cbfd 100644
--- a/public/app/features/dashboard/components/DashNav/template.html
+++ b/public/app/features/dashboard/components/DashNav/template.html
@@ -55,7 +55,5 @@
-
-
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 9b088b4735f..281916acb14 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -5,6 +5,9 @@ import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import classNames from 'classnames';
+// Services & Utils
+import { createErrorNotification } from 'app/core/copy/appNotification';
+
// Components
import { LoadingPlaceholder } from '@grafana/ui';
import { DashboardGrid } from '../dashgrid/DashboardGrid';
@@ -14,34 +17,45 @@ import { DashboardSettings } from '../components/DashboardSettings';
// Redux
import { initDashboard } from '../state/initDashboard';
import { setDashboardModel } from '../state/actions';
+import { updateLocation } from 'app/core/actions';
+import { notifyApp } from 'app/core/actions';
// Types
import { StoreState } from 'app/types';
-import { DashboardModel } from 'app/features/dashboard/state';
+import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardLoadingState } from 'app/types/dashboard';
interface Props {
- panelId: string;
urlUid?: string;
urlSlug?: string;
urlType?: string;
- editview: string;
+ editview?: string;
+ urlPanelId?: string;
$scope: any;
$injector: any;
- initDashboard: typeof initDashboard;
- setDashboardModel: typeof setDashboardModel;
+ urlEdit: boolean;
+ urlFullscreen: boolean;
loadingState: DashboardLoadingState;
dashboard: DashboardModel;
+ initDashboard: typeof initDashboard;
+ setDashboardModel: typeof setDashboardModel;
+ notifyApp: typeof notifyApp;
+ updateLocation: typeof updateLocation;
}
interface State {
isSettingsOpening: boolean;
+ isEditing: boolean;
+ isFullscreen: boolean;
+ fullscreenPanel: PanelModel | null;
}
export class DashboardPage extends PureComponent {
state: State = {
isSettingsOpening: false,
- isSettingsOpen: false,
+ isEditing: false,
+ isFullscreen: false,
+ fullscreenPanel: null,
};
async componentDidMount() {
@@ -55,30 +69,66 @@ export class DashboardPage extends PureComponent {
}
componentDidUpdate(prevProps: Props) {
- const { dashboard, editview } = this.props;
+ const { dashboard, editview, urlEdit, urlFullscreen, urlPanelId } = this.props;
- // when dashboard has loaded subscribe to somme events
- if (prevProps.dashboard === null && dashboard) {
- dashboard.events.on('view-mode-changed', this.onViewModeChanged);
-
- // set initial fullscreen class state
- this.setPanelFullscreenClass();
+ if (!dashboard) {
+ return;
}
+ // handle animation states when opening dashboard settings
if (!prevProps.editview && editview) {
this.setState({ isSettingsOpening: true });
setTimeout(() => {
- this.setState({ isSettingsOpening: false});
+ this.setState({ isSettingsOpening: false });
}, 10);
}
+
+ // // when dashboard has loaded subscribe to somme events
+ // if (prevProps.dashboard === null) {
+ // // set initial fullscreen class state
+ // this.setPanelFullscreenClass();
+ // }
+
+ // Sync url state with model
+ if (urlFullscreen !== dashboard.meta.isFullscreen || urlEdit !== dashboard.meta.isEditing) {
+ // entering fullscreen/edit mode
+ if (urlPanelId) {
+ const panel = dashboard.getPanelById(parseInt(urlPanelId, 10));
+
+ if (panel) {
+ dashboard.setViewMode(panel, urlFullscreen, urlEdit);
+ this.setState({ isEditing: urlEdit, isFullscreen: urlFullscreen, fullscreenPanel: panel });
+ } else {
+ this.handleFullscreenPanelNotFound(urlPanelId);
+ }
+ } else {
+ // handle leaving fullscreen mode
+ if (this.state.fullscreenPanel) {
+ dashboard.setViewMode(this.state.fullscreenPanel, urlFullscreen, urlEdit);
+ }
+ this.setState({ isEditing: urlEdit, isFullscreen: urlFullscreen, fullscreenPanel: null });
+ }
+
+ this.setPanelFullscreenClass(urlFullscreen);
+ }
}
- onViewModeChanged = () => {
- this.setPanelFullscreenClass();
- };
+ handleFullscreenPanelNotFound(urlPanelId: string) {
+ // Panel not found
+ this.props.notifyApp(createErrorNotification(`Panel with id ${urlPanelId} not found`));
+ // Clear url state
+ this.props.updateLocation({
+ query: {
+ edit: null,
+ fullscreen: null,
+ panelId: null,
+ },
+ partial: true
+ });
+ }
- setPanelFullscreenClass() {
- $('body').toggleClass('panel-in-fullscreen', this.props.dashboard.meta.fullscreen === true);
+ setPanelFullscreenClass(isFullscreen: boolean) {
+ $('body').toggleClass('panel-in-fullscreen', isFullscreen);
}
componentWillUnmount() {
@@ -94,10 +144,11 @@ export class DashboardPage extends PureComponent {
renderDashboard() {
const { dashboard, editview } = this.props;
+ const { isEditing, isFullscreen } = this.state;
const classes = classNames({
'dashboard-container': true,
- 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled
+ 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
});
return (
@@ -105,7 +156,7 @@ export class DashboardPage extends PureComponent {
{dashboard && editview && }
-
+
);
@@ -113,7 +164,7 @@ export class DashboardPage extends PureComponent {
render() {
const { dashboard, editview } = this.props;
- const { isSettingsOpening } = this.state;
+ const { isSettingsOpening, isEditing, isFullscreen } = this.state;
const classes = classNames({
'dashboard-page--settings-opening': isSettingsOpening,
@@ -122,7 +173,7 @@ export class DashboardPage extends PureComponent {
return (
-
+
{!dashboard && this.renderLoadingState()}
{dashboard && this.renderDashboard()}
@@ -130,19 +181,26 @@ export class DashboardPage extends PureComponent {
}
}
-const mapStateToProps = (state: StoreState) => ({
- urlUid: state.location.routeParams.uid,
- urlSlug: state.location.routeParams.slug,
- urlType: state.location.routeParams.type,
- panelId: state.location.query.panelId,
- editview: state.location.query.editview,
- loadingState: state.dashboard.loadingState,
- dashboard: state.dashboard.model as DashboardModel,
-});
+const mapStateToProps = (state: StoreState) => {
+ console.log('state location', state.location.query);
+ return {
+ urlUid: state.location.routeParams.uid,
+ urlSlug: state.location.routeParams.slug,
+ urlType: state.location.routeParams.type,
+ editview: state.location.query.editview,
+ urlPanelId: state.location.query.panelId,
+ urlFullscreen: state.location.query.fullscreen === true,
+ urlEdit: state.location.query.edit === true,
+ loadingState: state.dashboard.loadingState,
+ dashboard: state.dashboard.model as DashboardModel,
+ };
+};
const mapDispatchToProps = {
initDashboard,
- setDashboardModel
+ setDashboardModel,
+ notifyApp,
+ updateLocation,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage));
diff --git a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
index 658bfad3816..27f699ff3e6 100644
--- a/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
+++ b/public/app/features/dashboard/dashgrid/DashboardGrid.tsx
@@ -1,11 +1,14 @@
-import React from 'react';
+// Libaries
+import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
+import classNames from 'classnames';
+import sizeMe from 'react-sizeme';
+
+// Types
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { DashboardPanel } from './DashboardPanel';
import { DashboardModel, PanelModel } from '../state';
-import classNames from 'classnames';
-import sizeMe from 'react-sizeme';
let lastGridWidth = 1200;
let ignoreNextWidthChange = false;
@@ -76,19 +79,18 @@ function GridWrapper({
const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
-export interface DashboardGridProps {
+export interface Props {
dashboard: DashboardModel;
+ isEditing: boolean;
+ isFullscreen: boolean;
}
-export class DashboardGrid extends React.Component {
+export class DashboardGrid extends PureComponent {
gridToPanelMap: any;
panelMap: { [id: string]: PanelModel };
- constructor(props: DashboardGridProps) {
- super(props);
-
- // subscribe to dashboard events
- const dashboard = this.props.dashboard;
+ componentDidMount() {
+ const { dashboard } = this.props;
dashboard.on('panel-added', this.triggerForceUpdate);
dashboard.on('panel-removed', this.triggerForceUpdate);
dashboard.on('repeats-processed', this.triggerForceUpdate);
@@ -97,6 +99,16 @@ export class DashboardGrid extends React.Component {
dashboard.on('row-expanded', this.triggerForceUpdate);
}
+ componentWillUnmount() {
+ const { dashboard } = this.props;
+ dashboard.off('panel-added', this.triggerForceUpdate);
+ dashboard.off('panel-removed', this.triggerForceUpdate);
+ dashboard.off('repeats-processed', this.triggerForceUpdate);
+ dashboard.off('view-mode-changed', this.onViewModeChanged);
+ dashboard.off('row-collapsed', this.triggerForceUpdate);
+ dashboard.off('row-expanded', this.triggerForceUpdate);
+ }
+
buildLayout() {
const layout = [];
this.panelMap = {};
@@ -151,7 +163,6 @@ export class DashboardGrid extends React.Component {
onViewModeChanged = () => {
ignoreNextWidthChange = true;
- this.forceUpdate();
}
updateGridPos = (item: ReactGridLayout.Layout, layout: ReactGridLayout.Layout[]) => {
@@ -197,18 +208,20 @@ export class DashboardGrid extends React.Component {
}
render() {
+ const { dashboard, isFullscreen } = this.props;
+
return (
{this.renderPanels()}
diff --git a/public/app/features/dashboard/services/DashboardViewStateSrv.ts b/public/app/features/dashboard/services/DashboardViewStateSrv.ts
index fc38c3b241f..f5a68d6f647 100644
--- a/public/app/features/dashboard/services/DashboardViewStateSrv.ts
+++ b/public/app/features/dashboard/services/DashboardViewStateSrv.ts
@@ -98,8 +98,6 @@ export class DashboardViewStateSrv {
if (fromRouteUpdated !== true) {
this.$location.search(this.serializeToUrl());
}
-
- this.syncState();
}
toggleCollapsedPanelRow(panelId) {
@@ -115,34 +113,6 @@ export class DashboardViewStateSrv {
}
}
- syncState() {
- if (this.state.fullscreen) {
- const panel = this.dashboard.getPanelById(this.state.panelId);
-
- if (!panel) {
- this.state.fullscreen = null;
- this.state.panelId = null;
- this.state.edit = null;
-
- this.update(this.state);
-
- setTimeout(() => {
- appEvents.emit('alert-error', ['Error', 'Panel not found']);
- }, 100);
-
- return;
- }
-
- if (!panel.fullscreen) {
- this.enterFullscreen(panel);
- } else if (this.dashboard.meta.isEditing !== this.state.edit) {
- this.dashboard.setViewMode(panel, this.state.fullscreen, this.state.edit);
- }
- } else if (this.fullscreenPanel) {
- this.leaveFullscreen();
- }
- }
-
leaveFullscreen() {
const panel = this.fullscreenPanel;
diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts
index 70bdf49e5e4..817e6452f44 100644
--- a/public/app/routes/GrafanaCtrl.ts
+++ b/public/app/routes/GrafanaCtrl.ts
@@ -165,6 +165,8 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
for (const drop of Drop.drops) {
drop.destroy();
}
+
+ appEvents.emit('hide-dash-search');
});
// handle kiosk mode
diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts
index bdea5b04bac..713cd28efb1 100644
--- a/public/app/types/dashboard.ts
+++ b/public/app/types/dashboard.ts
@@ -1,6 +1,10 @@
import { DashboardAcl } from './acl';
export interface MutableDashboard {
+ meta: {
+ fullscreen: boolean;
+ isEditing: boolean;
+ }
}
export enum DashboardLoadingState {
diff --git a/public/views/index-template.html b/public/views/index-template.html
index a1c955d45d6..770ab74eccc 100644
--- a/public/views/index-template.html
+++ b/public/views/index-template.html
@@ -189,7 +189,7 @@
-
+
);
}
}
-const mapStateToProps = () => ({
-});
+const mapStateToProps = () => ({});
const mapDispatchToProps = {
- updateLocation
+ updateLocation,
};
export default connect(mapStateToProps, mapDispatchToProps)(DashNav);
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index 281916acb14..9f0f1cdff5f 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -60,8 +60,8 @@ export class DashboardPage extends PureComponent {
async componentDidMount() {
this.props.initDashboard({
- injector: this.props.$injector,
- scope: this.props.$scope,
+ $injector: this.props.$injector,
+ $scope: this.props.$scope,
urlSlug: this.props.urlSlug,
urlUid: this.props.urlUid,
urlType: this.props.urlType,
@@ -123,7 +123,7 @@ export class DashboardPage extends PureComponent {
fullscreen: null,
panelId: null,
},
- partial: true
+ partial: true,
});
}
@@ -163,7 +163,7 @@ export class DashboardPage extends PureComponent {
}
render() {
- const { dashboard, editview } = this.props;
+ const { dashboard, editview, $injector } = this.props;
const { isSettingsOpening, isEditing, isFullscreen } = this.state;
const classes = classNames({
@@ -173,7 +173,13 @@ export class DashboardPage extends PureComponent {
return (
-
+
{!dashboard && this.renderLoadingState()}
{dashboard && this.renderDashboard()}
diff --git a/public/app/features/dashboard/state/actions.ts b/public/app/features/dashboard/state/actions.ts
index 14721cdbe96..bc57b8e5f10 100644
--- a/public/app/features/dashboard/state/actions.ts
+++ b/public/app/features/dashboard/state/actions.ts
@@ -1,5 +1,4 @@
// Libaries
-import { StoreState } from 'app/types';
import { ThunkAction } from 'redux-thunk';
// Services & Utils
@@ -13,6 +12,7 @@ import { loadPluginDashboards } from '../../plugins/state/actions';
import { notifyApp } from 'app/core/actions';
// Types
+import { StoreState } from 'app/types';
import {
DashboardAcl,
DashboardAclDTO,
@@ -27,7 +27,6 @@ export const setDashboardLoadingState = actionCreatorFactory('SET_DASHBOARD_MODEL').create();
export type Action = ActionOf;
-
export type ThunkResult = ThunkAction;
export function getDashboardPermissions(id: number): ThunkResult {
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 10d7164fbff..f7e23238b7c 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -1,5 +1,6 @@
// Services & Utils
import { createErrorNotification } from 'app/core/copy/appNotification';
+import { getBackendSrv } from 'app/core/services/backend_srv';
// Actions
import { updateLocation } from 'app/core/actions';
@@ -12,24 +13,53 @@ import { DashboardLoadingState } from 'app/types/dashboard';
import { DashboardModel } from './DashboardModel';
export interface InitDashboardArgs {
- injector: any;
- scope: any;
+ $injector: any;
+ $scope: any;
urlUid?: string;
urlSlug?: string;
urlType?: string;
}
-export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: InitDashboardArgs): ThunkResult {
- return async dispatch => {
- const loaderSrv = injector.get('dashboardLoaderSrv');
+async function redirectToNewUrl(slug: string, dispatch: any) {
+ const res = await getBackendSrv().getDashboardBySlug(slug);
- dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching));
+ if (res) {
+ const url = locationUtil.stripBaseFromUrl(res.meta.url.replace('/d/', '/d-solo/'));
+ dispatch(updateLocation(url));
+ }
+}
+
+export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: InitDashboardArgs): ThunkResult {
+ return async dispatch => {
+ // handle old urls with no uid
+ if (!urlUid && urlSlug) {
+ redirectToNewUrl(urlSlug, dispatch);
+ return;
+ }
let dashDTO = null;
+ // set fetching state
+ dispatch(setDashboardLoadingState(DashboardLoadingState.Fetching));
+
try {
- // fetch dashboard from api
- dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
+ // if no uid or slug, load home dashboard
+ if (!urlUid && !urlSlug) {
+ dashDTO = await getBackendSrv().get('/api/dashboards/home');
+
+ if (dashDTO.redirectUri) {
+ const newUrl = locationUtil.stripBaseFromUrl(dashDTO.redirectUri);
+ dispatch(updateLocation({ path: newUrl }));
+ return;
+ } else {
+ dashDTO.meta.canSave = false;
+ dashDTO.meta.canShare = false;
+ dashDTO.meta.canStar = false;
+ }
+ } else {
+ const loaderSrv = $injector.get('dashboardLoaderSrv');
+ dashDTO = await loaderSrv.loadDashboard(urlType, urlSlug, urlUid);
+ }
} catch (err) {
dispatch(setDashboardLoadingState(DashboardLoadingState.Error));
console.log(err);
@@ -50,13 +80,13 @@ export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: Ini
}
// init services
- injector.get('timeSrv').init(dashboard);
- injector.get('annotationsSrv').init(dashboard);
+ $injector.get('timeSrv').init(dashboard);
+ $injector.get('annotationsSrv').init(dashboard);
// template values service needs to initialize completely before
// the rest of the dashboard can load
try {
- await injector.get('variableSrv').init(dashboard);
+ await $injector.get('variableSrv').init(dashboard);
} catch (err) {
dispatch(notifyApp(createErrorNotification('Templating init failed', err.toString())));
console.log(err);
@@ -68,11 +98,11 @@ export function initDashboard({ injector, scope, urlUid, urlSlug, urlType }: Ini
dashboard.autoFitPanels(window.innerHeight);
// init unsaved changes tracking
- injector.get('unsavedChangesSrv').init(dashboard, scope);
+ $injector.get('unsavedChangesSrv').init(dashboard, $scope);
- scope.dashboard = dashboard;
- injector.get('dashboardViewStateSrv').create(scope);
- injector.get('keybindingSrv').setupDashboardBindings(scope, dashboard);
+ $scope.dashboard = dashboard;
+ $injector.get('dashboardViewStateSrv').create($scope);
+ $injector.get('keybindingSrv').setupDashboardBindings($scope, dashboard);
} catch (err) {
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err.toString())));
console.log(err);
diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts
index cdd9ed89a08..abe347d689a 100644
--- a/public/app/routes/routes.ts
+++ b/public/app/routes/routes.ts
@@ -29,10 +29,12 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
- templateUrl: 'public/app/partials/dashboard.html',
- controller: 'LoadDashboardCtrl',
- reloadOnSearch: false,
+ template: ' ',
pageClass: 'page-dashboard',
+ reloadOnSearch: false,
+ resolve: {
+ component: () => DashboardPage,
+ },
})
.when('/d/:uid/:slug', {
template: ' ',
@@ -43,16 +45,20 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
},
})
.when('/d/:uid', {
- templateUrl: 'public/app/partials/dashboard.html',
- controller: 'LoadDashboardCtrl',
- reloadOnSearch: false,
+ template: ' ',
pageClass: 'page-dashboard',
+ reloadOnSearch: false,
+ resolve: {
+ component: () => DashboardPage,
+ },
})
.when('/dashboard/:type/:slug', {
- templateUrl: 'public/app/partials/dashboard.html',
- controller: 'LoadDashboardCtrl',
- reloadOnSearch: false,
+ template: ' ',
pageClass: 'page-dashboard',
+ reloadOnSearch: false,
+ resolve: {
+ component: () => DashboardPage,
+ },
})
.when('/d-solo/:uid/:slug', {
template: ' ',
diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts
index 713cd28efb1..9b1e750e859 100644
--- a/public/app/types/dashboard.ts
+++ b/public/app/types/dashboard.ts
@@ -4,7 +4,7 @@ export interface MutableDashboard {
meta: {
fullscreen: boolean;
isEditing: boolean;
- }
+ };
}
export enum DashboardLoadingState {
From 09efa24f281c69998865efc627fa278a50187e72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 15:29:14 +0100
Subject: [PATCH 038/770] Added more buttons in dashboard nav
---
.../dashboard/components/DashNav/DashNav.tsx | 124 +++++++++++-------
.../dashboard/containers/DashboardPage.tsx | 17 ++-
.../features/dashboard/state/initDashboard.ts | 2 +-
3 files changed, 95 insertions(+), 48 deletions(-)
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index 79d4da94aca..e82fa0ba75e 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -12,7 +12,7 @@ import { updateLocation } from 'app/core/actions';
import { DashboardModel } from '../../state/DashboardModel';
export interface Props {
- dashboard: DashboardModel | null;
+ dashboard: DashboardModel;
editview: string;
isEditing: boolean;
isFullscreen: boolean;
@@ -25,7 +25,20 @@ export class DashNav extends PureComponent {
appEvents.emit('show-dash-search');
};
- onAddPanel = () => {};
+ onAddPanel = () => {
+ const { dashboard } = this.props;
+
+ // Return if the "Add panel" exists already
+ if (dashboard.panels.length > 0 && dashboard.panels[0].type === 'add-panel') {
+ return;
+ }
+
+ dashboard.addPanel({
+ type: 'add-panel',
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
+ title: 'Panel Title',
+ });
+ };
onClose = () => {
this.props.updateLocation({
@@ -34,6 +47,16 @@ export class DashNav extends PureComponent {
});
};
+ onToggleTVMode = () => {
+ appEvents.emit('toggle-kiosk-mode');
+ };
+
+ onSave = () => {
+ const { $injector } = this.props;
+ const dashboardSrv = $injector.get('dashboardSrv');
+ dashboardSrv.saveDashboard();
+ };
+
onOpenSettings = () => {
this.props.updateLocation({
query: { editview: 'settings' },
@@ -51,30 +74,25 @@ export class DashNav extends PureComponent {
});
};
- renderLoadingState() {
- return (
-
- );
- }
+ onOpenShare = () => {
+ const $rootScope = this.props.$injector.get('$rootScope');
+ const modalScope = $rootScope.$new();
+ modalScope.tabIndex = 0;
+ modalScope.dashboard = this.props.dashboard;
+ appEvents.emit('show-modal', {
+ src: 'public/app/features/dashboard/components/ShareModal/template.html',
+ scope: modalScope,
+ });
+ };
render() {
const { dashboard, isFullscreen, editview } = this.props;
-
- if (!dashboard) {
- return this.renderLoadingState();
- }
+ const { canEdit, canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta;
+ const { snapshot } = dashboard;
const haveFolder = dashboard.meta.folderId > 0;
- const { canEdit, canStar, canSave, folderTitle, showSettings, isStarred } = dashboard.meta;
+ const snapshotUrl = snapshot && snapshot.originalUrl;
return (
@@ -124,34 +142,50 @@ export class DashNav extends PureComponent
{
)}
+ {canShare && (
+
+
+
+ )}
+
+ {canSave && (
+
+
+
+ )}
+
+ {snapshotUrl && (
+
+
+
+ )}
+
+
+
+
+
+
+
{
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
- //
//
- //
}
+
{(isFullscreen || editview) && (
{
const { dashboard, editview, $injector } = this.props;
const { isSettingsOpening, isEditing, isFullscreen } = this.state;
+ if (!dashboard) {
+ return null;
+ }
+
const classes = classNames({
'dashboard-page--settings-opening': isSettingsOpening,
'dashboard-page--settings-open': !isSettingsOpening && editview,
});
+ const gridWrapperClasses = classNames({
+ 'dashboard-container': true,
+ 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
+ });
return (
{
editview={editview}
$injector={$injector}
/>
- {!dashboard && this.renderLoadingState()}
- {dashboard && this.renderDashboard()}
+
+ {dashboard && editview &&
}
+
+
+
+
+
);
}
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index f7e23238b7c..19727cd8ab0 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -88,7 +88,7 @@ export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: I
try {
await $injector.get('variableSrv').init(dashboard);
} catch (err) {
- dispatch(notifyApp(createErrorNotification('Templating init failed', err.toString())));
+ dispatch(notifyApp(createErrorNotification('Templating init failed')));
console.log(err);
}
From 4a8effddf5c421a7588ebe6ffbfe6afac9938d15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 17:15:23 +0100
Subject: [PATCH 039/770] fixed panel removal
---
public/app/core/services/keybindingSrv.ts | 6 +--
.../dashboard/components/DashNav/DashNav.tsx | 40 +++++++++----------
.../dashboard/state/DashboardModel.ts | 11 +++--
.../features/dashboard/state/initDashboard.ts | 11 ++++-
public/app/features/panel/panel_ctrl.ts | 5 +--
5 files changed, 40 insertions(+), 33 deletions(-)
diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts
index ed321c6a69e..aa39763841e 100644
--- a/public/app/core/services/keybindingSrv.ts
+++ b/public/app/core/services/keybindingSrv.ts
@@ -144,7 +144,7 @@ export class KeybindingSrv {
this.$location.search(search);
}
- setupDashboardBindings(scope, dashboard) {
+ setupDashboardBindings(scope, dashboard, onRemovePanel) {
this.bind('mod+o', () => {
dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3;
appEvents.emit('graph-hover-clear');
@@ -212,9 +212,7 @@ export class KeybindingSrv {
// delete panel
this.bind('p r', () => {
if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
- this.$rootScope.appEvent('panel-remove', {
- panelId: dashboard.meta.focusPanelId,
- });
+ onRemovePanel(dashboard.meta.focusPanelId);
dashboard.meta.focusPanelId = 0;
}
});
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index e82fa0ba75e..4513d15ebb7 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -171,33 +171,29 @@ export class DashNav extends PureComponent {
)}
+
-
+
+
+
+
+
+
+ {
+ //
+ }
+
+ {(isFullscreen || editview) && (
+
-
+
-
- {
- //
- }
-
- {(isFullscreen || editview) && (
-
-
-
-
-
- )}
-
+ )}
);
}
diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts
index 929b984a93f..8d96a2eec73 100644
--- a/public/app/features/dashboard/state/DashboardModel.ts
+++ b/public/app/features/dashboard/state/DashboardModel.ts
@@ -1,20 +1,25 @@
+// Libaries
import moment from 'moment';
import _ from 'lodash';
-import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui';
+// Constants
+import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui';
import { GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL, GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
+
+// Utils & Services
import { Emitter } from 'app/core/utils/emitter';
import { contextSrv } from 'app/core/services/context_srv';
import sortByKeys from 'app/core/utils/sort_by_keys';
+// Types
import { PanelModel } from './PanelModel';
import { DashboardMigrator } from './DashboardMigrator';
import { TimeRange } from '@grafana/ui/src';
export class DashboardModel {
id: any;
- uid: any;
- title: any;
+ uid: string;
+ title: string;
autoUpdate: any;
description: any;
tags: any;
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 19727cd8ab0..01092617e2f 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -7,6 +7,7 @@ import { updateLocation } from 'app/core/actions';
import { notifyApp } from 'app/core/actions';
import locationUtil from 'app/core/utils/location_util';
import { setDashboardLoadingState, ThunkResult, setDashboardModel } from './actions';
+import { removePanel } from '../utils/panel';
// Types
import { DashboardLoadingState } from 'app/types/dashboard';
@@ -102,7 +103,15 @@ export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: I
$scope.dashboard = dashboard;
$injector.get('dashboardViewStateSrv').create($scope);
- $injector.get('keybindingSrv').setupDashboardBindings($scope, dashboard);
+
+ // dashboard keybindings should not live in core, this needs a bigger refactoring
+ // So declaring this here so it can depend on the removePanel util function
+ // Long term onRemovePanel should be handled via react prop callback
+ const onRemovePanel = (panelId: number) => {
+ removePanel(dashboard, dashboard.getPanelById(panelId), true);
+ };
+
+ $injector.get('keybindingSrv').setupDashboardBindings($scope, dashboard, onRemovePanel);
} catch (err) {
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err.toString())));
console.log(err);
diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts
index 6799b209147..41810ab21c0 100644
--- a/public/app/features/panel/panel_ctrl.ts
+++ b/public/app/features/panel/panel_ctrl.ts
@@ -7,6 +7,7 @@ import { Emitter } from 'app/core/core';
import getFactors from 'app/core/utils/factors';
import {
duplicatePanel,
+ removePanel,
copyPanel as copyPanelUtil,
editPanelJson as editPanelJsonUtil,
sharePanel as sharePanelUtil,
@@ -213,9 +214,7 @@ export class PanelCtrl {
}
removePanel() {
- this.publishAppEvent('panel-remove', {
- panelId: this.panel.id,
- });
+ removePanel(this.dashboard, this.panel, true);
}
editPanelJson() {
From d7151e5c887082746dbd42cfcdcb4550a50cee30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 18:25:13 +0100
Subject: [PATCH 040/770] improving dash nav react comp
---
.../dashboard/components/DashNav/DashNav.tsx | 47 ++++++++++++-------
.../components/DashNav/DashNavButton.tsx | 22 +++++++++
.../features/dashboard/state/initDashboard.ts | 2 +-
3 files changed, 53 insertions(+), 18 deletions(-)
create mode 100644 public/app/features/dashboard/components/DashNav/DashNavButton.tsx
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index 4513d15ebb7..d6ee9ae1f68 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -5,6 +5,9 @@ import { connect } from 'react-redux';
// Utils & Services
import { appEvents } from 'app/core/app_events';
+// Components
+import { DashNavButton } from './DashNavButton';
+
// State
import { updateLocation } from 'app/core/actions';
@@ -41,10 +44,17 @@ export class DashNav extends PureComponent {
};
onClose = () => {
- this.props.updateLocation({
- query: { editview: null, panelId: null, edit: null, fullscreen: null },
- partial: true,
- });
+ if (this.props.editview) {
+ this.props.updateLocation({
+ query: { editview: null },
+ partial: true,
+ });
+ } else {
+ this.props.updateLocation({
+ query: { panelId: null, edit: null, fullscreen: null },
+ partial: true,
+ });
+ }
};
onToggleTVMode = () => {
@@ -116,19 +126,12 @@ export class DashNav extends PureComponent {
{canEdit && (
-
-
-
- )}
-
- {showSettings && (
-
-
-
+
)}
{canStar && (
@@ -171,6 +174,16 @@ export class DashNav extends PureComponent
{
)}
+
+ {showSettings && (
+
+
+
+ )}
diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx
new file mode 100644
index 00000000000..1a98bf961dc
--- /dev/null
+++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx
@@ -0,0 +1,22 @@
+// Libraries
+import React, { FunctionComponent } from 'react';
+
+// Components
+import { Tooltip } from '@grafana/ui';
+
+interface Props {
+ icon: string;
+ tooltip: string;
+ classSuffix: string;
+ onClick: () => void;
+}
+
+export const DashNavButton: FunctionComponent
= ({ icon, tooltip, classSuffix, onClick }) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 01092617e2f..612ce46b422 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -33,7 +33,7 @@ async function redirectToNewUrl(slug: string, dispatch: any) {
export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: InitDashboardArgs): ThunkResult {
return async dispatch => {
// handle old urls with no uid
- if (!urlUid && urlSlug) {
+ if (!urlUid && urlSlug && !urlType) {
redirectToNewUrl(urlSlug, dispatch);
return;
}
From 1f3fafb198fc47e143e81afaa5bf497e1059f401 Mon Sep 17 00:00:00 2001
From: Paresh
Date: Sun, 3 Feb 2019 13:07:33 -0600
Subject: [PATCH 041/770] mssql: pass timerange for template variable queries
---
.../app/plugins/datasource/mssql/datasource.ts | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/public/app/plugins/datasource/mssql/datasource.ts b/public/app/plugins/datasource/mssql/datasource.ts
index 23aa5504d3e..1ede9cc3d1e 100644
--- a/public/app/plugins/datasource/mssql/datasource.ts
+++ b/public/app/plugins/datasource/mssql/datasource.ts
@@ -107,13 +107,24 @@ export class MssqlDatasource {
format: 'table',
};
+ const data = {
+ queries: [interpolatedQuery],
+ };
+
+ if (optionalOptions && optionalOptions.range) {
+ if (optionalOptions.range.from) {
+ data['from'] = optionalOptions.range.from.valueOf().toString();
+ }
+ if (optionalOptions.range.to) {
+ data['to'] = optionalOptions.range.to.valueOf().toString();
+ }
+ }
+
return this.backendSrv
.datasourceRequest({
url: '/api/tsdb/query',
method: 'POST',
- data: {
- queries: [interpolatedQuery],
- },
+ data: data,
})
.then(data => this.responseParser.parseMetricFindQueryResult(refId, data));
}
From 0324de37d2caa63fac56746ad0dbcea53bea83a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 20:38:13 +0100
Subject: [PATCH 042/770] refactorings and cleanup
---
.../dashboard/components/DashNav/DashNav.tsx | 71 ++++++++-----------
.../components/DashNav/DashNavButton.tsx | 21 ++++--
.../features/dashboard/state/initDashboard.ts | 28 ++++++--
3 files changed, 67 insertions(+), 53 deletions(-)
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index d6ee9ae1f68..559e3e1d66f 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -135,61 +135,49 @@ export class DashNav extends PureComponent {
)}
{canStar && (
-
- {isStarred && }
- {!isStarred && }
-
+ />
)}
{canShare && (
-
-
-
+ />
)}
{canSave && (
-
-
-
+
)}
{snapshotUrl && (
-
-
-
+ />
)}
{showSettings && (
-
-
-
+
)}
-
-
-
+
{
@@ -198,13 +186,12 @@ export class DashNav extends PureComponent {
{(isFullscreen || editview) && (
-
-
-
+ />
)}
diff --git a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx
index 1a98bf961dc..505baaf1f5d 100644
--- a/public/app/features/dashboard/components/DashNav/DashNavButton.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNavButton.tsx
@@ -8,15 +8,26 @@ interface Props {
icon: string;
tooltip: string;
classSuffix: string;
- onClick: () => void;
+ onClick?: () => void;
+ href?: string;
}
-export const DashNavButton: FunctionComponent = ({ icon, tooltip, classSuffix, onClick }) => {
+export const DashNavButton: FunctionComponent = ({ icon, tooltip, classSuffix, onClick, href }) => {
+ if (onClick) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
-
-
+
+
-
+
);
};
diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts
index 612ce46b422..a39a7fce285 100644
--- a/public/app/features/dashboard/state/initDashboard.ts
+++ b/public/app/features/dashboard/state/initDashboard.ts
@@ -1,6 +1,11 @@
// Services & Utils
import { createErrorNotification } from 'app/core/copy/appNotification';
import { getBackendSrv } from 'app/core/services/backend_srv';
+import { DashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
+import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
+import { AnnotationsSrv } from 'app/features/annotations/annotations_srv';
+import { VariableSrv } from 'app/features/templating/variable_srv';
+import { KeybindingSrv } from 'app/core/services/keybindingSrv';
// Actions
import { updateLocation } from 'app/core/actions';
@@ -81,13 +86,21 @@ export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: I
}
// init services
- $injector.get('timeSrv').init(dashboard);
- $injector.get('annotationsSrv').init(dashboard);
+ const timeSrv: TimeSrv = $injector.get('timeSrv');
+ const annotationsSrv: AnnotationsSrv = $injector.get('annotationsSrv');
+ const variableSrv: VariableSrv = $injector.get('variableSrv');
+ const keybindingSrv: KeybindingSrv = $injector.get('keybindingSrv');
+ const unsavedChangesSrv = $injector.get('unsavedChangesSrv');
+ const viewStateSrv = $injector.get('dashboardViewStateSrv');
+ const dashboardSrv: DashboardSrv = $injector.get('dashboardSrv');
+
+ timeSrv.init(dashboard);
+ annotationsSrv.init(dashboard);
// template values service needs to initialize completely before
// the rest of the dashboard can load
try {
- await $injector.get('variableSrv').init(dashboard);
+ await variableSrv.init(dashboard);
} catch (err) {
dispatch(notifyApp(createErrorNotification('Templating init failed')));
console.log(err);
@@ -99,10 +112,10 @@ export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: I
dashboard.autoFitPanels(window.innerHeight);
// init unsaved changes tracking
- $injector.get('unsavedChangesSrv').init(dashboard, $scope);
+ unsavedChangesSrv.init(dashboard, $scope);
$scope.dashboard = dashboard;
- $injector.get('dashboardViewStateSrv').create($scope);
+ viewStateSrv.create($scope);
// dashboard keybindings should not live in core, this needs a bigger refactoring
// So declaring this here so it can depend on the removePanel util function
@@ -111,12 +124,15 @@ export function initDashboard({ $injector, $scope, urlUid, urlSlug, urlType }: I
removePanel(dashboard, dashboard.getPanelById(panelId), true);
};
- $injector.get('keybindingSrv').setupDashboardBindings($scope, dashboard, onRemovePanel);
+ keybindingSrv.setupDashboardBindings($scope, dashboard, onRemovePanel);
} catch (err) {
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err.toString())));
console.log(err);
}
+ // legacy srv state
+ dashboardSrv.setCurrent(dashboard);
+ // set model in redux (even though it's mutable)
dispatch(setDashboardModel(dashboard));
};
}
From 883f7a164b455d3f5f9b7ff23252d37e6a1a9da3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Sun, 3 Feb 2019 21:06:07 +0100
Subject: [PATCH 043/770] added time picker
---
.../dashboard/components/DashNav/DashNav.tsx | 35 +++++++++++++++----
.../dashboard/containers/DashboardPage.tsx | 25 ++++++-------
public/sass/components/_navbar.scss | 2 +-
3 files changed, 40 insertions(+), 22 deletions(-)
diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx
index 559e3e1d66f..66edb149433 100644
--- a/public/app/features/dashboard/components/DashNav/DashNav.tsx
+++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx
@@ -3,6 +3,7 @@ import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
// Utils & Services
+import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
import { appEvents } from 'app/core/app_events';
// Components
@@ -24,6 +25,24 @@ export interface Props {
}
export class DashNav extends PureComponent {
+ timePickerEl: HTMLElement;
+ timepickerCmp: AngularComponent;
+
+ componentDidMount() {
+ const loader = getAngularLoader();
+
+ const template = ' ';
+ const scopeProps = { dashboard: this.props.dashboard };
+
+ this.timepickerCmp = loader.load(this.timePickerEl, scopeProps, template);
+ }
+
+ componentWillUnmount() {
+ if (this.timepickerCmp) {
+ this.timepickerCmp.destroy();
+ }
+ }
+
onOpenSearch = () => {
appEvents.emit('show-dash-search');
};
@@ -98,7 +117,7 @@ export class DashNav extends PureComponent {
render() {
const { dashboard, isFullscreen, editview } = this.props;
- const { canEdit, canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta;
+ const { canStar, canSave, canShare, folderTitle, showSettings, isStarred } = dashboard.meta;
const { snapshot } = dashboard;
const haveFolder = dashboard.meta.folderId > 0;
@@ -125,7 +144,7 @@ export class DashNav extends PureComponent {
*/}
- {canEdit && (
+ {canSave && (
{
)}
{showSettings && (
-
+
)}
@@ -180,9 +203,7 @@ export class DashNav extends PureComponent {
/>
- {
- //
- }
+ (this.timePickerEl = element)} />
{(isFullscreen || editview) && (
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index d86d5aea221..be12657a829 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -200,20 +200,17 @@ export class DashboardPage extends PureComponent
{
}
}
-const mapStateToProps = (state: StoreState) => {
- console.log('state location', state.location.query);
- return {
- urlUid: state.location.routeParams.uid,
- urlSlug: state.location.routeParams.slug,
- urlType: state.location.routeParams.type,
- editview: state.location.query.editview,
- urlPanelId: state.location.query.panelId,
- urlFullscreen: state.location.query.fullscreen === true,
- urlEdit: state.location.query.edit === true,
- loadingState: state.dashboard.loadingState,
- dashboard: state.dashboard.model as DashboardModel,
- };
-};
+const mapStateToProps = (state: StoreState) => ({
+ urlUid: state.location.routeParams.uid,
+ urlSlug: state.location.routeParams.slug,
+ urlType: state.location.routeParams.type,
+ editview: state.location.query.editview,
+ urlPanelId: state.location.query.panelId,
+ urlFullscreen: state.location.query.fullscreen === true,
+ urlEdit: state.location.query.edit === true,
+ loadingState: state.dashboard.loadingState,
+ dashboard: state.dashboard.model as DashboardModel,
+});
const mapDispatchToProps = {
initDashboard,
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss
index b3733b694fc..0744ed0dfc7 100644
--- a/public/sass/components/_navbar.scss
+++ b/public/sass/components/_navbar.scss
@@ -102,7 +102,7 @@
display: flex;
align-items: center;
justify-content: flex-end;
- margin-right: $spacer;
+ margin-left: 10px;
&--close {
display: none;
From 48c8ff8899b4307c2f15779fd180bb66034c2a75 Mon Sep 17 00:00:00 2001
From: Connor Patterson
Date: Sun, 3 Feb 2019 16:29:35 -0500
Subject: [PATCH 044/770] Add AWS/Neptune to metricsMap and dimensionsMap
---
pkg/tsdb/cloudwatch/metric_find_query.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go
index dfa03d2dfa9..f898a65f911 100644
--- a/pkg/tsdb/cloudwatch/metric_find_query.go
+++ b/pkg/tsdb/cloudwatch/metric_find_query.go
@@ -95,6 +95,7 @@ func init() {
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
"AWS/ML": {"PredictCount", "PredictFailureCount"},
"AWS/NATGateway": {"PacketsOutToDestination", "PacketsOutToSource", "PacketsInFromSource", "PacketsInFromDestination", "BytesOutToDestination", "BytesOutToSource", "BytesInFromSource", "BytesInFromDestination", "ErrorPortAllocation", "ActiveConnectionCount", "ConnectionAttemptCount", "ConnectionEstablishedCount", "IdleTimeoutCount", "PacketsDropCount"},
+ "AWS/Neptune": {"CPUUtilization", "ClusterReplicaLag", "ClusterReplicaLagMaximum", "ClusterReplicaLagMinimum", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "GremlinHttp1xx", "GremlinHttp2xx", "GremlinHttp4xx", "GremlinHttp5xx", "GremlinErrors", "GremlinRequests", "GremlinRequestsPerSec", "GremlinWebSocketSuccess", "GremlinWebSocketClientErrors", "GremlinWebSocketServerErrors", "GremlinWebSocketAvailableConnections", "Http1xx", "Http2xx", "Http4xx", "Http5xx", "Http100", "Http101", "Http200", "Http400", "Http403", "Http405", "Http413", "Http429", "Http500", "Http501", "LoaderErrors", "LoaderRequests", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "SparqlHttp1xx", "SparqlHttp2xx", "SparqlHttp4xx", "SparqlHttp5xx", "SparqlErrors", "SparqlRequests", "SparqlRequestsPerSec", "StatusErrors", "StatusRequests", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs"},
"AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"},
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
@@ -149,6 +150,7 @@ func init() {
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
"AWS/ML": {"MLModelId", "RequestMode"},
"AWS/NATGateway": {"NatGatewayId"},
+ "AWS/Neptune": {"DBClusterIdentifier", "Role", "DatabaseClass", "EngineName"},
"AWS/NetworkELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
"AWS/Redshift": {"NodeID", "ClusterIdentifier", "latency", "service class", "wmlid"},
From 43f8098981e0328d6d06e75cd746cf5f4b1e9912 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Mon, 28 Jan 2019 17:41:33 +0100
Subject: [PATCH 045/770] Removed the on every key change event
---
public/app/features/explore/QueryField.tsx | 68 ++++++++++++----------
1 file changed, 36 insertions(+), 32 deletions(-)
diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx
index 85315d2bdef..db6efb88f52 100644
--- a/public/app/features/explore/QueryField.tsx
+++ b/public/app/features/explore/QueryField.tsx
@@ -132,11 +132,11 @@ export class QueryField extends React.PureComponent {
+ onChange = ({ value }, invokeParentOnValueChanged?: boolean) => {
const documentChanged = value.document !== this.state.value.document;
const prevValue = this.state.value;
@@ -144,7 +144,7 @@ export class QueryField extends React.PureComponent {
if (documentChanged) {
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
- if (textChanged) {
+ if (textChanged && invokeParentOnValueChanged) {
this.handleChangeValue();
}
}
@@ -288,8 +288,37 @@ export class QueryField extends React.PureComponent {
+ handleEnterAndTabKey = change => {
const { typeaheadIndex, suggestions } = this.state;
+ if (this.menuEl) {
+ // Dont blur input
+ event.preventDefault();
+ if (!suggestions || suggestions.length === 0) {
+ return undefined;
+ }
+
+ const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex);
+ const nextChange = this.applyTypeahead(change, suggestion);
+
+ const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text');
+ if (insertTextOperation) {
+ const suggestionText = insertTextOperation.text;
+ this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
+ if (this.placeholdersBuffer.hasPlaceholders()) {
+ nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
+ }
+ }
+
+ return true;
+ } else {
+ this.handleChangeValue();
+
+ return undefined;
+ }
+ };
+
+ onKeyDown = (event, change) => {
+ const { typeaheadIndex } = this.state;
switch (event.key) {
case 'Escape': {
@@ -312,27 +341,7 @@ export class QueryField extends React.PureComponent operation.type === 'insert_text');
- if (insertTextOperation) {
- const suggestionText = insertTextOperation.text;
- this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
- if (this.placeholdersBuffer.hasPlaceholders()) {
- nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
- }
- }
-
- return true;
- }
+ return this.handleEnterAndTabKey(change);
break;
}
@@ -364,12 +373,7 @@ export class QueryField extends React.PureComponent {
if (this.mounted) {
- this.setState({
- suggestions: [],
- typeaheadIndex: 0,
- typeaheadPrefix: '',
- typeaheadContext: null,
- });
+ this.setState({ suggestions: [], typeaheadIndex: 0, typeaheadPrefix: '', typeaheadContext: null });
this.resetTimer = null;
}
};
@@ -396,7 +400,7 @@ export class QueryField extends React.PureComponent {
// Manually triggering change
const change = this.applyTypeahead(this.state.value.change(), item);
- this.onChange(change);
+ this.onChange(change, true);
};
updateMenu = () => {
From acea1d7f0015d0014364d84d190f5e5b0eafae71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Fri, 1 Feb 2019 11:55:01 +0100
Subject: [PATCH 046/770] Alignment of interfaces and components
---
packages/grafana-ui/src/types/plugin.ts | 18 ++++++++-
public/app/features/explore/QueryField.tsx | 34 ++++++++---------
public/app/features/explore/QueryRow.tsx | 14 ++++---
.../loki/components/LokiQueryField.tsx | 38 +++++++++----------
.../prometheus/components/PromQueryField.tsx | 35 +++++++++--------
public/app/store/configureStore.ts | 4 +-
6 files changed, 77 insertions(+), 66 deletions(-)
diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts
index 00735827825..1be862e17f3 100644
--- a/packages/grafana-ui/src/types/plugin.ts
+++ b/packages/grafana-ui/src/types/plugin.ts
@@ -41,6 +41,12 @@ export interface DataSourceApi {
pluginExports?: PluginExports;
}
+export interface ExploreDataSourceApi extends DataSourceApi {
+ modifyQuery?(query: TQuery, action: any): TQuery;
+ getHighlighterExpression?(query: TQuery): string;
+ languageProvider?: any;
+}
+
export interface QueryEditorProps {
datasource: DSType;
query: TQuery;
@@ -48,6 +54,16 @@ export interface QueryEditorProps void;
}
+export interface ExploreQueryFieldProps {
+ datasource: DSType;
+ initialQuery: TQuery;
+ error?: string | JSX.Element;
+ hint?: QueryHint;
+ history: any[];
+ onExecuteQuery?: () => void;
+ onQueryChange?: (value: TQuery) => void;
+}
+
export interface PluginExports {
Datasource?: DataSourceApi;
QueryCtrl?: any;
@@ -55,7 +71,7 @@ export interface PluginExports {
ConfigCtrl?: any;
AnnotationsQueryCtrl?: any;
VariableQueryEditor?: any;
- ExploreQueryField?: any;
+ ExploreQueryField?: ComponentClass>;
ExploreStartPage?: any;
// Panel plugin
diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx
index db6efb88f52..880bedd7905 100644
--- a/public/app/features/explore/QueryField.tsx
+++ b/public/app/features/explore/QueryField.tsx
@@ -33,10 +33,9 @@ export interface QueryFieldProps {
cleanText?: (text: string) => string;
disabled?: boolean;
initialQuery: string | null;
- onBlur?: () => void;
- onFocus?: () => void;
+ onExecuteQuery?: () => void;
+ onQueryChange?: (value: string) => void;
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
- onValueChanged?: (value: string) => void;
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
placeholder?: string;
portalOrigin?: string;
@@ -145,7 +144,7 @@ export class QueryField extends React.PureComponent {
+ executeOnQueryChangeAndExecuteQueries = () => {
// Send text change to parent
- const { onValueChanged } = this.props;
- if (onValueChanged) {
- onValueChanged(Plain.serialize(this.state.value));
+ const { onQueryChange, onExecuteQuery } = this.props;
+ if (onQueryChange) {
+ onQueryChange(Plain.serialize(this.state.value));
+ }
+
+ if (onExecuteQuery) {
+ onExecuteQuery();
}
};
@@ -311,7 +314,7 @@ export class QueryField extends React.PureComponent {
- const { onBlur } = this.props;
// If we dont wait here, menu clicks wont work because the menu
// will be gone.
this.resetTimer = setTimeout(this.resetTypeahead, 100);
// Disrupting placeholder entry wipes all remaining placeholders needing input
this.placeholdersBuffer.clearPlaceholders();
- if (onBlur) {
- onBlur();
- }
+
+ this.executeOnQueryChangeAndExecuteQueries();
};
- handleFocus = () => {
- const { onFocus } = this.props;
- if (onFocus) {
- onFocus();
- }
- };
+ handleFocus = () => {};
onClickMenu = (item: CompletionItem) => {
// Manually triggering change
diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx
index f6181161d56..7de728edb99 100644
--- a/public/app/features/explore/QueryRow.tsx
+++ b/public/app/features/explore/QueryRow.tsx
@@ -20,7 +20,7 @@ import {
// Types
import { StoreState } from 'app/types';
-import { RawTimeRange, DataQuery, QueryHint } from '@grafana/ui';
+import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint } from '@grafana/ui';
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
@@ -37,7 +37,7 @@ interface QueryRowProps {
changeQuery: typeof changeQuery;
className?: string;
exploreId: ExploreId;
- datasourceInstance: any;
+ datasourceInstance: ExploreDataSourceApi;
highlightLogsExpression: typeof highlightLogsExpression;
history: HistoryItem[];
index: number;
@@ -115,13 +115,15 @@ export class QueryRow extends PureComponent {
{QueryField ? (
) : (
void;
- onPressEnter?: () => void;
- onQueryChange?: (value: LokiQuery, override?: boolean) => void;
+interface LokiQueryFieldProps extends ExploreQueryFieldProps {
+ history: HistoryItem[];
}
interface LokiQueryFieldState {
@@ -98,14 +91,14 @@ export class LokiQueryField extends React.PureComponent node.type === 'code_block',
getSyntax: node => 'promql',
}),
];
- this.pluginsSearch = [RunnerPlugin({ handler: props.onPressEnter })];
+ this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })];
this.state = {
logLabelOptions: [],
@@ -169,21 +162,25 @@ export class LokiQueryField extends React.PureComponent {
// Send text change to parent
- const { initialQuery, onQueryChange } = this.props;
+ const { initialQuery, onQueryChange, onExecuteQuery } = this.props;
if (onQueryChange) {
const query = {
...initialQuery,
expr: value,
};
- onQueryChange(query, override);
+ onQueryChange(query);
+
+ if (override && onExecuteQuery) {
+ onExecuteQuery();
+ }
}
};
onClickHintFix = () => {
- const { hint, onClickHintFix } = this.props;
- if (onClickHintFix && hint && hint.fix) {
- onClickHintFix(hint.fix.action);
- }
+ // const { hint, onClickHintFix } = this.props;
+ // if (onClickHintFix && hint && hint.fix) {
+ // onClickHintFix(hint.fix.action);
+ // }
};
onUpdateLanguage = () => {
@@ -243,7 +240,8 @@ export class LokiQueryField extends React.PureComponent void;
- onPressEnter?: () => void;
- onQueryChange?: (value: PromQuery, override?: boolean) => void;
+interface PromQueryFieldProps extends ExploreQueryFieldProps {
+ history: HistoryItem[];
}
interface PromQueryFieldState {
@@ -116,7 +110,7 @@ class PromQueryField extends React.PureComponent node.type === 'code_block',
getSyntax: node => 'promql',
@@ -174,21 +168,25 @@ class PromQueryField extends React.PureComponent {
// Send text change to parent
- const { initialQuery, onQueryChange } = this.props;
+ const { initialQuery, onQueryChange, onExecuteQuery } = this.props;
if (onQueryChange) {
const query: PromQuery = {
...initialQuery,
expr: value,
};
- onQueryChange(query, override);
+ onQueryChange(query);
+
+ if (override && onExecuteQuery) {
+ onExecuteQuery();
+ }
}
};
onClickHintFix = () => {
- const { hint, onClickHintFix } = this.props;
- if (onClickHintFix && hint && hint.fix) {
- onClickHintFix(hint.fix.action);
- }
+ // const { hint, onClickHintFix } = this.props;
+ // if (onClickHintFix && hint && hint.fix) {
+ // onClickHintFix(hint.fix.action);
+ // }
};
onUpdateLanguage = () => {
@@ -264,7 +262,8 @@ class PromQueryField extends React.PureComponent
Date: Fri, 1 Feb 2019 12:54:16 +0100
Subject: [PATCH 047/770] More types and some refactoring
---
packages/grafana-ui/src/types/plugin.ts | 5 +++--
public/app/features/explore/QueryField.tsx | 2 --
public/app/features/explore/QueryRow.tsx | 14 ++++++--------
public/app/features/explore/state/actionTypes.ts | 6 +++---
public/app/features/explore/state/actions.ts | 10 ++++++++--
public/app/features/explore/state/reducers.ts | 2 +-
.../datasource/loki/components/LokiQueryField.tsx | 8 ++++----
.../prometheus/components/PromQueryField.tsx | 8 ++++----
8 files changed, 29 insertions(+), 26 deletions(-)
diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts
index 1be862e17f3..e951e91a223 100644
--- a/packages/grafana-ui/src/types/plugin.ts
+++ b/packages/grafana-ui/src/types/plugin.ts
@@ -1,6 +1,6 @@
import { ComponentClass } from 'react';
import { PanelProps, PanelOptionsProps } from './panel';
-import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource';
+import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
export interface DataSourceApi {
/**
@@ -42,7 +42,7 @@ export interface DataSourceApi {
}
export interface ExploreDataSourceApi extends DataSourceApi {
- modifyQuery?(query: TQuery, action: any): TQuery;
+ modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
getHighlighterExpression?(query: TQuery): string;
languageProvider?: any;
}
@@ -62,6 +62,7 @@ export interface ExploreQueryFieldProps void;
onQueryChange?: (value: TQuery) => void;
+ onExecuteHint?: (action: QueryFixAction) => void;
}
export interface PluginExports {
diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx
index 880bedd7905..a0e70e8066c 100644
--- a/public/app/features/explore/QueryField.tsx
+++ b/public/app/features/explore/QueryField.tsx
@@ -387,8 +387,6 @@ export class QueryField extends React.PureComponent {};
diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx
index 7de728edb99..bbc0bf0d101 100644
--- a/public/app/features/explore/QueryRow.tsx
+++ b/public/app/features/explore/QueryRow.tsx
@@ -20,7 +20,7 @@ import {
// Types
import { StoreState } from 'app/types';
-import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint } from '@grafana/ui';
+import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction } from '@grafana/ui';
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
@@ -78,10 +78,10 @@ export class QueryRow extends PureComponent {
this.onChangeQuery(null, true);
};
- onClickHintFix = action => {
+ onClickHintFix = (action: QueryFixAction) => {
const { datasourceInstance, exploreId, index } = this.props;
if (datasourceInstance && datasourceInstance.modifyQuery) {
- const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action);
+ const modifier = (queries: DataQuery, action: QueryFixAction) => datasourceInstance.modifyQuery(queries, action);
this.props.modifyQueries(exploreId, action, index, modifier);
}
};
@@ -116,14 +116,12 @@ export class QueryRow extends PureComponent {
) : (
DataQuery[];
+ modifier: (queries: DataQuery[], modification: QueryFixAction) => DataQuery[];
};
}
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index 1a11b7fcac9..63432e9c516 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -30,6 +30,7 @@ import {
DataQuery,
DataSourceSelectItem,
QueryHint,
+ QueryFixAction,
} from '@grafana/ui/src/types';
import {
ExploreId,
@@ -54,6 +55,7 @@ import {
ScanStopAction,
UpdateDatasourceInstanceAction,
QueriesImported,
+ ModifyQueriesAction,
} from './actionTypes';
type ThunkResult = ThunkAction;
@@ -385,12 +387,16 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
*/
export function modifyQueries(
exploreId: ExploreId,
- modification: any,
+ modification: QueryFixAction,
index: number,
modifier: any
): ThunkResult {
return dispatch => {
- dispatch({ type: ActionTypes.ModifyQueries, payload: { exploreId, modification, index, modifier } });
+ const modifyQueryAction: ModifyQueriesAction = {
+ type: ActionTypes.ModifyQueries,
+ payload: { exploreId, modification, index, modifier },
+ };
+ dispatch(modifyQueryAction);
if (!modification.preventSubmit) {
dispatch(runQueries(exploreId));
}
diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts
index eb67beee3b3..14c8d87bbd2 100644
--- a/public/app/features/explore/state/reducers.ts
+++ b/public/app/features/explore/state/reducers.ts
@@ -230,7 +230,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
case ActionTypes.ModifyQueries: {
const { initialQueries, modifiedQueries, queryTransactions } = state;
- const { modification, index, modifier } = action.payload as any;
+ const { modification, index, modifier } = action.payload;
let nextQueries: DataQuery[];
let nextQueryTransactions;
if (index === undefined) {
diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx
index 76d2facc5b6..5046c353f17 100644
--- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx
+++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx
@@ -177,10 +177,10 @@ export class LokiQueryField extends React.PureComponent {
- // const { hint, onClickHintFix } = this.props;
- // if (onClickHintFix && hint && hint.fix) {
- // onClickHintFix(hint.fix.action);
- // }
+ const { hint, onExecuteHint } = this.props;
+ if (onExecuteHint && hint && hint.fix) {
+ onExecuteHint(hint.fix.action);
+ }
};
onUpdateLanguage = () => {
diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
index 4bdd9f17392..c86ea5c4072 100644
--- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
+++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx
@@ -183,10 +183,10 @@ class PromQueryField extends React.PureComponent {
- // const { hint, onClickHintFix } = this.props;
- // if (onClickHintFix && hint && hint.fix) {
- // onClickHintFix(hint.fix.action);
- // }
+ const { hint, onExecuteHint } = this.props;
+ if (onExecuteHint && hint && hint.fix) {
+ onExecuteHint(hint.fix.action);
+ }
};
onUpdateLanguage = () => {
From 1f5bb767186b8b0c36594771d1602bffef2af68d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Mon, 4 Feb 2019 07:47:10 +0100
Subject: [PATCH 048/770] Refactor of action, actionTypes and reducer
---
public/app/features/explore/Explore.tsx | 17 +-
public/app/features/explore/QueryRow.tsx | 23 +-
public/app/features/explore/Wrapper.tsx | 14 +-
.../app/features/explore/state/actionTypes.ts | 664 ++++++++++--------
public/app/features/explore/state/actions.ts | 296 +++-----
.../features/explore/state/reducers.test.ts | 65 +-
public/app/features/explore/state/reducers.ts | 313 +++++----
7 files changed, 698 insertions(+), 694 deletions(-)
diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx
index 06a6ae24cac..31ffdf4ab24 100644
--- a/public/app/features/explore/Explore.tsx
+++ b/public/app/features/explore/Explore.tsx
@@ -18,15 +18,7 @@ import TableContainer from './TableContainer';
import TimePicker, { parseTime } from './TimePicker';
// Actions
-import {
- changeSize,
- changeTime,
- initializeExplore,
- modifyQueries,
- scanStart,
- scanStop,
- setQueries,
-} from './state/actions';
+import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions';
// Types
import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui';
@@ -35,6 +27,7 @@ import { StoreState } from 'app/types';
import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
import { Emitter } from 'app/core/utils/emitter';
import { ExploreToolbar } from './ExploreToolbar';
+import { scanStopAction } from './state/actionTypes';
interface ExploreProps {
StartPage?: any;
@@ -54,7 +47,7 @@ interface ExploreProps {
scanning?: boolean;
scanRange?: RawTimeRange;
scanStart: typeof scanStart;
- scanStop: typeof scanStop;
+ scanStopAction: typeof scanStopAction;
setQueries: typeof setQueries;
split: boolean;
showingStartPage?: boolean;
@@ -171,7 +164,7 @@ export class Explore extends React.PureComponent {
};
onStopScanning = () => {
- this.props.scanStop(this.props.exploreId);
+ this.props.scanStopAction({ exploreId: this.props.exploreId });
};
render() {
@@ -281,7 +274,7 @@ const mapDispatchToProps = {
initializeExplore,
modifyQueries,
scanStart,
- scanStop,
+ scanStopAction,
setQueries,
};
diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx
index bbc0bf0d101..5e2e8442e54 100644
--- a/public/app/features/explore/QueryRow.tsx
+++ b/public/app/features/explore/QueryRow.tsx
@@ -9,20 +9,14 @@ import QueryEditor from './QueryEditor';
import QueryTransactionStatus from './QueryTransactionStatus';
// Actions
-import {
- addQueryRow,
- changeQuery,
- highlightLogsExpression,
- modifyQueries,
- removeQueryRow,
- runQueries,
-} from './state/actions';
+import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions';
// Types
import { StoreState } from 'app/types';
import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction } from '@grafana/ui';
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
+import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
@@ -38,7 +32,7 @@ interface QueryRowProps {
className?: string;
exploreId: ExploreId;
datasourceInstance: ExploreDataSourceApi;
- highlightLogsExpression: typeof highlightLogsExpression;
+ highlightLogsExpressionAction: typeof highlightLogsExpressionAction;
history: HistoryItem[];
index: number;
initialQuery: DataQuery;
@@ -46,7 +40,7 @@ interface QueryRowProps {
queryTransactions: QueryTransaction[];
exploreEvents: Emitter;
range: RawTimeRange;
- removeQueryRow: typeof removeQueryRow;
+ removeQueryRowAction: typeof removeQueryRowAction;
runQueries: typeof runQueries;
}
@@ -88,14 +82,15 @@ export class QueryRow extends PureComponent {
onClickRemoveButton = () => {
const { exploreId, index } = this.props;
- this.props.removeQueryRow(exploreId, index);
+ this.props.removeQueryRowAction({ exploreId, index });
};
updateLogsHighlights = _.debounce((value: DataQuery) => {
const { datasourceInstance } = this.props;
if (datasourceInstance.getHighlighterExpression) {
+ const { exploreId } = this.props;
const expressions = [datasourceInstance.getHighlighterExpression(value)];
- this.props.highlightLogsExpression(this.props.exploreId, expressions);
+ this.props.highlightLogsExpressionAction({ exploreId, expressions });
}
}, 500);
@@ -168,9 +163,9 @@ function mapStateToProps(state: StoreState, { exploreId, index }) {
const mapDispatchToProps = {
addQueryRow,
changeQuery,
- highlightLogsExpression,
+ highlightLogsExpressionAction,
modifyQueries,
- removeQueryRow,
+ removeQueryRowAction,
runQueries,
};
diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx
index aca2e6d8cbd..f64b2704b71 100644
--- a/public/app/features/explore/Wrapper.tsx
+++ b/public/app/features/explore/Wrapper.tsx
@@ -7,16 +7,16 @@ import { StoreState } from 'app/types';
import { ExploreId, ExploreUrlState } from 'app/types/explore';
import { parseUrlState } from 'app/core/utils/explore';
-import { initializeExploreSplit, resetExplore } from './state/actions';
import ErrorBoundary from './ErrorBoundary';
import Explore from './Explore';
import { CustomScrollbar } from '@grafana/ui';
+import { initializeExploreSplitAction, resetExploreAction } from './state/actionTypes';
interface WrapperProps {
- initializeExploreSplit: typeof initializeExploreSplit;
+ initializeExploreSplitAction: typeof initializeExploreSplitAction;
split: boolean;
updateLocation: typeof updateLocation;
- resetExplore: typeof resetExplore;
+ resetExploreAction: typeof resetExploreAction;
urlStates: { [key: string]: string };
}
@@ -39,12 +39,12 @@ export class Wrapper extends Component {
componentDidMount() {
if (this.initialSplit) {
- this.props.initializeExploreSplit();
+ this.props.initializeExploreSplitAction();
}
}
componentWillUnmount() {
- this.props.resetExplore();
+ this.props.resetExploreAction();
}
render() {
@@ -77,9 +77,9 @@ const mapStateToProps = (state: StoreState) => {
};
const mapDispatchToProps = {
- initializeExploreSplit,
+ initializeExploreSplitAction,
updateLocation,
- resetExplore,
+ resetExploreAction,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
diff --git a/public/app/features/explore/state/actionTypes.ts b/public/app/features/explore/state/actionTypes.ts
index 53954f4dc2b..05ef661a8e5 100644
--- a/public/app/features/explore/state/actionTypes.ts
+++ b/public/app/features/explore/state/actionTypes.ts
@@ -1,6 +1,13 @@
// Types
import { Emitter } from 'app/core/core';
-import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi, QueryFixAction } from '@grafana/ui/src/types';
+import {
+ RawTimeRange,
+ TimeRange,
+ DataQuery,
+ DataSourceSelectItem,
+ DataSourceApi,
+ QueryFixAction,
+} from '@grafana/ui/src/types';
import {
ExploreId,
ExploreItemState,
@@ -9,233 +16,26 @@ import {
ResultType,
QueryTransaction,
} from 'app/types/explore';
+import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory';
+/** Higher order actions
+ *
+ */
export enum ActionTypes {
- AddQueryRow = 'explore/ADD_QUERY_ROW',
- ChangeDatasource = 'explore/CHANGE_DATASOURCE',
- ChangeQuery = 'explore/CHANGE_QUERY',
- ChangeSize = 'explore/CHANGE_SIZE',
- ChangeTime = 'explore/CHANGE_TIME',
- ClearQueries = 'explore/CLEAR_QUERIES',
- HighlightLogsExpression = 'explore/HIGHLIGHT_LOGS_EXPRESSION',
- InitializeExplore = 'explore/INITIALIZE_EXPLORE',
InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT',
- LoadDatasourceFailure = 'explore/LOAD_DATASOURCE_FAILURE',
- LoadDatasourceMissing = 'explore/LOAD_DATASOURCE_MISSING',
- LoadDatasourcePending = 'explore/LOAD_DATASOURCE_PENDING',
- LoadDatasourceSuccess = 'explore/LOAD_DATASOURCE_SUCCESS',
- ModifyQueries = 'explore/MODIFY_QUERIES',
- QueryTransactionFailure = 'explore/QUERY_TRANSACTION_FAILURE',
- QueryTransactionStart = 'explore/QUERY_TRANSACTION_START',
- QueryTransactionSuccess = 'explore/QUERY_TRANSACTION_SUCCESS',
- RemoveQueryRow = 'explore/REMOVE_QUERY_ROW',
- RunQueries = 'explore/RUN_QUERIES',
- RunQueriesEmpty = 'explore/RUN_QUERIES_EMPTY',
- ScanRange = 'explore/SCAN_RANGE',
- ScanStart = 'explore/SCAN_START',
- ScanStop = 'explore/SCAN_STOP',
- SetQueries = 'explore/SET_QUERIES',
SplitClose = 'explore/SPLIT_CLOSE',
SplitOpen = 'explore/SPLIT_OPEN',
- StateSave = 'explore/STATE_SAVE',
- ToggleGraph = 'explore/TOGGLE_GRAPH',
- ToggleLogs = 'explore/TOGGLE_LOGS',
- ToggleTable = 'explore/TOGGLE_TABLE',
- UpdateDatasourceInstance = 'explore/UPDATE_DATASOURCE_INSTANCE',
ResetExplore = 'explore/RESET_EXPLORE',
- QueriesImported = 'explore/QueriesImported',
-}
-
-export interface AddQueryRowAction {
- type: ActionTypes.AddQueryRow;
- payload: {
- exploreId: ExploreId;
- index: number;
- query: DataQuery;
- };
-}
-
-export interface ChangeQueryAction {
- type: ActionTypes.ChangeQuery;
- payload: {
- exploreId: ExploreId;
- query: DataQuery;
- index: number;
- override: boolean;
- };
-}
-
-export interface ChangeSizeAction {
- type: ActionTypes.ChangeSize;
- payload: {
- exploreId: ExploreId;
- width: number;
- height: number;
- };
-}
-
-export interface ChangeTimeAction {
- type: ActionTypes.ChangeTime;
- payload: {
- exploreId: ExploreId;
- range: TimeRange;
- };
-}
-
-export interface ClearQueriesAction {
- type: ActionTypes.ClearQueries;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface HighlightLogsExpressionAction {
- type: ActionTypes.HighlightLogsExpression;
- payload: {
- exploreId: ExploreId;
- expressions: string[];
- };
-}
-
-export interface InitializeExploreAction {
- type: ActionTypes.InitializeExplore;
- payload: {
- exploreId: ExploreId;
- containerWidth: number;
- eventBridge: Emitter;
- exploreDatasources: DataSourceSelectItem[];
- queries: DataQuery[];
- range: RawTimeRange;
- };
}
export interface InitializeExploreSplitAction {
type: ActionTypes.InitializeExploreSplit;
-}
-
-export interface LoadDatasourceFailureAction {
- type: ActionTypes.LoadDatasourceFailure;
- payload: {
- exploreId: ExploreId;
- error: string;
- };
-}
-
-export interface LoadDatasourcePendingAction {
- type: ActionTypes.LoadDatasourcePending;
- payload: {
- exploreId: ExploreId;
- requestedDatasourceName: string;
- };
-}
-
-export interface LoadDatasourceMissingAction {
- type: ActionTypes.LoadDatasourceMissing;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface LoadDatasourceSuccessAction {
- type: ActionTypes.LoadDatasourceSuccess;
- payload: {
- exploreId: ExploreId;
- StartPage?: any;
- datasourceInstance: any;
- history: HistoryItem[];
- logsHighlighterExpressions?: any[];
- showingStartPage: boolean;
- supportsGraph: boolean;
- supportsLogs: boolean;
- supportsTable: boolean;
- };
-}
-
-export interface ModifyQueriesAction {
- type: ActionTypes.ModifyQueries;
- payload: {
- exploreId: ExploreId;
- modification: QueryFixAction;
- index: number;
- modifier: (queries: DataQuery[], modification: QueryFixAction) => DataQuery[];
- };
-}
-
-export interface QueryTransactionFailureAction {
- type: ActionTypes.QueryTransactionFailure;
- payload: {
- exploreId: ExploreId;
- queryTransactions: QueryTransaction[];
- };
-}
-
-export interface QueryTransactionStartAction {
- type: ActionTypes.QueryTransactionStart;
- payload: {
- exploreId: ExploreId;
- resultType: ResultType;
- rowIndex: number;
- transaction: QueryTransaction;
- };
-}
-
-export interface QueryTransactionSuccessAction {
- type: ActionTypes.QueryTransactionSuccess;
- payload: {
- exploreId: ExploreId;
- history: HistoryItem[];
- queryTransactions: QueryTransaction[];
- };
-}
-
-export interface RemoveQueryRowAction {
- type: ActionTypes.RemoveQueryRow;
- payload: {
- exploreId: ExploreId;
- index: number;
- };
-}
-
-export interface RunQueriesEmptyAction {
- type: ActionTypes.RunQueriesEmpty;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface ScanStartAction {
- type: ActionTypes.ScanStart;
- payload: {
- exploreId: ExploreId;
- scanner: RangeScanner;
- };
-}
-
-export interface ScanRangeAction {
- type: ActionTypes.ScanRange;
- payload: {
- exploreId: ExploreId;
- range: RawTimeRange;
- };
-}
-
-export interface ScanStopAction {
- type: ActionTypes.ScanStop;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface SetQueriesAction {
- type: ActionTypes.SetQueries;
- payload: {
- exploreId: ExploreId;
- queries: DataQuery[];
- };
+ payload: {};
}
export interface SplitCloseAction {
type: ActionTypes.SplitClose;
+ payload: {};
}
export interface SplitOpenAction {
@@ -245,80 +45,384 @@ export interface SplitOpenAction {
};
}
-export interface StateSaveAction {
- type: ActionTypes.StateSave;
-}
-
-export interface ToggleTableAction {
- type: ActionTypes.ToggleTable;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface ToggleGraphAction {
- type: ActionTypes.ToggleGraph;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface ToggleLogsAction {
- type: ActionTypes.ToggleLogs;
- payload: {
- exploreId: ExploreId;
- };
-}
-
-export interface UpdateDatasourceInstanceAction {
- type: ActionTypes.UpdateDatasourceInstance;
- payload: {
- exploreId: ExploreId;
- datasourceInstance: DataSourceApi;
- };
-}
-
export interface ResetExploreAction {
type: ActionTypes.ResetExplore;
payload: {};
}
-export interface QueriesImported {
- type: ActionTypes.QueriesImported;
- payload: {
- exploreId: ExploreId;
- queries: DataQuery[];
- };
+/** Lower order actions
+ *
+ */
+export interface AddQueryRowPayload {
+ exploreId: ExploreId;
+ index: number;
+ query: DataQuery;
}
-export type Action =
- | AddQueryRowAction
- | ChangeQueryAction
- | ChangeSizeAction
- | ChangeTimeAction
- | ClearQueriesAction
- | HighlightLogsExpressionAction
- | InitializeExploreAction
+export interface ChangeQueryPayload {
+ exploreId: ExploreId;
+ query: DataQuery;
+ index: number;
+ override: boolean;
+}
+
+export interface ChangeSizePayload {
+ exploreId: ExploreId;
+ width: number;
+ height: number;
+}
+
+export interface ChangeTimePayload {
+ exploreId: ExploreId;
+ range: TimeRange;
+}
+
+export interface ClearQueriesPayload {
+ exploreId: ExploreId;
+}
+
+export interface HighlightLogsExpressionPayload {
+ exploreId: ExploreId;
+ expressions: string[];
+}
+
+export interface InitializeExplorePayload {
+ exploreId: ExploreId;
+ containerWidth: number;
+ eventBridge: Emitter;
+ exploreDatasources: DataSourceSelectItem[];
+ queries: DataQuery[];
+ range: RawTimeRange;
+}
+
+export interface LoadDatasourceFailurePayload {
+ exploreId: ExploreId;
+ error: string;
+}
+
+export interface LoadDatasourceMissingPayload {
+ exploreId: ExploreId;
+}
+
+export interface LoadDatasourcePendingPayload {
+ exploreId: ExploreId;
+ requestedDatasourceName: string;
+}
+
+export interface LoadDatasourceSuccessPayload {
+ exploreId: ExploreId;
+ StartPage?: any;
+ datasourceInstance: any;
+ history: HistoryItem[];
+ logsHighlighterExpressions?: any[];
+ showingStartPage: boolean;
+ supportsGraph: boolean;
+ supportsLogs: boolean;
+ supportsTable: boolean;
+}
+
+export interface ModifyQueriesPayload {
+ exploreId: ExploreId;
+ modification: QueryFixAction;
+ index: number;
+ modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery;
+}
+
+export interface QueryTransactionFailurePayload {
+ exploreId: ExploreId;
+ queryTransactions: QueryTransaction[];
+}
+
+export interface QueryTransactionStartPayload {
+ exploreId: ExploreId;
+ resultType: ResultType;
+ rowIndex: number;
+ transaction: QueryTransaction;
+}
+
+export interface QueryTransactionSuccessPayload {
+ exploreId: ExploreId;
+ history: HistoryItem[];
+ queryTransactions: QueryTransaction[];
+}
+
+export interface RemoveQueryRowPayload {
+ exploreId: ExploreId;
+ index: number;
+}
+
+export interface RunQueriesEmptyPayload {
+ exploreId: ExploreId;
+}
+
+export interface ScanStartPayload {
+ exploreId: ExploreId;
+ scanner: RangeScanner;
+}
+
+export interface ScanRangePayload {
+ exploreId: ExploreId;
+ range: RawTimeRange;
+}
+
+export interface ScanStopPayload {
+ exploreId: ExploreId;
+}
+
+export interface SetQueriesPayload {
+ exploreId: ExploreId;
+ queries: DataQuery[];
+}
+
+export interface SplitOpenPayload {
+ itemState: ExploreItemState;
+}
+
+export interface ToggleTablePayload {
+ exploreId: ExploreId;
+}
+
+export interface ToggleGraphPayload {
+ exploreId: ExploreId;
+}
+
+export interface ToggleLogsPayload {
+ exploreId: ExploreId;
+}
+
+export interface UpdateDatasourceInstancePayload {
+ exploreId: ExploreId;
+ datasourceInstance: DataSourceApi;
+}
+
+export interface QueriesImportedPayload {
+ exploreId: ExploreId;
+ queries: DataQuery[];
+}
+
+/**
+ * Adds a query row after the row with the given index.
+ */
+export const addQueryRowAction = actionCreatorFactory('explore/ADD_QUERY_ROW').create();
+
+/**
+ * Loads a new datasource identified by the given name.
+ */
+export const changeDatasourceAction = noPayloadActionCreatorFactory('explore/CHANGE_DATASOURCE').create();
+
+/**
+ * Query change handler for the query row with the given index.
+ * If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
+ */
+export const changeQueryAction = actionCreatorFactory('explore/CHANGE_QUERY').create();
+
+/**
+ * Keep track of the Explore container size, in particular the width.
+ * The width will be used to calculate graph intervals (number of datapoints).
+ */
+export const changeSizeAction = actionCreatorFactory('explore/CHANGE_SIZE').create();
+
+/**
+ * Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
+ */
+export const changeTimeAction = actionCreatorFactory('explore/CHANGE_TIME').create();
+
+/**
+ * Clear all queries and results.
+ */
+export const clearQueriesAction = actionCreatorFactory('explore/CLEAR_QUERIES').create();
+
+/**
+ * Highlight expressions in the log results
+ */
+export const highlightLogsExpressionAction = actionCreatorFactory(
+ 'explore/HIGHLIGHT_LOGS_EXPRESSION'
+).create();
+
+/**
+ * Initialize Explore state with state from the URL and the React component.
+ * Call this only on components for with the Explore state has not been initialized.
+ */
+export const initializeExploreAction = actionCreatorFactory(
+ 'explore/INITIALIZE_EXPLORE'
+).create();
+
+/**
+ * Initialize the wrapper split state
+ */
+export const initializeExploreSplitAction = noPayloadActionCreatorFactory('explore/INITIALIZE_EXPLORE_SPLIT').create();
+
+/**
+ * Display an error that happened during the selection of a datasource
+ */
+export const loadDatasourceFailureAction = actionCreatorFactory(
+ 'explore/LOAD_DATASOURCE_FAILURE'
+).create();
+
+/**
+ * Display an error when no datasources have been configured
+ */
+export const loadDatasourceMissingAction = actionCreatorFactory(
+ 'explore/LOAD_DATASOURCE_MISSING'
+).create();
+
+/**
+ * Start the async process of loading a datasource to display a loading indicator
+ */
+export const loadDatasourcePendingAction = actionCreatorFactory(
+ 'explore/LOAD_DATASOURCE_PENDING'
+).create();
+
+/**
+ * Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
+ * run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
+ * e.g., Prometheus -> Loki queries.
+ */
+export const loadDatasourceSuccessAction = actionCreatorFactory(
+ 'explore/LOAD_DATASOURCE_SUCCESS'
+).create();
+
+/**
+ * Action to modify a query given a datasource-specific modifier action.
+ * @param exploreId Explore area
+ * @param modification Action object with a type, e.g., ADD_FILTER
+ * @param index Optional query row index. If omitted, the modification is applied to all query rows.
+ * @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
+ */
+export const modifyQueriesAction = actionCreatorFactory('explore/MODIFY_QUERIES').create();
+
+/**
+ * Mark a query transaction as failed with an error extracted from the query response.
+ * The transaction will be marked as `done`.
+ */
+export const queryTransactionFailureAction = actionCreatorFactory(
+ 'explore/QUERY_TRANSACTION_FAILURE'
+).create();
+
+/**
+ * Start a query transaction for the given result type.
+ * @param exploreId Explore area
+ * @param transaction Query options and `done` status.
+ * @param resultType Associate the transaction with a result viewer, e.g., Graph
+ * @param rowIndex Index is used to associate latency for this transaction with a query row
+ */
+export const queryTransactionStartAction = actionCreatorFactory(
+ 'explore/QUERY_TRANSACTION_START'
+).create();
+
+/**
+ * Complete a query transaction, mark the transaction as `done` and store query state in URL.
+ * If the transaction was started by a scanner, it keeps on scanning for more results.
+ * Side-effect: the query is stored in localStorage.
+ * @param exploreId Explore area
+ * @param transactionId ID
+ * @param result Response from `datasourceInstance.query()`
+ * @param latency Duration between request and response
+ * @param queries Queries from all query rows
+ * @param datasourceId Origin datasource instance, used to discard results if current datasource is different
+ */
+export const queryTransactionSuccessAction = actionCreatorFactory(
+ 'explore/QUERY_TRANSACTION_SUCCESS'
+).create();
+
+/**
+ * Remove query row of the given index, as well as associated query results.
+ */
+export const removeQueryRowAction = actionCreatorFactory('explore/REMOVE_QUERY_ROW').create();
+export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create();
+export const runQueriesEmptyAction = actionCreatorFactory('explore/RUN_QUERIES_EMPTY').create();
+
+/**
+ * Start a scan for more results using the given scanner.
+ * @param exploreId Explore area
+ * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
+ */
+export const scanStartAction = actionCreatorFactory('explore/SCAN_START').create();
+export const scanRangeAction = actionCreatorFactory('explore/SCAN_RANGE').create();
+
+/**
+ * Stop any scanning for more results.
+ */
+export const scanStopAction = actionCreatorFactory('explore/SCAN_STOP').create();
+
+/**
+ * Reset queries to the given queries. Any modifications will be discarded.
+ * Use this action for clicks on query examples. Triggers a query run.
+ */
+export const setQueriesAction = actionCreatorFactory('explore/SET_QUERIES').create();
+
+/**
+ * Close the split view and save URL state.
+ */
+export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLOSE').create();
+
+/**
+ * Open the split view and copy the left state to be the right state.
+ * The right state is automatically initialized.
+ * The copy keeps all query modifications but wipes the query results.
+ */
+export const splitOpenAction = actionCreatorFactory('explore/SPLIT_OPEN').create();
+export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create();
+
+/**
+ * Expand/collapse the table result viewer. When collapsed, table queries won't be run.
+ */
+export const toggleTableAction = actionCreatorFactory('explore/TOGGLE_TABLE').create();
+
+/**
+ * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
+ */
+export const toggleGraphAction = actionCreatorFactory('explore/TOGGLE_GRAPH').create();
+
+/**
+ * Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
+ */
+export const toggleLogsAction = actionCreatorFactory('explore/TOGGLE_LOGS').create();
+
+/**
+ * Updates datasource instance before datasouce loading has started
+ */
+export const updateDatasourceInstanceAction = actionCreatorFactory(
+ 'explore/UPDATE_DATASOURCE_INSTANCE'
+).create();
+
+/**
+ * Resets state for explore.
+ */
+export const resetExploreAction = noPayloadActionCreatorFactory('explore/RESET_EXPLORE').create();
+export const queriesImportedAction = actionCreatorFactory('explore/QueriesImported').create();
+
+export type HigherOrderAction =
| InitializeExploreSplitAction
- | LoadDatasourceFailureAction
- | LoadDatasourceMissingAction
- | LoadDatasourcePendingAction
- | LoadDatasourceSuccessAction
- | ModifyQueriesAction
- | QueryTransactionFailureAction
- | QueryTransactionStartAction
- | QueryTransactionSuccessAction
- | RemoveQueryRowAction
- | RunQueriesEmptyAction
- | ScanRangeAction
- | ScanStartAction
- | ScanStopAction
- | SetQueriesAction
| SplitCloseAction
| SplitOpenAction
- | ToggleGraphAction
- | ToggleLogsAction
- | ToggleTableAction
- | UpdateDatasourceInstanceAction
| ResetExploreAction
- | QueriesImported;
+ | ActionOf;
+
+export type Action =
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf
+ | ActionOf;
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index 63432e9c516..f32575edda5 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -32,40 +32,49 @@ import {
QueryHint,
QueryFixAction,
} from '@grafana/ui/src/types';
+import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions } from 'app/types/explore';
import {
- ExploreId,
- ExploreUrlState,
- RangeScanner,
- ResultType,
- QueryOptions,
- QueryTransaction,
-} from 'app/types/explore';
-
-import {
- Action as ThunkableAction,
- ActionTypes,
- AddQueryRowAction,
- ChangeSizeAction,
- HighlightLogsExpressionAction,
- LoadDatasourceFailureAction,
- LoadDatasourceMissingAction,
- LoadDatasourcePendingAction,
- LoadDatasourceSuccessAction,
- QueryTransactionStartAction,
- ScanStopAction,
- UpdateDatasourceInstanceAction,
- QueriesImported,
- ModifyQueriesAction,
+ Action,
+ updateDatasourceInstanceAction,
+ changeQueryAction,
+ changeSizeAction,
+ ChangeSizePayload,
+ changeTimeAction,
+ scanStopAction,
+ clearQueriesAction,
+ initializeExploreAction,
+ loadDatasourceMissingAction,
+ loadDatasourceFailureAction,
+ loadDatasourcePendingAction,
+ queriesImportedAction,
+ LoadDatasourceSuccessPayload,
+ loadDatasourceSuccessAction,
+ modifyQueriesAction,
+ queryTransactionFailureAction,
+ queryTransactionStartAction,
+ queryTransactionSuccessAction,
+ scanRangeAction,
+ runQueriesEmptyAction,
+ scanStartAction,
+ setQueriesAction,
+ splitCloseAction,
+ splitOpenAction,
+ toggleGraphAction,
+ toggleLogsAction,
+ toggleTableAction,
+ addQueryRowAction,
+ AddQueryRowPayload,
} from './actionTypes';
+import { ActionOf } from 'app/core/redux/actionCreatorFactory';
-type ThunkResult = ThunkAction;
+type ThunkResult = ThunkAction;
-/**
- * Adds a query row after the row with the given index.
- */
-export function addQueryRow(exploreId: ExploreId, index: number): AddQueryRowAction {
+// /**
+// * Adds a query row after the row with the given index.
+// */
+export function addQueryRow(exploreId: ExploreId, index: number): ActionOf {
const query = generateEmptyQuery(index + 1);
- return { type: ActionTypes.AddQueryRow, payload: { exploreId, index, query } };
+ return addQueryRowAction({ exploreId, index, query });
}
/**
@@ -79,7 +88,7 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance));
- dispatch(updateDatasourceInstance(exploreId, newDataSourceInstance));
+ dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
dispatch(loadDatasource(exploreId, newDataSourceInstance));
};
}
@@ -100,7 +109,7 @@ export function changeQuery(
query = { ...generateEmptyQuery(index) };
}
- dispatch({ type: ActionTypes.ChangeQuery, payload: { exploreId, query, index, override } });
+ dispatch(changeQueryAction({ exploreId, query, index, override }));
if (override) {
dispatch(runQueries(exploreId));
}
@@ -114,8 +123,8 @@ export function changeQuery(
export function changeSize(
exploreId: ExploreId,
{ height, width }: { height: number; width: number }
-): ChangeSizeAction {
- return { type: ActionTypes.ChangeSize, payload: { exploreId, height, width } };
+): ActionOf {
+ return changeSizeAction({ exploreId, height, width });
}
/**
@@ -123,7 +132,7 @@ export function changeSize(
*/
export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult {
return dispatch => {
- dispatch({ type: ActionTypes.ChangeTime, payload: { exploreId, range } });
+ dispatch(changeTimeAction({ exploreId, range }));
dispatch(runQueries(exploreId));
};
}
@@ -133,19 +142,12 @@ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<
*/
export function clearQueries(exploreId: ExploreId): ThunkResult {
return dispatch => {
- dispatch(scanStop(exploreId));
- dispatch({ type: ActionTypes.ClearQueries, payload: { exploreId } });
+ dispatch(scanStopAction({ exploreId }));
+ dispatch(clearQueriesAction({ exploreId }));
dispatch(stateSave());
};
}
-/**
- * Highlight expressions in the log results
- */
-export function highlightLogsExpression(exploreId: ExploreId, expressions: string[]): HighlightLogsExpressionAction {
- return { type: ActionTypes.HighlightLogsExpression, payload: { exploreId, expressions } };
-}
-
/**
* Initialize Explore state with state from the URL and the React component.
* Call this only on components for with the Explore state has not been initialized.
@@ -167,18 +169,16 @@ export function initializeExplore(
meta: ds.meta,
}));
- dispatch({
- type: ActionTypes.InitializeExplore,
- payload: {
+ dispatch(
+ initializeExploreAction({
exploreId,
containerWidth,
- datasourceName,
eventBridge,
exploreDatasources,
queries,
range,
- },
- });
+ })
+ );
if (exploreDatasources.length >= 1) {
let instance;
@@ -195,75 +195,20 @@ export function initializeExplore(
instance = await getDatasourceSrv().get();
}
- dispatch(updateDatasourceInstance(exploreId, instance));
+ dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: instance }));
dispatch(loadDatasource(exploreId, instance));
} else {
- dispatch(loadDatasourceMissing(exploreId));
+ dispatch(loadDatasourceMissingAction({ exploreId }));
}
};
}
-/**
- * Initialize the wrapper split state
- */
-export function initializeExploreSplit() {
- return async dispatch => {
- dispatch({ type: ActionTypes.InitializeExploreSplit });
- };
-}
-
-/**
- * Display an error that happened during the selection of a datasource
- */
-export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
- type: ActionTypes.LoadDatasourceFailure,
- payload: {
- exploreId,
- error,
- },
-});
-
-/**
- * Display an error when no datasources have been configured
- */
-export const loadDatasourceMissing = (exploreId: ExploreId): LoadDatasourceMissingAction => ({
- type: ActionTypes.LoadDatasourceMissing,
- payload: { exploreId },
-});
-
-/**
- * Start the async process of loading a datasource to display a loading indicator
- */
-export const loadDatasourcePending = (
- exploreId: ExploreId,
- requestedDatasourceName: string
-): LoadDatasourcePendingAction => ({
- type: ActionTypes.LoadDatasourcePending,
- payload: {
- exploreId,
- requestedDatasourceName,
- },
-});
-
-export const queriesImported = (exploreId: ExploreId, queries: DataQuery[]): QueriesImported => {
- return {
- type: ActionTypes.QueriesImported,
- payload: {
- exploreId,
- queries,
- },
- };
-};
-
/**
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
* e.g., Prometheus -> Loki queries.
*/
-export const loadDatasourceSuccess = (
- exploreId: ExploreId,
- instance: any,
-): LoadDatasourceSuccessAction => {
+export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): ActionOf => {
// Capabilities
const supportsGraph = instance.meta.metrics;
const supportsLogs = instance.meta.logs;
@@ -276,37 +221,18 @@ export const loadDatasourceSuccess = (
// Save last-used datasource
store.set(LAST_USED_DATASOURCE_KEY, instance.name);
- return {
- type: ActionTypes.LoadDatasourceSuccess,
- payload: {
- exploreId,
- StartPage,
- datasourceInstance: instance,
- history,
- showingStartPage: Boolean(StartPage),
- supportsGraph,
- supportsLogs,
- supportsTable,
- },
- };
+ return loadDatasourceSuccessAction({
+ exploreId,
+ StartPage,
+ datasourceInstance: instance,
+ history,
+ showingStartPage: Boolean(StartPage),
+ supportsGraph,
+ supportsLogs,
+ supportsTable,
+ });
};
-/**
- * Updates datasource instance before datasouce loading has started
- */
-export function updateDatasourceInstance(
- exploreId: ExploreId,
- instance: DataSourceApi
-): UpdateDatasourceInstanceAction {
- return {
- type: ActionTypes.UpdateDatasourceInstance,
- payload: {
- exploreId,
- datasourceInstance: instance,
- },
- };
-}
-
export function importQueries(
exploreId: ExploreId,
queries: DataQuery[],
@@ -332,7 +258,7 @@ export function importQueries(
...generateEmptyQuery(i),
}));
- dispatch(queriesImported(exploreId, nextQueries));
+ dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
};
}
@@ -344,7 +270,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
const datasourceName = instance.name;
// Keep ID to track selection
- dispatch(loadDatasourcePending(exploreId, datasourceName));
+ dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
let datasourceError = null;
try {
@@ -355,7 +281,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
}
if (datasourceError) {
- dispatch(loadDatasourceFailure(exploreId, datasourceError));
+ dispatch(loadDatasourceFailureAction({ exploreId, error: datasourceError }));
return;
}
@@ -392,11 +318,7 @@ export function modifyQueries(
modifier: any
): ThunkResult {
return dispatch => {
- const modifyQueryAction: ModifyQueriesAction = {
- type: ActionTypes.ModifyQueries,
- payload: { exploreId, modification, index, modifier },
- };
- dispatch(modifyQueryAction);
+ dispatch(modifyQueriesAction({ exploreId, modification, index, modifier }));
if (!modification.preventSubmit) {
dispatch(runQueries(exploreId));
}
@@ -461,29 +383,10 @@ export function queryTransactionFailure(
return qt;
});
- dispatch({
- type: ActionTypes.QueryTransactionFailure,
- payload: { exploreId, queryTransactions: nextQueryTransactions },
- });
+ dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions }));
};
}
-/**
- * Start a query transaction for the given result type.
- * @param exploreId Explore area
- * @param transaction Query options and `done` status.
- * @param resultType Associate the transaction with a result viewer, e.g., Graph
- * @param rowIndex Index is used to associate latency for this transaction with a query row
- */
-export function queryTransactionStart(
- exploreId: ExploreId,
- transaction: QueryTransaction,
- resultType: ResultType,
- rowIndex: number
-): QueryTransactionStartAction {
- return { type: ActionTypes.QueryTransactionStart, payload: { exploreId, resultType, rowIndex, transaction } };
-}
-
/**
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
* If the transaction was started by a scanner, it keeps on scanning for more results.
@@ -540,14 +443,13 @@ export function queryTransactionSuccess(
// Side-effect: Saving history in localstorage
const nextHistory = updateHistory(history, datasourceId, queries);
- dispatch({
- type: ActionTypes.QueryTransactionSuccess,
- payload: {
+ dispatch(
+ queryTransactionSuccessAction({
exploreId,
history: nextHistory,
queryTransactions: nextQueryTransactions,
- },
- });
+ })
+ );
// Keep scanning for results if this was the last scanning transaction
if (scanning) {
@@ -555,26 +457,16 @@ export function queryTransactionSuccess(
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
if (!other) {
const range = scanner();
- dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
+ dispatch(scanRangeAction({ exploreId, range }));
}
} else {
// We can stop scanning if we have a result
- dispatch(scanStop(exploreId));
+ dispatch(scanStopAction({ exploreId }));
}
}
};
}
-/**
- * Remove query row of the given index, as well as associated query results.
- */
-export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult {
- return dispatch => {
- dispatch({ type: ActionTypes.RemoveQueryRow, payload: { exploreId, index } });
- dispatch(runQueries(exploreId));
- };
-}
-
/**
* Main action to run queries and dispatches sub-actions based on which result viewers are active
*/
@@ -592,7 +484,7 @@ export function runQueries(exploreId: ExploreId) {
} = getState().explore[exploreId];
if (!hasNonEmptyQuery(modifiedQueries)) {
- dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } });
+ dispatch(runQueriesEmptyAction({ exploreId }));
dispatch(stateSave()); // Remember to saves to state and update location
return;
}
@@ -673,7 +565,7 @@ function runQueriesForType(
queryIntervals,
scanning
);
- dispatch(queryTransactionStart(exploreId, transaction, resultType, rowIndex));
+ dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction }));
try {
const now = Date.now();
const res = await datasourceInstance.query(transaction.options);
@@ -697,21 +589,14 @@ function runQueriesForType(
export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult {
return dispatch => {
// Register the scanner
- dispatch({ type: ActionTypes.ScanStart, payload: { exploreId, scanner } });
+ dispatch(scanStartAction({ exploreId, scanner }));
// Scanning must trigger query run, and return the new range
const range = scanner();
// Set the new range to be displayed
- dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
+ dispatch(scanRangeAction({ exploreId, range }));
};
}
-/**
- * Stop any scanning for more results.
- */
-export function scanStop(exploreId: ExploreId): ScanStopAction {
- return { type: ActionTypes.ScanStop, payload: { exploreId } };
-}
-
/**
* Reset queries to the given queries. Any modifications will be discarded.
* Use this action for clicks on query examples. Triggers a query run.
@@ -720,13 +605,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
return dispatch => {
// Inject react keys into query objects
const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() }));
- dispatch({
- type: ActionTypes.SetQueries,
- payload: {
- exploreId,
- queries,
- },
- });
+ dispatch(setQueriesAction({ exploreId, queries }));
dispatch(runQueries(exploreId));
};
}
@@ -736,7 +615,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
*/
export function splitClose(): ThunkResult {
return dispatch => {
- dispatch({ type: ActionTypes.SplitClose });
+ dispatch(splitCloseAction());
dispatch(stateSave());
};
}
@@ -755,7 +634,7 @@ export function splitOpen(): ThunkResult {
queryTransactions: [],
initialQueries: leftState.modifiedQueries.slice(),
};
- dispatch({ type: ActionTypes.SplitOpen, payload: { itemState } });
+ dispatch(splitOpenAction({ itemState }));
dispatch(stateSave());
};
}
@@ -791,7 +670,7 @@ export function stateSave() {
*/
export function toggleGraph(exploreId: ExploreId): ThunkResult {
return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleGraph, payload: { exploreId } });
+ dispatch(toggleGraphAction({ exploreId }));
if (getState().explore[exploreId].showingGraph) {
dispatch(runQueries(exploreId));
}
@@ -803,7 +682,7 @@ export function toggleGraph(exploreId: ExploreId): ThunkResult {
*/
export function toggleLogs(exploreId: ExploreId): ThunkResult {
return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleLogs, payload: { exploreId } });
+ dispatch(toggleLogsAction({ exploreId }));
if (getState().explore[exploreId].showingLogs) {
dispatch(runQueries(exploreId));
}
@@ -815,18 +694,9 @@ export function toggleLogs(exploreId: ExploreId): ThunkResult {
*/
export function toggleTable(exploreId: ExploreId): ThunkResult {
return (dispatch, getState) => {
- dispatch({ type: ActionTypes.ToggleTable, payload: { exploreId } });
+ dispatch(toggleTableAction({ exploreId }));
if (getState().explore[exploreId].showingTable) {
dispatch(runQueries(exploreId));
}
};
}
-
-/**
- * Resets state for explore.
- */
-export function resetExplore(): ThunkResult {
- return dispatch => {
- dispatch({ type: ActionTypes.ResetExplore, payload: {} });
- };
-}
diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts
index 8227a947c5b..44079313c04 100644
--- a/public/app/features/explore/state/reducers.test.ts
+++ b/public/app/features/explore/state/reducers.test.ts
@@ -1,42 +1,47 @@
-import { Action, ActionTypes } from './actionTypes';
import { itemReducer, makeExploreItemState } from './reducers';
-import { ExploreId } from 'app/types/explore';
+import { ExploreId, ExploreItemState } from 'app/types/explore';
+import { reducerTester } from 'test/core/redux/reducerTester';
+import { scanStartAction, scanStopAction } from './actionTypes';
+import { Reducer } from 'redux';
+import { ActionOf } from 'app/core/redux/actionCreatorFactory';
describe('Explore item reducer', () => {
describe('scanning', () => {
test('should start scanning', () => {
- let state = makeExploreItemState();
- const action: Action = {
- type: ActionTypes.ScanStart,
- payload: {
- exploreId: ExploreId.left,
- scanner: jest.fn(),
- },
+ const scanner = jest.fn();
+ const initalState = {
+ ...makeExploreItemState(),
+ scanning: false,
+ scanner: undefined,
};
- state = itemReducer(state, action);
- expect(state.scanning).toBeTruthy();
- expect(state.scanner).toBe(action.payload.scanner);
+
+ reducerTester()
+ .givenReducer(itemReducer as Reducer>, initalState)
+ .whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner }))
+ .thenStateShouldEqual({
+ ...makeExploreItemState(),
+ scanning: true,
+ scanner,
+ });
});
test('should stop scanning', () => {
- let state = makeExploreItemState();
- const start: Action = {
- type: ActionTypes.ScanStart,
- payload: {
- exploreId: ExploreId.left,
- scanner: jest.fn(),
- },
+ const scanner = jest.fn();
+ const initalState = {
+ ...makeExploreItemState(),
+ scanning: true,
+ scanner,
+ scanRange: {},
};
- state = itemReducer(state, start);
- expect(state.scanning).toBeTruthy();
- const action: Action = {
- type: ActionTypes.ScanStop,
- payload: {
- exploreId: ExploreId.left,
- },
- };
- state = itemReducer(state, action);
- expect(state.scanning).toBeFalsy();
- expect(state.scanner).toBeUndefined();
+
+ reducerTester()
+ .givenReducer(itemReducer as Reducer>, initalState)
+ .whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
+ .thenStateShouldEqual({
+ ...makeExploreItemState(),
+ scanning: false,
+ scanner: undefined,
+ scanRange: undefined,
+ });
});
});
});
diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts
index 14c8d87bbd2..fc9be0c28b8 100644
--- a/public/app/features/explore/state/reducers.ts
+++ b/public/app/features/explore/state/reducers.ts
@@ -7,7 +7,36 @@ import {
import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
import { DataQuery } from '@grafana/ui/src/types';
-import { Action, ActionTypes } from './actionTypes';
+import { HigherOrderAction, ActionTypes } from './actionTypes';
+import { reducerFactory } from 'app/core/redux';
+import {
+ addQueryRowAction,
+ changeQueryAction,
+ changeSizeAction,
+ changeTimeAction,
+ clearQueriesAction,
+ highlightLogsExpressionAction,
+ initializeExploreAction,
+ updateDatasourceInstanceAction,
+ loadDatasourceFailureAction,
+ loadDatasourceMissingAction,
+ loadDatasourcePendingAction,
+ loadDatasourceSuccessAction,
+ modifyQueriesAction,
+ queryTransactionFailureAction,
+ queryTransactionStartAction,
+ queryTransactionSuccessAction,
+ removeQueryRowAction,
+ runQueriesEmptyAction,
+ scanRangeAction,
+ scanStartAction,
+ scanStopAction,
+ setQueriesAction,
+ toggleGraphAction,
+ toggleLogsAction,
+ toggleTableAction,
+ queriesImportedAction,
+} from './actionTypes';
export const DEFAULT_RANGE = {
from: 'now-6h',
@@ -58,9 +87,10 @@ export const initialExploreState: ExploreState = {
/**
* Reducer for an Explore area, to be used by the global Explore reducer.
*/
-export const itemReducer = (state, action: Action): ExploreItemState => {
- switch (action.type) {
- case ActionTypes.AddQueryRow: {
+export const itemReducer = reducerFactory({} as ExploreItemState)
+ .addMapper({
+ filter: addQueryRowAction,
+ mapper: (state, action): ExploreItemState => {
const { initialQueries, modifiedQueries, queryTransactions } = state;
const { index, query } = action.payload;
@@ -77,10 +107,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
// Ongoing transactions need to update their row indices
const nextQueryTransactions = queryTransactions.map(qt => {
if (qt.rowIndex > index) {
- return {
- ...qt,
- rowIndex: qt.rowIndex + 1,
- };
+ return { ...qt, rowIndex: qt.rowIndex + 1 };
}
return qt;
});
@@ -92,9 +119,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
modifiedQueries: nextModifiedQueries,
queryTransactions: nextQueryTransactions,
};
- }
-
- case ActionTypes.ChangeQuery: {
+ },
+ })
+ .addMapper({
+ filter: changeQueryAction,
+ mapper: (state, action): ExploreItemState => {
const { initialQueries, queryTransactions } = state;
let { modifiedQueries } = state;
const { query, index, override } = action.payload;
@@ -102,17 +131,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
// Fast path: only change modifiedQueries to not trigger an update
modifiedQueries[index] = query;
if (!override) {
- return {
- ...state,
- modifiedQueries,
- };
+ return { ...state, modifiedQueries };
}
// Override path: queries are completely reset
- const nextQuery: DataQuery = {
- ...query,
- ...generateEmptyQuery(index),
- };
+ const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
const nextQueries = [...initialQueries];
nextQueries[index] = nextQuery;
modifiedQueries = [...nextQueries];
@@ -126,9 +149,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
- }
-
- case ActionTypes.ChangeSize: {
+ },
+ })
+ .addMapper({
+ filter: changeSizeAction,
+ mapper: (state, action): ExploreItemState => {
const { range, datasourceInstance } = state;
let interval = '1s';
if (datasourceInstance && datasourceInstance.interval) {
@@ -137,16 +162,17 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
const containerWidth = action.payload.width;
const queryIntervals = getIntervals(range, interval, containerWidth);
return { ...state, containerWidth, queryIntervals };
- }
-
- case ActionTypes.ChangeTime: {
- return {
- ...state,
- range: action.payload.range,
- };
- }
-
- case ActionTypes.ClearQueries: {
+ },
+ })
+ .addMapper({
+ filter: changeTimeAction,
+ mapper: (state, action): ExploreItemState => {
+ return { ...state, range: action.payload.range };
+ },
+ })
+ .addMapper({
+ filter: clearQueriesAction,
+ mapper: (state): ExploreItemState => {
const queries = ensureQueries();
return {
...state,
@@ -155,14 +181,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
queryTransactions: [],
showingStartPage: Boolean(state.StartPage),
};
- }
-
- case ActionTypes.HighlightLogsExpression: {
+ },
+ })
+ .addMapper({
+ filter: highlightLogsExpressionAction,
+ mapper: (state, action): ExploreItemState => {
const { expressions } = action.payload;
return { ...state, logsHighlighterExpressions: expressions };
- }
-
- case ActionTypes.InitializeExplore: {
+ },
+ })
+ .addMapper({
+ filter: initializeExploreAction,
+ mapper: (state, action): ExploreItemState => {
const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload;
return {
...state,
@@ -174,30 +204,37 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
initialized: true,
modifiedQueries: queries.slice(),
};
- }
-
- case ActionTypes.UpdateDatasourceInstance: {
+ },
+ })
+ .addMapper({
+ filter: updateDatasourceInstanceAction,
+ mapper: (state, action): ExploreItemState => {
const { datasourceInstance } = action.payload;
- return {
- ...state,
- datasourceInstance,
- datasourceName: datasourceInstance.name,
- };
- }
-
- case ActionTypes.LoadDatasourceFailure: {
+ return { ...state, datasourceInstance };
+ /*datasourceName: datasourceInstance.name removed after refactor, datasourceName does not exists on ExploreItemState */
+ },
+ })
+ .addMapper({
+ filter: loadDatasourceFailureAction,
+ mapper: (state, action): ExploreItemState => {
return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
- }
-
- case ActionTypes.LoadDatasourceMissing: {
+ },
+ })
+ .addMapper({
+ filter: loadDatasourceMissingAction,
+ mapper: (state): ExploreItemState => {
return { ...state, datasourceMissing: true, datasourceLoading: false };
- }
-
- case ActionTypes.LoadDatasourcePending: {
+ },
+ })
+ .addMapper({
+ filter: loadDatasourcePendingAction,
+ mapper: (state, action): ExploreItemState => {
return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
- }
-
- case ActionTypes.LoadDatasourceSuccess: {
+ },
+ })
+ .addMapper({
+ filter: loadDatasourceSuccessAction,
+ mapper: (state, action): ExploreItemState => {
const { containerWidth, range } = state;
const {
StartPage,
@@ -226,9 +263,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
logsHighlighterExpressions: undefined,
queryTransactions: [],
};
- }
-
- case ActionTypes.ModifyQueries: {
+ },
+ })
+ .addMapper({
+ filter: modifyQueriesAction,
+ mapper: (state, action): ExploreItemState => {
const { initialQueries, modifiedQueries, queryTransactions } = state;
const { modification, index, modifier } = action.payload;
let nextQueries: DataQuery[];
@@ -246,12 +285,7 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
nextQueries = initialQueries.map((query, i) => {
// Synchronize all queries with local query cache to ensure consistency
// TODO still needed?
- return i === index
- ? {
- ...modifier(modifiedQueries[i], modification),
- ...generateEmptyQuery(i),
- }
- : query;
+ return i === index ? { ...modifier(modifiedQueries[i], modification), ...generateEmptyQuery(i) } : query;
});
nextQueryTransactions = queryTransactions
// Consume the hint corresponding to the action
@@ -270,18 +304,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
- }
-
- case ActionTypes.QueryTransactionFailure: {
+ },
+ })
+ .addMapper({
+ filter: queryTransactionFailureAction,
+ mapper: (state, action): ExploreItemState => {
const { queryTransactions } = action.payload;
- return {
- ...state,
- queryTransactions,
- showingStartPage: false,
- };
- }
-
- case ActionTypes.QueryTransactionStart: {
+ return { ...state, queryTransactions, showingStartPage: false };
+ },
+ })
+ .addMapper({
+ filter: queryTransactionStartAction,
+ mapper: (state, action): ExploreItemState => {
const { queryTransactions } = state;
const { resultType, rowIndex, transaction } = action.payload;
// Discarding existing transactions of same type
@@ -292,14 +326,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
// Append new transaction
const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
- return {
- ...state,
- queryTransactions: nextQueryTransactions,
- showingStartPage: false,
- };
- }
-
- case ActionTypes.QueryTransactionSuccess: {
+ return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
+ },
+ })
+ .addMapper({
+ filter: queryTransactionSuccessAction,
+ mapper: (state, action): ExploreItemState => {
const { datasourceInstance, queryIntervals } = state;
const { history, queryTransactions } = action.payload;
const results = calculateResultsFromQueryTransactions(
@@ -308,16 +340,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
queryIntervals.intervalMs
);
- return {
- ...state,
- ...results,
- history,
- queryTransactions,
- showingStartPage: false,
- };
- }
-
- case ActionTypes.RemoveQueryRow: {
+ return { ...state, ...results, history, queryTransactions, showingStartPage: false };
+ },
+ })
+ .addMapper({
+ filter: removeQueryRowAction,
+ mapper: (state, action): ExploreItemState => {
const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
let { modifiedQueries } = state;
const { index } = action.payload;
@@ -346,21 +374,29 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
- }
-
- case ActionTypes.RunQueriesEmpty: {
+ },
+ })
+ .addMapper({
+ filter: runQueriesEmptyAction,
+ mapper: (state): ExploreItemState => {
return { ...state, queryTransactions: [] };
- }
-
- case ActionTypes.ScanRange: {
+ },
+ })
+ .addMapper({
+ filter: scanRangeAction,
+ mapper: (state, action): ExploreItemState => {
return { ...state, scanRange: action.payload.range };
- }
-
- case ActionTypes.ScanStart: {
+ },
+ })
+ .addMapper({
+ filter: scanStartAction,
+ mapper: (state, action): ExploreItemState => {
return { ...state, scanning: true, scanner: action.payload.scanner };
- }
-
- case ActionTypes.ScanStop: {
+ },
+ })
+ .addMapper({
+ filter: scanStopAction,
+ mapper: (state): ExploreItemState => {
const { queryTransactions } = state;
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
return {
@@ -370,14 +406,18 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
scanRange: undefined,
scanner: undefined,
};
- }
-
- case ActionTypes.SetQueries: {
+ },
+ })
+ .addMapper({
+ filter: setQueriesAction,
+ mapper: (state, action): ExploreItemState => {
const { queries } = action.payload;
return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
- }
-
- case ActionTypes.ToggleGraph: {
+ },
+ })
+ .addMapper({
+ filter: toggleGraphAction,
+ mapper: (state): ExploreItemState => {
const showingGraph = !state.showingGraph;
let nextQueryTransactions = state.queryTransactions;
if (!showingGraph) {
@@ -385,9 +425,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
}
return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
- }
-
- case ActionTypes.ToggleLogs: {
+ },
+ })
+ .addMapper({
+ filter: toggleLogsAction,
+ mapper: (state): ExploreItemState => {
const showingLogs = !state.showingLogs;
let nextQueryTransactions = state.queryTransactions;
if (!showingLogs) {
@@ -395,9 +437,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
}
return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
- }
-
- case ActionTypes.ToggleTable: {
+ },
+ })
+ .addMapper({
+ filter: toggleTableAction,
+ mapper: (state): ExploreItemState => {
const showingTable = !state.showingTable;
if (showingTable) {
return { ...state, showingTable, queryTransactions: state.queryTransactions };
@@ -412,25 +456,21 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
);
return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
- }
-
- case ActionTypes.QueriesImported: {
- return {
- ...state,
- initialQueries: action.payload.queries,
- modifiedQueries: action.payload.queries.slice(),
- };
- }
- }
-
- return state;
-};
+ },
+ })
+ .addMapper({
+ filter: queriesImportedAction,
+ mapper: (state, action): ExploreItemState => {
+ return { ...state, initialQueries: action.payload.queries, modifiedQueries: action.payload.queries.slice() };
+ },
+ })
+ .create();
/**
* Global Explore reducer that handles multiple Explore areas (left and right).
* Actions that have an `exploreId` get routed to the ExploreItemReducer.
*/
-export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => {
+export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
switch (action.type) {
case ActionTypes.SplitClose: {
return { ...state, split: false };
@@ -453,10 +493,7 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
const { exploreId } = action.payload as any;
if (exploreId !== undefined) {
const exploreItemState = state[exploreId];
- return {
- ...state,
- [exploreId]: itemReducer(exploreItemState, action),
- };
+ return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
}
}
From d9578bc48505c890a75c9e6c7ef996fe0886531d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Mon, 4 Feb 2019 08:17:18 +0100
Subject: [PATCH 049/770] Merge with master
---
.../datasource/loki/components/LokiQueryEditor.tsx | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
index 634a642c65e..e9912522f16 100644
--- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
+++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx
@@ -33,7 +33,7 @@ export class LokiQueryEditor extends PureComponent {
query: {
...this.state.query,
expr: query.expr,
- }
+ },
});
};
@@ -61,12 +61,18 @@ export class LokiQueryEditor extends PureComponent {
datasource={datasource}
initialQuery={query}
onQueryChange={this.onFieldChange}
- onPressEnter={this.onRunQuery}
+ onExecuteQuery={this.onRunQuery}
+ history={[]}
/>
From f5084045f216c79702ad7124ae72a7144d014881 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Mon, 4 Feb 2019 09:32:39 +0100
Subject: [PATCH 050/770] Fix save provisioned dashboard modal
---
public/app/features/dashboard/components/SaveModals/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/app/features/dashboard/components/SaveModals/index.ts b/public/app/features/dashboard/components/SaveModals/index.ts
index afab0796d28..6f55cc2ce06 100644
--- a/public/app/features/dashboard/components/SaveModals/index.ts
+++ b/public/app/features/dashboard/components/SaveModals/index.ts
@@ -1,2 +1,3 @@
export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl';
export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl';
+export { SaveProvisionedDashboardModalCtrl } from './SaveProvisionedDashboardModalCtrl';
From fdd5ac1895e4ee7af5c9a060ba2c8fc8b4ef830d Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Mon, 4 Feb 2019 09:55:23 +0100
Subject: [PATCH 051/770] devenv: switching back using loki master plus various
fixes
---
devenv/docker/blocks/loki/docker-compose.yaml | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
diff --git a/devenv/docker/blocks/loki/docker-compose.yaml b/devenv/docker/blocks/loki/docker-compose.yaml
index c2fee15b0bb..0ac5d439354 100644
--- a/devenv/docker/blocks/loki/docker-compose.yaml
+++ b/devenv/docker/blocks/loki/docker-compose.yaml
@@ -1,24 +1,14 @@
-version: "3"
-
-networks:
loki:
-
-services:
- loki:
- image: grafana/loki:master-3e6a75e
+ image: grafana/loki:master
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
- networks:
- - loki
promtail:
- image: grafana/promtail:master-3e6a75e
+ image: grafana/promtail:master
volumes:
- ./docker/blocks/loki/config.yaml:/etc/promtail/docker-config.yaml
- /var/log:/var/log
- ../data/log:/var/log/grafana
command:
-config.file=/etc/promtail/docker-config.yaml
- networks:
- - loki
From c61e90543411fee4971f9f67918ef09de32065ac Mon Sep 17 00:00:00 2001
From: Peter Holmberg
Date: Mon, 4 Feb 2019 10:22:45 +0100
Subject: [PATCH 052/770] fixing logging action
---
.../ValueMappingsEditor/ValueMappingsEditor.story.tsx | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
index d6c8cec8c1e..85504f6cd09 100644
--- a/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
+++ b/packages/grafana-ui/src/components/ValueMappingsEditor/ValueMappingsEditor.story.tsx
@@ -6,12 +6,5 @@ import { ValueMappingsEditor } from './ValueMappingsEditor';
const ValueMappingsEditorStories = storiesOf('UI/ValueMappingsEditor', module);
ValueMappingsEditorStories.add('default', () => {
- return (
- {
- action('Mapping changed');
- }}
- />
- );
+ return ;
});
From 6b98b05976fb837433370ff45d214b6889e1bc14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Mon, 4 Feb 2019 11:07:32 +0100
Subject: [PATCH 053/770] Removed modifiedQueries from state
---
public/app/features/explore/QueryEditor.tsx | 11 ++----
public/app/features/explore/QueryRows.tsx | 9 +++--
public/app/features/explore/state/actions.ts | 18 ++++-----
public/app/features/explore/state/reducers.ts | 39 ++++---------------
public/app/types/explore.ts | 8 +---
5 files changed, 26 insertions(+), 59 deletions(-)
diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx
index 083cd8a2e17..1d329f1c56e 100644
--- a/public/app/features/explore/QueryEditor.tsx
+++ b/public/app/features/explore/QueryEditor.tsx
@@ -14,7 +14,7 @@ interface QueryEditorProps {
datasource: any;
error?: string | JSX.Element;
onExecuteQuery?: () => void;
- onQueryChange?: (value: DataQuery, override?: boolean) => void;
+ onQueryChange?: (value: DataQuery) => void;
initialQuery: DataQuery;
exploreEvents: Emitter;
range: RawTimeRange;
@@ -40,20 +40,17 @@ export default class QueryEditor extends PureComponent {
datasource,
target,
refresh: () => {
- this.props.onQueryChange(target, false);
+ this.props.onQueryChange(target);
this.props.onExecuteQuery();
},
events: exploreEvents,
- panel: {
- datasource,
- targets: [target],
- },
+ panel: { datasource, targets: [target] },
dashboard: {},
},
};
this.component = loader.load(this.element, scopeProps, template);
- this.props.onQueryChange(target, false);
+ this.props.onQueryChange(target);
}
componentWillUnmount() {
diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx
index f8bb6e5ce6b..d65c1283bd6 100644
--- a/public/app/features/explore/QueryRows.tsx
+++ b/public/app/features/explore/QueryRows.tsx
@@ -21,10 +21,11 @@ export default class QueryRows extends PureComponent {
const { className = '', exploreEvents, exploreId, initialQueries } = this.props;
return (
- {initialQueries.map((query, index) => (
- // TODO instead of relying on initialQueries, move to react key list in redux
-
- ))}
+ {initialQueries.map((query, index) => {
+ // using query.key will introduce infinite loop because QueryEditor#53
+ const key = query.datasource ? `${query.datasource}-${index}` : query.key;
+ return ;
+ })}
);
}
diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts
index f32575edda5..8530e7678ad 100644
--- a/public/app/features/explore/state/actions.ts
+++ b/public/app/features/explore/state/actions.ts
@@ -84,9 +84,9 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
return async (dispatch, getState) => {
const newDataSourceInstance = await getDatasourceSrv().get(datasource);
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
- const modifiedQueries = getState().explore[exploreId].modifiedQueries;
+ const queries = getState().explore[exploreId].initialQueries;
- await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance));
+ await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
dispatch(loadDatasource(exploreId, newDataSourceInstance));
@@ -254,7 +254,7 @@ export function importQueries(
}
const nextQueries = importedQueries.map((q, i) => ({
- ...importedQueries[i],
+ ...q,
...generateEmptyQuery(i),
}));
@@ -474,7 +474,7 @@ export function runQueries(exploreId: ExploreId) {
return (dispatch, getState) => {
const {
datasourceInstance,
- modifiedQueries,
+ initialQueries,
showingLogs,
showingGraph,
showingTable,
@@ -483,7 +483,7 @@ export function runQueries(exploreId: ExploreId) {
supportsTable,
} = getState().explore[exploreId];
- if (!hasNonEmptyQuery(modifiedQueries)) {
+ if (!hasNonEmptyQuery(initialQueries)) {
dispatch(runQueriesEmptyAction({ exploreId }));
dispatch(stateSave()); // Remember to saves to state and update location
return;
@@ -547,7 +547,7 @@ function runQueriesForType(
const {
datasourceInstance,
eventBridge,
- modifiedQueries: queries,
+ initialQueries: queries,
queryIntervals,
range,
scanning,
@@ -632,7 +632,7 @@ export function splitOpen(): ThunkResult {
const itemState = {
...leftState,
queryTransactions: [],
- initialQueries: leftState.modifiedQueries.slice(),
+ initialQueries: leftState.initialQueries.slice(),
};
dispatch(splitOpenAction({ itemState }));
dispatch(stateSave());
@@ -649,14 +649,14 @@ export function stateSave() {
const urlStates: { [index: string]: string } = {};
const leftUrlState: ExploreUrlState = {
datasource: left.datasourceInstance.name,
- queries: left.modifiedQueries.map(clearQueryKeys),
+ queries: left.initialQueries.map(clearQueryKeys),
range: left.range,
};
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
if (split) {
const rightUrlState: ExploreUrlState = {
datasource: right.datasourceInstance.name,
- queries: right.modifiedQueries.map(clearQueryKeys),
+ queries: right.initialQueries.map(clearQueryKeys),
range: right.range,
};
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts
index fc9be0c28b8..9343cf0ec57 100644
--- a/public/app/features/explore/state/reducers.ts
+++ b/public/app/features/explore/state/reducers.ts
@@ -61,7 +61,6 @@ export const makeExploreItemState = (): ExploreItemState => ({
history: [],
initialQueries: [],
initialized: false,
- modifiedQueries: [],
queryTransactions: [],
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
range: DEFAULT_RANGE,
@@ -91,16 +90,9 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
.addMapper({
filter: addQueryRowAction,
mapper: (state, action): ExploreItemState => {
- const { initialQueries, modifiedQueries, queryTransactions } = state;
+ const { initialQueries, queryTransactions } = state;
const { index, query } = action.payload;
- // Add new query row after given index, keep modifications of existing rows
- const nextModifiedQueries = [
- ...modifiedQueries.slice(0, index + 1),
- { ...query },
- ...initialQueries.slice(index + 1),
- ];
-
// Add to initialQueries, which will cause a new row to be rendered
const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.slice(index + 1)];
@@ -116,7 +108,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
...state,
initialQueries: nextQueries,
logsHighlighterExpressions: undefined,
- modifiedQueries: nextModifiedQueries,
queryTransactions: nextQueryTransactions,
};
},
@@ -125,20 +116,12 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
filter: changeQueryAction,
mapper: (state, action): ExploreItemState => {
const { initialQueries, queryTransactions } = state;
- let { modifiedQueries } = state;
- const { query, index, override } = action.payload;
-
- // Fast path: only change modifiedQueries to not trigger an update
- modifiedQueries[index] = query;
- if (!override) {
- return { ...state, modifiedQueries };
- }
+ const { query, index } = action.payload;
// Override path: queries are completely reset
const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
const nextQueries = [...initialQueries];
nextQueries[index] = nextQuery;
- modifiedQueries = [...nextQueries];
// Discard ongoing transaction related to row query
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
@@ -146,7 +129,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
return {
...state,
initialQueries: nextQueries,
- modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
},
@@ -177,7 +159,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
return {
...state,
initialQueries: queries.slice(),
- modifiedQueries: queries.slice(),
queryTransactions: [],
showingStartPage: Boolean(state.StartPage),
};
@@ -202,7 +183,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
range,
initialQueries: queries,
initialized: true,
- modifiedQueries: queries.slice(),
};
},
})
@@ -268,14 +248,14 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
.addMapper({
filter: modifyQueriesAction,
mapper: (state, action): ExploreItemState => {
- const { initialQueries, modifiedQueries, queryTransactions } = state;
+ const { initialQueries, queryTransactions } = state;
const { modification, index, modifier } = action.payload;
let nextQueries: DataQuery[];
let nextQueryTransactions;
if (index === undefined) {
// Modify all queries
nextQueries = initialQueries.map((query, i) => ({
- ...modifier(modifiedQueries[i], modification),
+ ...modifier({ ...query }, modification),
...generateEmptyQuery(i),
}));
// Discard all ongoing transactions
@@ -285,7 +265,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
nextQueries = initialQueries.map((query, i) => {
// Synchronize all queries with local query cache to ensure consistency
// TODO still needed?
- return i === index ? { ...modifier(modifiedQueries[i], modification), ...generateEmptyQuery(i) } : query;
+ return i === index ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(i) } : query;
});
nextQueryTransactions = queryTransactions
// Consume the hint corresponding to the action
@@ -301,7 +281,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
return {
...state,
initialQueries: nextQueries,
- modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
},
@@ -347,11 +326,8 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
filter: removeQueryRowAction,
mapper: (state, action): ExploreItemState => {
const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
- let { modifiedQueries } = state;
const { index } = action.payload;
- modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
-
if (initialQueries.length <= 1) {
return state;
}
@@ -371,7 +347,6 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
...results,
initialQueries: nextQueries,
logsHighlighterExpressions: undefined,
- modifiedQueries: nextQueries.slice(),
queryTransactions: nextQueryTransactions,
};
},
@@ -412,7 +387,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
filter: setQueriesAction,
mapper: (state, action): ExploreItemState => {
const { queries } = action.payload;
- return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
+ return { ...state, initialQueries: queries.slice() };
},
})
.addMapper({
@@ -461,7 +436,7 @@ export const itemReducer = reducerFactory({} as ExploreItemSta
.addMapper({
filter: queriesImportedAction,
mapper: (state, action): ExploreItemState => {
- return { ...state, initialQueries: action.payload.queries, modifiedQueries: action.payload.queries.slice() };
+ return { ...state, initialQueries: action.payload.queries };
},
})
.create();
diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts
index 34b7ff08c99..92145dc2324 100644
--- a/public/app/types/explore.ts
+++ b/public/app/types/explore.ts
@@ -145,7 +145,7 @@ export interface ExploreItemState {
history: HistoryItem[];
/**
* Initial queries for this Explore, e.g., set via URL. Each query will be
- * converted to a query row. Query edits should be tracked in `modifiedQueries` though.
+ * converted to a query row.
*/
initialQueries: DataQuery[];
/**
@@ -162,12 +162,6 @@ export interface ExploreItemState {
* Log query result to be displayed in the logs result viewer.
*/
logsResult?: LogsModel;
- /**
- * Copy of `initialQueries` that tracks user edits.
- * Don't connect this property to a react component as it is updated on every query change.
- * Used when running queries. Needs to be reset to `initialQueries` when those are reset as well.
- */
- modifiedQueries: DataQuery[];
/**
* Query intervals for graph queries to determine how many datapoints to return.
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
From eb8dfefb231559acce4291a9d288d8d44fc02b50 Mon Sep 17 00:00:00 2001
From: Marcus Efraimsson
Date: Mon, 4 Feb 2019 11:16:11 +0100
Subject: [PATCH 054/770] changelog: add notes about closing #14231
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7164f5d99a9..1a19957b3ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### Minor
* **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae)
+* **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson)
# 6.0.0-beta1 (2019-01-30)
From 217468074f6f8f420554cbed203db97921324e17 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?=
Date: Mon, 4 Feb 2019 11:19:45 +0100
Subject: [PATCH 055/770] added submenu, made sure submenu visibility is always
up to date
---
.../app/features/annotations/editor_ctrl.ts | 7 +++-
.../DashLinks/DashLinksEditorCtrl.ts | 4 ++-
.../components/DashNav/DashNavCtrl.ts | 2 --
.../dashboard/components/SubMenu/SubMenu.tsx | 36 +++++++++++++++++++
.../dashboard/components/SubMenu/index.ts | 1 +
.../dashboard/containers/DashboardPage.tsx | 2 ++
.../dashboard/services/DashboardSrv.ts | 7 ++--
.../app/features/explore/ExploreToolbar.tsx | 4 +--
public/sass/components/_navbar.scss | 3 +-
9 files changed, 56 insertions(+), 10 deletions(-)
create mode 100644 public/app/features/dashboard/components/SubMenu/SubMenu.tsx
diff --git a/public/app/features/annotations/editor_ctrl.ts b/public/app/features/annotations/editor_ctrl.ts
index 18b00793ff8..c12e442f6d3 100644
--- a/public/app/features/annotations/editor_ctrl.ts
+++ b/public/app/features/annotations/editor_ctrl.ts
@@ -2,6 +2,7 @@ import angular from 'angular';
import _ from 'lodash';
import $ from 'jquery';
import coreModule from 'app/core/core_module';
+import { DashboardModel } from 'app/features/dashboard/state';
export class AnnotationsEditorCtrl {
mode: any;
@@ -10,6 +11,7 @@ export class AnnotationsEditorCtrl {
currentAnnotation: any;
currentDatasource: any;
currentIsNew: any;
+ dashboard: DashboardModel;
annotationDefaults: any = {
name: '',
@@ -26,9 +28,10 @@ export class AnnotationsEditorCtrl {
constructor($scope, private datasourceSrv) {
$scope.ctrl = this;
+ this.dashboard = $scope.dashboard;
this.mode = 'list';
this.datasources = datasourceSrv.getAnnotationSources();
- this.annotations = $scope.dashboard.annotations.list;
+ this.annotations = this.dashboard.annotations.list;
this.reset();
this.onColorChange = this.onColorChange.bind(this);
@@ -78,11 +81,13 @@ export class AnnotationsEditorCtrl {
this.annotations.push(this.currentAnnotation);
this.reset();
this.mode = 'list';
+ this.dashboard.updateSubmenuVisibility();
}
removeAnnotation(annotation) {
const index = _.indexOf(this.annotations, annotation);
this.annotations.splice(index, 1);
+ this.dashboard.updateSubmenuVisibility();
}
onColorChange(newColor) {
diff --git a/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
index 398ad757bf3..339c8e7de4c 100644
--- a/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
+++ b/public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
@@ -1,5 +1,6 @@
import angular from 'angular';
import _ from 'lodash';
+import { DashboardModel } from 'app/features/dashboard/state';
export let iconMap = {
'external link': 'fa-external-link',
@@ -12,7 +13,7 @@ export let iconMap = {
};
export class DashLinksEditorCtrl {
- dashboard: any;
+ dashboard: DashboardModel;
iconMap: any;
mode: any;
link: any;
@@ -40,6 +41,7 @@ export class DashLinksEditorCtrl {
addLink() {
this.dashboard.links.push(this.link);
this.mode = 'list';
+ this.dashboard.updateSubmenuVisibility();
}
editLink(link) {
diff --git a/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts b/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts
index e75c1468a1f..fbf84d354e3 100644
--- a/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts
+++ b/public/app/features/dashboard/components/DashNav/DashNavCtrl.ts
@@ -10,8 +10,6 @@ export class DashNavCtrl {
/** @ngInject */
constructor(private $scope, private dashboardSrv, private $location, public playlistSrv) {
- appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
-
if (this.dashboard.meta.isSnapshot) {
const meta = this.dashboard.meta;
this.titleTooltip = 'Created: ' + moment(meta.created).calendar();
diff --git a/public/app/features/dashboard/components/SubMenu/SubMenu.tsx b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx
new file mode 100644
index 00000000000..caef8f2de38
--- /dev/null
+++ b/public/app/features/dashboard/components/SubMenu/SubMenu.tsx
@@ -0,0 +1,36 @@
+// Libaries
+import React, { PureComponent } from 'react';
+
+// Utils & Services
+import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
+
+// Types
+import { DashboardModel } from '../../state/DashboardModel';
+
+export interface Props {
+ dashboard: DashboardModel | null;
+}
+
+export class SubMenu extends PureComponent {
+ element: HTMLElement;
+ angularCmp: AngularComponent;
+
+ componentDidMount() {
+ const loader = getAngularLoader();
+
+ const template = ' ';
+ const scopeProps = { dashboard: this.props.dashboard };
+
+ this.angularCmp = loader.load(this.element, scopeProps, template);
+ }
+
+ componentWillUnmount() {
+ if (this.angularCmp) {
+ this.angularCmp.destroy();
+ }
+ }
+
+ render() {
+ return this.element = element} />;
+ }
+}
diff --git a/public/app/features/dashboard/components/SubMenu/index.ts b/public/app/features/dashboard/components/SubMenu/index.ts
index 1790aa66782..ca113ab75d6 100644
--- a/public/app/features/dashboard/components/SubMenu/index.ts
+++ b/public/app/features/dashboard/components/SubMenu/index.ts
@@ -1 +1,2 @@
export { SubMenuCtrl } from './SubMenuCtrl';
+export { SubMenu } from './SubMenu';
diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx
index be12657a829..e01998f65a9 100644
--- a/public/app/features/dashboard/containers/DashboardPage.tsx
+++ b/public/app/features/dashboard/containers/DashboardPage.tsx
@@ -12,6 +12,7 @@ import { createErrorNotification } from 'app/core/copy/appNotification';
import { LoadingPlaceholder } from '@grafana/ui';
import { DashboardGrid } from '../dashgrid/DashboardGrid';
import { DashNav } from '../components/DashNav';
+import { SubMenu } from '../components/SubMenu';
import { DashboardSettings } from '../components/DashboardSettings';
// Redux
@@ -192,6 +193,7 @@ export class DashboardPage extends PureComponent
{
{dashboard && editview && }
+ {dashboard.meta.submenuEnabled && }
diff --git a/public/app/features/dashboard/services/DashboardSrv.ts b/public/app/features/dashboard/services/DashboardSrv.ts
index 03aeb34ed36..7d3dfb68cd8 100644
--- a/public/app/features/dashboard/services/DashboardSrv.ts
+++ b/public/app/features/dashboard/services/DashboardSrv.ts
@@ -1,12 +1,15 @@
import coreModule from 'app/core/core_module';
-import { DashboardModel } from '../state/DashboardModel';
+import { appEvents } from 'app/core/app_events';
import locationUtil from 'app/core/utils/location_util';
+import { DashboardModel } from '../state/DashboardModel';
export class DashboardSrv {
dash: any;
/** @ngInject */
- constructor(private backendSrv, private $rootScope, private $location) {}
+ constructor(private backendSrv, private $rootScope, private $location) {
+ appEvents.on('save-dashboard', this.saveDashboard.bind(this), $rootScope);
+ }
create(dashboard, meta) {
return new DashboardModel(dashboard, meta);
diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx
index 35f06d11c81..228cfb147e8 100644
--- a/public/app/features/explore/ExploreToolbar.tsx
+++ b/public/app/features/explore/ExploreToolbar.tsx
@@ -97,10 +97,10 @@ export class UnConnectedExploreToolbar extends PureComponent {
diff --git a/public/sass/components/_navbar.scss b/public/sass/components/_navbar.scss
index 0744ed0dfc7..0cfa314a985 100644
--- a/public/sass/components/_navbar.scss
+++ b/public/sass/components/_navbar.scss
@@ -83,8 +83,7 @@
font-size: 19px;
line-height: 8px;
opacity: 0.75;
- margin-right: 8px;
- // icon hidden on smaller screens
+ margin-right: 13px;
display: none;
}
From 5e2b9e40a2f6a858bef3ba9ccabc7fff6d96c47d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Hugo=20H=C3=A4ggmark?=
Date: Mon, 4 Feb 2019 11:25:07 +0100
Subject: [PATCH 056/770] Added more typings
---
packages/grafana-ui/src/types/plugin.ts | 10 ++++++----
public/app/features/explore/Explore.tsx | 6 +++---
.../datasource/loki/components/LokiStartPage.tsx | 7 ++-----
.../datasource/prometheus/components/PromStart.tsx | 7 ++-----
public/app/types/explore.ts | 13 +++++++++++--
5 files changed, 24 insertions(+), 19 deletions(-)
diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts
index e951e91a223..e674c9fbc32 100644
--- a/packages/grafana-ui/src/types/plugin.ts
+++ b/packages/grafana-ui/src/types/plugin.ts
@@ -65,15 +65,19 @@ export interface ExploreQueryFieldProps