Merge remote-tracking branch 'grafana/master' into alpha-text2

* grafana/master:
  Explore: Enable click on name label
  Bumping grafana ui version (#15669)
  Need this to be available for plugins
  docs: 6.0 whats new
  Updated latest.json with 6.0
  Update CHANGELOG.md
  docs: grafana 6.0 has been released.
  moves social package to /login
  moves tracing packge into /infra
  Update CHANGELOG.md
  changelog: adds notes for #14509 and #15179
  graph: fixes click after scroll in series override menu
  moves metric package to /infra
  stackdriver: change reducer mapping for distribution metrics
  stackdriver: fix for float64 bounds for distribution metrics
  Fixed value dropdown not updating when it's current value updates, fixes #15566
This commit is contained in:
ryan 2019-02-28 10:07:53 -08:00
commit d3cbc5638c
63 changed files with 685 additions and 250 deletions

View File

@ -1,4 +1,10 @@
# Unreleased # 6.0.0 stable (2019-02-25)
### Bug Fixes
* **Stackdriver**: fix for float64 bounds for distribution metrics [#14509](https://github.com/grafana/grafana/issues/14509)
* **Stackdriver**: no reducers available for distribution type [#15179](https://github.com/grafana/grafana/issues/15179)
* **Dashboard**: fixes click after scroll in series override menu [#15621](https://github.com/grafana/grafana/issues/15621)
* **MySQL**: fix mysql query using _interval_ms variable throws error [#14507](https://github.com/grafana/grafana/issues/14507)
# 6.0.0-beta3 (2019-02-19) # 6.0.0-beta3 (2019-02-19)

View File

@ -14,8 +14,6 @@ weight = -11
This update to Grafana introduces a new way of exploring your data, support for log data and tons of other features. This update to Grafana introduces a new way of exploring your data, support for log data and tons of other features.
Grafana v6.0 is out in **Beta**, [Download Now!](https://grafana.com/grafana/download/beta)
The main highlights are: The main highlights are:
- [Explore]({{< relref "#explore" >}}) - A new query focused workflow for ad-hoc data exploration and troubleshooting. - [Explore]({{< relref "#explore" >}}) - A new query focused workflow for ad-hoc data exploration and troubleshooting.
@ -107,9 +105,9 @@ continue to refine and start using in other panels.
### React Panels & Query Editors ### React Panels & Query Editors
A major part of all the work that has gone into Grafana v6.0 has been on the migration to React. This investment A major part of all the work that has gone into Grafana v6.0 has been on the migration to React. This investment
is part of the future proofing of Grafana and it's code base and ecosystem. Starting in v6.0 **Panels** and **Data is part of the future proofing of Grafana's code base and ecosystem. Starting in v6.0 **Panels** and **Data
source** plugins can be written in React using our published `@grafana/ui` sdk library. More information on this source** plugins can be written in React using our published `@grafana/ui` sdk library. More information on this
will be shared closer to or just after release. will be shared soon.
{{< docs-imagebox img="/img/docs/v60/react_panels.png" max-width="600px" caption="React Panel" >}} {{< docs-imagebox img="/img/docs/v60/react_panels.png" max-width="600px" caption="React Panel" >}}
<br /> <br />
@ -122,7 +120,7 @@ To get started read the guide: [Using Google Stackdriver in Grafana](/features/d
## Azure Monitor Datasource ## Azure Monitor Datasource
One of the goals of the Grafana v6.0 release is to add support for the three major clouds. Amazon Cloudwatch has been a core datasource for years and Google Stackdriver is also now supported. We developed an external plugin for Azure Monitor last year and for this release the [plugin](https://grafana.com/plugins/grafana-azure-monitor-datasource) is being moved into Grafana to be one of the built-in datasources. For users of the external plugin, Grafana will automatically start using the built-in version. As a core datasource, the Azure Monitor datasource will get alerting support for the official 6.0 release. One of the goals of the Grafana v6.0 release is to add support for the three major clouds. Amazon Cloudwatch has been a core datasource for years and Google Stackdriver is also now supported. We developed an external plugin for Azure Monitor last year and for this release the [plugin](https://grafana.com/plugins/grafana-azure-monitor-datasource) is being moved into Grafana to be one of the built-in datasources. For users of the external plugin, Grafana will automatically start using the built-in version. As a core datasource, the Azure Monitor datasource is able to get alerting support, in the 6.0 release alerting is supported for the Azure Monitor service, with the rest to follow.
The Azure Monitor datasource integrates four Azure services with Grafana - Azure Monitor, Azure Log Analytics, Azure Application Insights and Azure Application Insights Analytics. The Azure Monitor datasource integrates four Azure services with Grafana - Azure Monitor, Azure Log Analytics, Azure Application Insights and Azure Application Insights Analytics.

View File

@ -1,4 +1,4 @@
{ {
"stable": "5.4.3", "stable": "6.0.0",
"testing": "5.4.3" "testing": "6.0.0"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@grafana/ui", "name": "@grafana/ui",
"version": "6.0.0-alpha.0", "version": "6.0.1-alpha.0",
"description": "Grafana Components Library", "description": "Grafana Components Library",
"keywords": [ "keywords": [
"typescript", "typescript",

View File

@ -2,3 +2,4 @@ export * from './processTimeSeries';
export * from './valueFormats/valueFormats'; export * from './valueFormats/valueFormats';
export * from './colors'; export * from './colors';
export * from './namedColorsPalette'; export * from './namedColorsPalette';
export { getMappedValue } from './valueMappings';

View File

@ -3,7 +3,7 @@ package api
import ( import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )

View File

@ -13,8 +13,8 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/dashdiffs" "github.com/grafana/grafana/pkg/components/dashdiffs"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"

View File

@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"

View File

@ -2,7 +2,7 @@ package api
import ( import (
"github.com/grafana/grafana/pkg/api/pluginproxy" "github.com/grafana/grafana/pkg/api/pluginproxy"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
) )

View File

@ -7,9 +7,9 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"

View File

@ -16,12 +16,12 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/login/social"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/social"
) )
var ( var (

View File

@ -3,7 +3,7 @@ package api
import ( import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"

View File

@ -6,7 +6,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"

View File

@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
) )

View File

@ -4,7 +4,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"

View File

@ -14,8 +14,8 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
_ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/conditions"
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"

View File

@ -16,9 +16,9 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/social"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -28,8 +28,9 @@ import (
// self registering services // self registering services
_ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/extensions"
_ "github.com/grafana/grafana/pkg/infra/metrics"
_ "github.com/grafana/grafana/pkg/infra/serverlock" _ "github.com/grafana/grafana/pkg/infra/serverlock"
_ "github.com/grafana/grafana/pkg/metrics" _ "github.com/grafana/grafana/pkg/infra/tracing"
_ "github.com/grafana/grafana/pkg/plugins" _ "github.com/grafana/grafana/pkg/plugins"
_ "github.com/grafana/grafana/pkg/services/alerting" _ "github.com/grafana/grafana/pkg/services/alerting"
_ "github.com/grafana/grafana/pkg/services/auth" _ "github.com/grafana/grafana/pkg/services/auth"
@ -39,7 +40,6 @@ import (
_ "github.com/grafana/grafana/pkg/services/rendering" _ "github.com/grafana/grafana/pkg/services/rendering"
_ "github.com/grafana/grafana/pkg/services/search" _ "github.com/grafana/grafana/pkg/services/search"
_ "github.com/grafana/grafana/pkg/services/sqlstore" _ "github.com/grafana/grafana/pkg/services/sqlstore"
_ "github.com/grafana/grafana/pkg/tracing"
) )
func NewGrafanaServer() *GrafanaServerImpl { func NewGrafanaServer() *GrafanaServerImpl {

View File

@ -3,8 +3,8 @@ package metrics
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/infra/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/grafana/grafana/pkg/metrics/graphitebridge" "github.com/grafana/grafana/pkg/infra/metrics/graphitebridge"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )

View File

@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/social"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
) )

View File

@ -5,8 +5,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
) )
type DefaultEvalHandler struct { type DefaultEvalHandler struct {

View File

@ -7,8 +7,8 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/imguploader" "github.com/grafana/grafana/pkg/components/imguploader"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"

View File

@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )

View File

@ -5,8 +5,8 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"

View File

@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"

View File

@ -7,7 +7,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )

View File

@ -24,7 +24,7 @@ import (
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
) )
type CloudWatchExecutor struct { type CloudWatchExecutor struct {

View File

@ -17,7 +17,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
) )

View File

@ -336,6 +336,8 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver
return StackdriverResponse{}, err return StackdriverResponse{}, err
} }
// slog.Info("stackdriver", "response", string(body))
if res.StatusCode/100 != 2 { if res.StatusCode/100 != 2 {
slog.Error("Request failed", "status", res.Status, "body", string(body)) slog.Error("Request failed", "status", res.Status, "body", string(body))
return StackdriverResponse{}, fmt.Errorf(string(body)) return StackdriverResponse{}, fmt.Errorf(string(body))
@ -559,7 +561,7 @@ func calcBucketBound(bucketOptions StackdriverBucketOptions, n int) string {
} else if bucketOptions.ExponentialBuckets != nil { } else if bucketOptions.ExponentialBuckets != nil {
bucketBound = strconv.FormatInt(int64(bucketOptions.ExponentialBuckets.Scale*math.Pow(bucketOptions.ExponentialBuckets.GrowthFactor, float64(n-1))), 10) bucketBound = strconv.FormatInt(int64(bucketOptions.ExponentialBuckets.Scale*math.Pow(bucketOptions.ExponentialBuckets.GrowthFactor, float64(n-1))), 10)
} else if bucketOptions.ExplicitBuckets != nil { } else if bucketOptions.ExplicitBuckets != nil {
bucketBound = strconv.FormatInt(bucketOptions.ExplicitBuckets.Bounds[(n-1)], 10) bucketBound = fmt.Sprintf("%g", bucketOptions.ExplicitBuckets.Bounds[n])
} }
return bucketBound return bucketBound
} }

View File

@ -344,8 +344,8 @@ func TestStackdriver(t *testing.T) {
}) })
}) })
Convey("when data from query is distribution", func() { Convey("when data from query is distribution with exponential bounds", func() {
data, err := loadTestFile("./test-data/3-series-response-distribution.json") data, err := loadTestFile("./test-data/3-series-response-distribution-exponential.json")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(data.TimeSeries), ShouldEqual, 1) So(len(data.TimeSeries), ShouldEqual, 1)
@ -370,6 +370,14 @@ func TestStackdriver(t *testing.T) {
So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1536669060000) So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1536669060000)
}) })
Convey("bucket bounds should be correct", func() {
So(res.Series[0].Name, ShouldEqual, "0")
So(res.Series[1].Name, ShouldEqual, "1")
So(res.Series[2].Name, ShouldEqual, "2")
So(res.Series[3].Name, ShouldEqual, "4")
So(res.Series[4].Name, ShouldEqual, "8")
})
Convey("value should be correct", func() { Convey("value should be correct", func() {
So(res.Series[8].Points[0][0].Float64, ShouldEqual, 1) So(res.Series[8].Points[0][0].Float64, ShouldEqual, 1)
So(res.Series[9].Points[0][0].Float64, ShouldEqual, 1) So(res.Series[9].Points[0][0].Float64, ShouldEqual, 1)
@ -383,6 +391,45 @@ func TestStackdriver(t *testing.T) {
}) })
}) })
Convey("when data from query is distribution with explicit bounds", func() {
data, err := loadTestFile("./test-data/4-series-response-distribution-explicit.json")
So(err, ShouldBeNil)
So(len(data.TimeSeries), ShouldEqual, 1)
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
query := &StackdriverQuery{AliasBy: "{{bucket}}"}
err = executor.parseResponse(res, data, query)
So(err, ShouldBeNil)
So(len(res.Series), ShouldEqual, 33)
for i := 0; i < 33; i++ {
if i == 0 {
So(res.Series[i].Name, ShouldEqual, "0")
}
So(len(res.Series[i].Points), ShouldEqual, 2)
}
Convey("timestamps should be in ascending order", func() {
So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1550859086000)
So(res.Series[0].Points[1][1].Float64, ShouldEqual, 1550859146000)
})
Convey("bucket bounds should be correct", func() {
So(res.Series[0].Name, ShouldEqual, "0")
So(res.Series[1].Name, ShouldEqual, "0.01")
So(res.Series[2].Name, ShouldEqual, "0.05")
So(res.Series[3].Name, ShouldEqual, "0.1")
})
Convey("value should be correct", func() {
So(res.Series[8].Points[0][0].Float64, ShouldEqual, 381)
So(res.Series[9].Points[0][0].Float64, ShouldEqual, 212)
So(res.Series[10].Points[0][0].Float64, ShouldEqual, 56)
So(res.Series[8].Points[1][0].Float64, ShouldEqual, 375)
So(res.Series[9].Points[1][0].Float64, ShouldEqual, 213)
So(res.Series[10].Points[1][0].Float64, ShouldEqual, 56)
})
})
}) })
Convey("when interpolating filter wildcards", func() { Convey("when interpolating filter wildcards", func() {

View File

@ -0,0 +1,209 @@
{
"timeSeries": [
{
"metric": {
"type": "custom.googleapis.com\/opencensus\/grpc.io\/client\/roundtrip_latency"
},
"resource": {
"type": "global",
"labels": {
"project_id": "grafana-demo"
}
},
"metricKind": "DELTA",
"valueType": "DISTRIBUTION",
"points": [
{
"interval": {
"startTime": "2019-02-22T18:11:26Z",
"endTime": "2019-02-22T18:12:26Z"
},
"value": {
"distributionValue": {
"count": "1878",
"mean": 17.813718392255,
"sumOfSquaredDeviation": 7141630.651914,
"bucketOptions": {
"explicitBuckets": {
"bounds": [
0,
0.01,
0.05,
0.1,
0.3,
0.6,
0.8,
1,
2,
3,
4,
5,
6,
8,
10,
13,
16,
20,
25,
30,
40,
50,
65,
80,
100,
130,
160,
200,
250,
300,
400,
500,
650,
800,
1000,
2000,
5000,
10000,
20000,
50000,
100000
]
}
},
"bucketCounts": [
"0",
"0",
"0",
"0",
"8",
"403",
"297",
"184",
"375",
"213",
"56",
"31",
"15",
"13",
"4",
"1",
"5",
"2",
"8",
"13",
"26",
"13",
"45",
"48",
"61",
"10",
"3",
"6",
"7",
"4",
"7",
"12",
"8"
]
}
}
},
{
"interval": {
"startTime": "2019-02-22T18:10:26Z",
"endTime": "2019-02-22T18:11:26Z"
},
"value": {
"distributionValue": {
"count": "1887",
"mean": 17.654277577766,
"sumOfSquaredDeviation": 7082587.2133073,
"bucketOptions": {
"explicitBuckets": {
"bounds": [
0,
0.01,
0.05,
0.1,
0.3,
0.6,
0.8,
1,
2,
3,
4,
5,
6,
8,
10,
13,
16,
20,
25,
30,
40,
50,
65,
80,
100,
130,
160,
200,
250,
300,
400,
500,
650,
800,
1000,
2000,
5000,
10000,
20000,
50000,
100000
]
}
},
"bucketCounts": [
"0",
"0",
"0",
"0",
"8",
"404",
"298",
"187",
"381",
"212",
"56",
"31",
"15",
"14",
"4",
"1",
"4",
"2",
"9",
"13",
"24",
"13",
"46",
"46",
"61",
"11",
"3",
"6",
"7",
"5",
"7",
"11",
"8"
]
}
}
}
]
}
]
}

View File

@ -26,7 +26,7 @@ type StackdriverBucketOptions struct {
Scale float64 `json:"scale"` Scale float64 `json:"scale"`
} `json:"exponentialBuckets"` } `json:"exponentialBuckets"`
ExplicitBuckets *struct { ExplicitBuckets *struct {
Bounds []int64 `json:"bounds"` Bounds []float64 `json:"bounds"`
} `json:"explicitBuckets"` } `json:"explicitBuckets"`
} }

View File

@ -128,7 +128,7 @@ export function dropdownTypeahead2($compile) {
'<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>'; '<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
const buttonTemplate = const buttonTemplate =
'<a class="gf-form-input dropdown-toggle"' + '<a class="{{buttonTemplateClass}} dropdown-toggle"' +
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' + ' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
' ><i class="fa fa-plus"></i></a>'; ' ><i class="fa fa-plus"></i></a>';
@ -137,9 +137,15 @@ export function dropdownTypeahead2($compile) {
menuItems: '=dropdownTypeahead2', menuItems: '=dropdownTypeahead2',
dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect', dropdownTypeaheadOnSelect: '&dropdownTypeaheadOnSelect',
model: '=ngModel', model: '=ngModel',
buttonTemplateClass: '@',
}, },
link: ($scope, elem, attrs) => { link: ($scope, elem, attrs) => {
const $input = $(inputTemplate); const $input = $(inputTemplate);
if (!$scope.buttonTemplateClass) {
$scope.buttonTemplateClass = 'gf-form-input';
}
const $button = $(buttonTemplate); const $button = $(buttonTemplate);
const timeoutId = { const timeoutId = {
blur: null, blur: null,

View File

@ -240,7 +240,7 @@ export class ValueSelectDropdownCtrl {
/** @ngInject */ /** @ngInject */
export function valueSelectDropdown($compile, $window, $timeout, $rootScope) { export function valueSelectDropdown($compile, $window, $timeout, $rootScope) {
return { return {
scope: { variable: '=', onUpdated: '&' }, scope: { dashboard: '=', variable: '=', onUpdated: '&' },
templateUrl: 'public/app/partials/valueSelectDropdown.html', templateUrl: 'public/app/partials/valueSelectDropdown.html',
controller: 'ValueSelectDropdownCtrl', controller: 'ValueSelectDropdownCtrl',
controllerAs: 'vm', controllerAs: 'vm',
@ -288,13 +288,13 @@ export function valueSelectDropdown($compile, $window, $timeout, $rootScope) {
} }
}); });
const cleanUp = $rootScope.$on('template-variable-value-updated', () => { scope.vm.dashboard.on(
scope.vm.updateLinkText(); 'template-variable-value-updated',
}); () => {
scope.vm.updateLinkText();
scope.$on('$destroy', () => { },
cleanUp(); scope
}); );
scope.vm.init(); scope.vm.init();
}, },

View File

@ -4,7 +4,7 @@
<label class="gf-form-label template-variable" ng-hide="variable.hide === 1"> <label class="gf-form-label template-variable" ng-hide="variable.hide === 1">
{{variable.label || variable.name}} {{variable.label || variable.name}}
</label> </label>
<value-select-dropdown ng-if="variable.type !== 'adhoc' && variable.type !== 'textbox'" variable="variable" on-updated="ctrl.variableUpdated(variable)"></value-select-dropdown> <value-select-dropdown ng-if="variable.type !== 'adhoc' && variable.type !== 'textbox'" dashboard="ctrl.dashboard" variable="variable" on-updated="ctrl.variableUpdated(variable)"></value-select-dropdown>
<input type="text" ng-if="variable.type === 'textbox'" ng-model="variable.query" class="gf-form-input width-12" ng-blur="variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ng-keydown="$event.keyCode === 13 && variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ></input> <input type="text" ng-if="variable.type === 'textbox'" ng-model="variable.query" class="gf-form-input width-12" ng-blur="variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ng-keydown="$event.keyCode === 13 && variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ></input>
</div> </div>
<ad-hoc-filters ng-if="variable.type === 'adhoc'" variable="variable" dashboard="ctrl.dashboard"></ad-hoc-filters> <ad-hoc-filters ng-if="variable.type === 'adhoc'" variable="variable" dashboard="ctrl.dashboard"></ad-hoc-filters>

View File

@ -1,19 +1,38 @@
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true"> <query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<div ng-if="ctrl.target.rawQuery">
<div ng-if="ctrl.target.rawQuery">
<div class="gf-form"> <div class="gf-form">
<textarea rows="3" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" placeholder="InfluxDB Query" ng-model-onblur ng-change="ctrl.refresh()"></textarea> <textarea
rows="3"
class="gf-form-input"
ng-model="ctrl.target.query"
spellcheck="false"
placeholder="InfluxDB Query"
ng-model-onblur
ng-change="ctrl.refresh()"
></textarea>
</div> </div>
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword">FORMAT AS</label> <label class="gf-form-label query-keyword">FORMAT AS</label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select> <select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"
></select>
</div> </div>
</div> </div>
<div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'"> <div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'">
<label class="gf-form-label query-keyword">ALIAS BY</label> <label class="gf-form-label query-keyword">ALIAS BY</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()"> <input
type="text"
class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck="false"
placeholder="Naming pattern"
ng-blur="ctrl.refresh()"
/>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
@ -21,108 +40,154 @@
</div> </div>
</div> </div>
<div ng-if="!ctrl.target.rawQuery"> <div ng-if="!ctrl.target.rawQuery">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">FROM</label>
<div class="gf-form-inline"> <metric-segment
<div class="gf-form"> segment="ctrl.policySegment"
<label class="gf-form-label query-keyword width-7">FROM</label> get-options="ctrl.getPolicySegments()"
on-change="ctrl.policyChanged()"
></metric-segment>
<metric-segment
segment="ctrl.measurementSegment"
get-options="ctrl.getMeasurements($query)"
on-change="ctrl.measurementChanged()"
></metric-segment>
</div>
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment> <div class="gf-form">
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements($query)" on-change="ctrl.measurementChanged()"></metric-segment> <label class="gf-form-label query-keyword">WHERE</label>
</div> </div>
<div class="gf-form"> <div class="gf-form" ng-repeat="segment in ctrl.tagSegments">
<label class="gf-form-label query-keyword">WHERE</label> <metric-segment
</div> segment="segment"
get-options="ctrl.getTagsOrValues(segment, $index)"
on-change="ctrl.tagSegmentUpdated(segment, $index)"
></metric-segment>
</div>
<div class="gf-form" ng-repeat="segment in ctrl.tagSegments"> <div class="gf-form gf-form--grow">
<metric-segment segment="segment" get-options="ctrl.getTagsOrValues(segment, $index)" on-change="ctrl.tagSegmentUpdated(segment, $index)"></metric-segment> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div>
<div class="gf-form gf-form--grow"> <div class="gf-form-inline" ng-repeat="selectParts in ctrl.queryModel.selectModels">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form">
</div> <label class="gf-form-label query-keyword width-7"> <span ng-show="$index === 0">SELECT</span>&nbsp; </label>
</div> </div>
<div class="gf-form-inline" ng-repeat="selectParts in ctrl.queryModel.selectModels"> <div class="gf-form" ng-repeat="part in selectParts">
<div class="gf-form"> <query-part-editor
<label class="gf-form-label query-keyword width-7"> class="gf-form-label query-part"
<span ng-show="$index === 0">SELECT</span>&nbsp; part="part"
</label> handle-event="ctrl.handleSelectPartEvent(selectParts, part, $event)"
</div> >
</query-part-editor>
</div>
<div class="gf-form" ng-repeat="part in selectParts"> <div class="gf-form">
<query-part-editor class="gf-form-label query-part" part="part" handle-event="ctrl.handleSelectPartEvent(selectParts, part, $event)"> <label
</query-part-editor> class="dropdown"
</div> dropdown-typeahead2="ctrl.selectMenu"
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"
button-template-class="gf-form-label query-part"
>
</label>
</div>
<div class="gf-form"> <div class="gf-form gf-form--grow">
<label class="dropdown" <div class="gf-form-label gf-form-label--grow"></div>
dropdown-typeahead="ctrl.selectMenu" </div>
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"> </div>
</label>
</div>
<div class="gf-form gf-form--grow"> <div class="gf-form-inline">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form">
</div> <label class="gf-form-label query-keyword width-7">
</div> <span>GROUP BY</span>
</label>
<div class="gf-form-inline"> <query-part-editor
<div class="gf-form"> ng-repeat="part in ctrl.queryModel.groupByParts"
<label class="gf-form-label query-keyword width-7"> part="part"
<span>GROUP BY</span> class="gf-form-label query-part"
</label> handle-event="ctrl.handleGroupByPartEvent(part, $index, $event)"
>
</query-part-editor>
</div>
<query-part-editor ng-repeat="part in ctrl.queryModel.groupByParts" <div class="gf-form">
part="part" class="gf-form-label query-part" <metric-segment
handle-event="ctrl.handleGroupByPartEvent(part, $index, $event)"> segment="ctrl.groupBySegment"
</query-part-editor> get-options="ctrl.getGroupByOptions()"
</div> on-change="ctrl.groupByAction(part, $index)"
></metric-segment>
</div>
<div class="gf-form"> <div class="gf-form gf-form--grow">
<metric-segment segment="ctrl.groupBySegment" get-options="ctrl.getGroupByOptions()" on-change="ctrl.groupByAction(part, $index)"></metric-segment> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.target.orderByTime === 'DESC'"> <div class="gf-form-inline" ng-if="ctrl.target.orderByTime === 'DESC'">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-7">ORDER BY</label> <label class="gf-form-label query-keyword width-7">ORDER BY</label>
<label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span class="query-keyword">DESC</span> <i class="fa fa-remove"></i></label> <label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()"
>time <span class="query-keyword">DESC</span> <i class="fa fa-remove"></i
></label>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
<div class="gf-form-inline" ng-if="ctrl.target.limit"> <div class="gf-form-inline" ng-if="ctrl.target.limit">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-7">LIMIT</label> <label class="gf-form-label query-keyword width-7">LIMIT</label>
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.limit" spellcheck='false' placeholder="No Limit" ng-blur="ctrl.refresh()"> <input
type="text"
class="gf-form-input width-9"
ng-model="ctrl.target.limit"
spellcheck="false"
placeholder="No Limit"
ng-blur="ctrl.refresh()"
/>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
<div class="gf-form-inline" ng-if="ctrl.target.slimit"> <div class="gf-form-inline" ng-if="ctrl.target.slimit">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-7">SLIMIT</label> <label class="gf-form-label query-keyword width-7">SLIMIT</label>
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.slimit" spellcheck='false' placeholder="No Limit" ng-blur="ctrl.refresh()"> <input
type="text"
class="gf-form-input width-9"
ng-model="ctrl.target.slimit"
spellcheck="false"
placeholder="No Limit"
ng-blur="ctrl.refresh()"
/>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
<div class="gf-form-inline" ng-if="ctrl.target.tz"> <div class="gf-form-inline" ng-if="ctrl.target.tz">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-7">tz</label> <label class="gf-form-label query-keyword width-7">tz</label>
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.tz" spellcheck='false' placeholder="No Timezone" ng-blur="ctrl.refresh()"> <input
type="text"
class="gf-form-input width-9"
ng-model="ctrl.target.tz"
spellcheck="false"
placeholder="No Timezone"
ng-blur="ctrl.refresh()"
/>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
@ -133,7 +198,12 @@
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label query-keyword width-7">FORMAT AS</label> <label class="gf-form-label query-keyword width-7">FORMAT AS</label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select> <select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"
></select>
</div> </div>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
@ -141,15 +211,21 @@
</div> </div>
</div> </div>
<div class="gf-form-inline" ng-hide="ctrl.target.resultFormat === 'table'"> <div class="gf-form-inline" ng-hide="ctrl.target.resultFormat === 'table'">
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
<label class="gf-form-label query-keyword width-7">ALIAS BY</label> <label class="gf-form-label query-keyword width-7">ALIAS BY</label>
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()"> <input
type="text"
class="gf-form-input"
ng-model="ctrl.target.alias"
spellcheck="false"
placeholder="Naming pattern"
ng-blur="ctrl.refresh()"
/>
</div> </div>
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div> <div class="gf-form-label gf-form-label--grow"></div>
</div> </div>
</div> </div>
</div> </div>
</query-editor-row> </query-editor-row>

View File

@ -45,8 +45,10 @@
<div class="gf-form"> <div class="gf-form">
<label class="dropdown" <label class="dropdown"
dropdown-typeahead="ctrl.selectMenu" dropdown-typeahead2="ctrl.selectMenu"
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"> dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"
button-template-class="gf-form-label query-part"
>
</label> </label>
</div> </div>

View File

@ -45,8 +45,10 @@
<div class="gf-form"> <div class="gf-form">
<label class="dropdown" <label class="dropdown"
dropdown-typeahead="ctrl.selectMenu" dropdown-typeahead2="ctrl.selectMenu"
dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"> dropdown-typeahead-on-select="ctrl.addSelectPart(selectParts, $item, $subItem)"
button-template-class="gf-form-label query-part"
>
</label> </label>
</div> </div>

View File

@ -100,7 +100,7 @@ export class ResultTransformer {
table.columns.push({ text: 'Time', type: 'time' }); table.columns.push({ text: 'Time', type: 'time' });
_.each(sortedLabels, (label, labelIndex) => { _.each(sortedLabels, (label, labelIndex) => {
metricLabels[label] = labelIndex + 1; metricLabels[label] = labelIndex + 1;
table.columns.push({ text: label, filterable: !label.startsWith('__') }); table.columns.push({ text: label, filterable: true });
}); });
const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value'; const valueText = resultCount > 1 || valueWithRefId ? `Value #${refId}` : 'Value';
table.columns.push({ text: valueText }); table.columns.push({ text: valueText });

View File

@ -66,11 +66,12 @@ describe('Prometheus Result Transformer', () => {
]); ]);
expect(table.columns).toMatchObject([ expect(table.columns).toMatchObject([
{ text: 'Time', type: 'time' }, { text: 'Time', type: 'time' },
{ text: '__name__' }, { text: '__name__', filterable: true },
{ text: 'instance' }, { text: 'instance', filterable: true },
{ text: 'job' }, { text: 'job' },
{ text: 'Value' }, { text: 'Value' },
]); ]);
expect(table.columns[4].filterable).toBeUndefined();
}); });
it('should column title include refId if response count is more than 2', () => { it('should column title include refId if response count is more than 2', () => {

View File

@ -49,7 +49,8 @@ describe('Aggregations', () => {
}); });
it('', () => { it('', () => {
const options = wrapper.state().aggOptions[0].options; const options = wrapper.state().aggOptions[0].options;
expect(options.length).toEqual(5);
expect(options.length).toEqual(10);
expect(options.map(o => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE'])); expect(options.map(o => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE']));
}); });
}); });

View File

@ -73,7 +73,7 @@ export class Aggregations extends React.Component<Props, State> {
value={crossSeriesReducer} value={crossSeriesReducer}
variables={templateSrv.variables} variables={templateSrv.variables}
options={aggOptions} options={aggOptions}
placeholder="Select Aggregation" placeholder="Select Reducer"
className="width-15" className="width-15"
/> />
</div> </div>

View File

@ -185,7 +185,7 @@ export class Metrics extends React.Component<Props, State> {
}, },
]} ]}
placeholder="Select Metric" placeholder="Select Metric"
className="width-15" className="width-26"
/> />
</div> </div>
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">

View File

@ -28,7 +28,7 @@ Array [
<div <div
className="css-0 gf-form-select-box__placeholder" className="css-0 gf-form-select-box__placeholder"
> >
Select Aggregation Select Reducer
</div> </div>
<div <div
className="css-0" className="css-0"

View File

@ -72,7 +72,7 @@ Array [
Metric Metric
</span> </span>
<div <div
className="css-0 gf-form-input gf-form-input--form-dropdown width-15" className="css-0 gf-form-input gf-form-input--form-dropdown width-26"
onKeyDown={[Function]} onKeyDown={[Function]}
> >
<div <div
@ -196,7 +196,7 @@ Array [
<div <div
className="css-0 gf-form-select-box__placeholder" className="css-0 gf-form-select-box__placeholder"
> >
Select Aggregation Select Reducer
</div> </div>
<div <div
className="css-0" className="css-0"

View File

@ -189,7 +189,7 @@ export const aggOptions = [
ValueTypes.BOOL, ValueTypes.BOOL,
ValueTypes.STRING, ValueTypes.STRING,
], ],
metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE],
}, },
{ {
text: 'count true', text: 'count true',
@ -207,25 +207,25 @@ export const aggOptions = [
text: '99th percentile', text: '99th percentile',
value: 'REDUCE_PERCENTILE_99', value: 'REDUCE_PERCENTILE_99',
valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE],
}, },
{ {
text: '95th percentile', text: '95th percentile',
value: 'REDUCE_PERCENTILE_95', value: 'REDUCE_PERCENTILE_95',
valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE],
}, },
{ {
text: '50th percentile', text: '50th percentile',
value: 'REDUCE_PERCENTILE_50', value: 'REDUCE_PERCENTILE_50',
valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE],
}, },
{ {
text: '5th percentile', text: '5th percentile',
value: 'REDUCE_PERCENTILE_05', value: 'REDUCE_PERCENTILE_05',
valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION], valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
metricKinds: [MetricKind.GAUGE, MetricKind.DELTA], metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE],
}, },
]; ];

View File

@ -11,7 +11,7 @@ export function SeriesOverridesCtrl($scope, $element, popoverSrv) {
const option = { const option = {
text: name, text: name,
propertyName: propertyName, propertyName: propertyName,
index: $scope.overrideMenu.lenght, index: $scope.overrideMenu.length,
values: values, values: values,
submenu: _.map(values, value => { submenu: _.map(values, value => {
return { text: String(value), value: value }; return { text: String(value), value: value };

View File

@ -1,110 +1,194 @@
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">Draw Modes</h5>
<gf-form-switch
class="gf-form"
label="Bars"
label-class="width-5"
checked="ctrl.panel.bars"
on-change="ctrl.render()"
></gf-form-switch>
<gf-form-switch
class="gf-form"
label="Lines"
label-class="width-5"
checked="ctrl.panel.lines"
on-change="ctrl.render()"
></gf-form-switch>
<gf-form-switch
class="gf-form"
label="Points"
label-class="width-5"
checked="ctrl.panel.points"
on-change="ctrl.render()"
></gf-form-switch>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">Mode Options</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Fill</label>
<div class="gf-form-select-wrapper max-width-5">
<select
class="gf-form-input"
ng-model="ctrl.panel.fill"
ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"
ng-change="ctrl.render()"
ng-disabled="!ctrl.panel.lines"
></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Line Width</label>
<div class="gf-form-select-wrapper max-width-5">
<select
class="gf-form-input"
ng-model="ctrl.panel.linewidth"
ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]"
ng-change="ctrl.render()"
ng-disabled="!ctrl.panel.lines"
></select>
</div>
</div>
<gf-form-switch
ng-disabled="!ctrl.panel.lines"
class="gf-form"
label="Staircase"
label-class="width-8"
checked="ctrl.panel.steppedLine"
on-change="ctrl.render()"
>
</gf-form-switch>
<div class="gf-form" ng-if="ctrl.panel.points">
<label class="gf-form-label width-8">Point Radius</label>
<div class="gf-form-select-wrapper max-width-5">
<select
class="gf-form-input"
ng-model="ctrl.panel.pointradius"
ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]"
ng-change="ctrl.render()"
></select>
</div>
</div>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">Hover tooltip</h5>
<div class="gf-form">
<label class="gf-form-label width-9">Mode</label>
<div class="gf-form-select-wrapper max-width-8">
<select
class="gf-form-input"
ng-model="ctrl.panel.tooltip.shared"
ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]"
ng-change="ctrl.render()"
></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">Sort order</label>
<div class="gf-form-select-wrapper max-width-8">
<select
class="gf-form-input"
ng-model="ctrl.panel.tooltip.sort"
ng-options="f.value as f.text for f in [{text: 'None', value: 0}, {text: 'Increasing', value: 1}, {text: 'Decreasing', value: 2}]"
ng-change="ctrl.render()"
></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.stack">
<label class="gf-form-label width-9">Stacked value</label>
<div class="gf-form-select-wrapper max-width-8">
<select
class="gf-form-input"
ng-model="ctrl.panel.tooltip.value_type"
ng-options="f for f in ['cumulative','individual']"
ng-change="ctrl.render()"
></select>
</div>
</div>
</div>
<div class="editor-row"> <div class="section gf-form-group">
<div class="section gf-form-group"> <h5 class="section-heading">Stacking & Null value</h5>
<h5 class="section-heading">Draw Modes</h5> <gf-form-switch
<gf-form-switch class="gf-form" label="Bars" label-class="width-5" checked="ctrl.panel.bars" on-change="ctrl.render()"></gf-form-switch> class="gf-form"
<gf-form-switch class="gf-form" label="Lines" label-class="width-5" checked="ctrl.panel.lines" on-change="ctrl.render()"></gf-form-switch> label="Stack"
<gf-form-switch class="gf-form" label="Points" label-class="width-5" checked="ctrl.panel.points" on-change="ctrl.render()"></gf-form-switch> label-class="width-7"
</div> checked="ctrl.panel.stack"
<div class="section gf-form-group"> on-change="ctrl.refresh()"
<h5 class="section-heading">Mode Options</h5> >
<div class="gf-form"> </gf-form-switch>
<label class="gf-form-label width-8">Fill</label> <gf-form-switch
<div class="gf-form-select-wrapper max-width-5"> class="gf-form"
<select class="gf-form-input" ng-model="ctrl.panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.lines"></select> ng-show="ctrl.panel.stack"
</div> label="Percent"
</div> label-class="width-7"
<div class="gf-form"> checked="ctrl.panel.percentage"
<label class="gf-form-label width-8">Line Width</label> on-change="ctrl.render()"
<div class="gf-form-select-wrapper max-width-5"> >
<select class="gf-form-input" ng-model="ctrl.panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.lines"></select> </gf-form-switch>
</div> <div class="gf-form">
</div> <label class="gf-form-label width-7">Null value</label>
<gf-form-switch ng-disabled="!ctrl.panel.lines" class="gf-form" label="Staircase" label-class="width-8" checked="ctrl.panel.steppedLine" on-change="ctrl.render()"> <div class="gf-form-select-wrapper">
</gf-form-switch> <select
<div class="gf-form" ng-if="ctrl.panel.points"> class="gf-form-input max-width-9"
<label class="gf-form-label width-8">Point Radius</label> ng-model="ctrl.panel.nullPointMode"
<div class="gf-form-select-wrapper max-width-5"> ng-options="f for f in ['connected', 'null', 'null as zero']"
<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select> ng-change="ctrl.render()"
</div> ></select>
</div> </div>
</div> </div>
<div class="section gf-form-group"> </div>
<h5 class="section-heading">Hover tooltip</h5> </div>
<div class="gf-form">
<label class="gf-form-label width-9">Mode</label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.shared" ng-options="f.value as f.text for f in [{text: 'All series', value: true}, {text: 'Single', value: false}]" ng-change="ctrl.render()"></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">Sort order</label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.sort" ng-options="f.value as f.text for f in [{text: 'None', value: 0}, {text: 'Increasing', value: 1}, {text: 'Decreasing', value: 2}]" ng-change="ctrl.render()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.stack">
<label class="gf-form-label width-9">Stacked value</label>
<div class="gf-form-select-wrapper max-width-8">
<select class="gf-form-input" ng-model="ctrl.panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
<div class="section gf-form-group"> <div>
<h5 class="section-heading">Stacking & Null value</h5> <div class="gf-form-inline" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<gf-form-switch class="gf-form" label="Stack" label-class="width-7" checked="ctrl.panel.stack" on-change="ctrl.refresh()"> <div class="gf-form">
</gf-form-switch> <label class="gf-form-label">alias or regex</label>
<gf-form-switch class="gf-form" ng-show="ctrl.panel.stack" label="Percent" label-class="width-7" checked="ctrl.panel.percentage" on-change="ctrl.render()"> </div>
</gf-form-switch> <div class="gf-form width-15">
<div class="gf-form"> <input
<label class="gf-form-label width-7">Null value</label> type="text"
<div class="gf-form-select-wrapper"> ng-model="override.alias"
<select class="gf-form-input max-width-9" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select> bs-typeahead="getSeriesNames"
</div> ng-blur="ctrl.render()"
</div> data-min-length="0"
</div> data-items="100"
</div> class="gf-form-input width-15"
/>
</div>
<div class="gf-form" ng-repeat="option in currentOverrides">
<label class="gf-form-label">
<i class="pointer fa fa-remove" ng-click="removeOverride(option)"></i>
<span ng-show="option.propertyName === 'color'">
Color: <i class="fa fa-circle" ng-style="{color:option.value}"></i>
</span>
<span ng-show="option.propertyName !== 'color'"> {{ option.name }}: {{ option.value }} </span>
</label>
</div>
<div> <div class="gf-form">
<div class="gf-form-inline" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl"> <span
<div class="gf-form"> class="dropdown"
<label class="gf-form-label">alias or regex</label> dropdown-typeahead2="overrideMenu"
</div> dropdown-typeahead-on-select="setOverride($item, $subItem)"
<div class="gf-form width-15"> button-template-class="gf-form-label"
<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="ctrl.render()" data-min-length=0 data-items=100 class="gf-form-input width-15"> >
</div> </span>
<div class="gf-form" ng-repeat="option in currentOverrides"> </div>
<label class="gf-form-label">
<i class="pointer fa fa-remove" ng-click="removeOverride(option)"></i>
<span ng-show="option.propertyName === 'color'">
Color: <i class="fa fa-circle" ng-style="{color:option.value}"></i>
</span>
<span ng-show="option.propertyName !== 'color'">
{{option.name}}: {{option.value}}
</span>
</label>
</div>
<div class="gf-form"> <div class="gf-form gf-form--grow">
<span class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($item, $subItem)"> <div class="gf-form-label gf-form-label--grow"></div>
</span> </div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
<div class="gf-form">
<label class="gf-form-label">
<i class="fa fa-trash pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
</label>
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addSeriesOverride()">
<i class="fa fa-plus"></i>&nbsp;Add series override<tip>Regex match example: /server[0-3]/i </tip>
</button>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label">
<i class="fa fa-trash pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
</label>
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addSeriesOverride()">
<i class="fa fa-plus"></i>&nbsp;Add series override<tip>Regex match example: /server[0-3]/i </tip>
</button>
</div>
</div>