Merge branch 'master' into fix/ie11-support
commit
f1f65d0b41
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -1,12 +1,33 @@
|
||||||
## v1.3.6.0 [unreleased]
|
## v1.3.7.0 [unreleased]
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
1. [#1715](https://github.com/influxdata/chronograf/pull/1715): Chronograf now renders on IE11.
|
1. [#1715](https://github.com/influxdata/chronograf/pull/1715): Chronograf now renders on IE11.
|
||||||
|
1. [#1845](https://github.com/influxdata/chronograf/pull/1845): Fix no-scroll bar appearing in the Data Explorer table
|
||||||
|
1. [#1866](https://github.com/influxdata/chronograf/pull/1866): Fix missing cell type (and consequently single-stat)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
### UI Improvements
|
1. [#1863](https://github.com/influxdata/chronograf/pull/1863): Improve 'new-sources' server flag example by adding 'type' key
|
||||||
1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner to indicate data is being written
|
|
||||||
|
|
||||||
## v1.3.5.0 [2017-07-25]
|
### UI Improvements
|
||||||
|
1. [#1846](https://github.com/influxdata/chronograf/pull/1846): Increase screen real estate of Query Maker in the Cell Editor Overlay
|
||||||
|
|
||||||
|
## v1.3.6.0 [2017-08-08]
|
||||||
|
### Bug Fixes
|
||||||
|
1. [#1798](https://github.com/influxdata/chronograf/pull/1798): Fix domain not updating in visualizations when changing time range manually
|
||||||
|
1. [#1799](https://github.com/influxdata/chronograf/pull/1799): Prevent console error spam from Dygraph's synchronize method when a dashboard has only one graph
|
||||||
|
1. [#1813](https://github.com/influxdata/chronograf/pull/1813): Guarantee UUID for each Alert Table key to prevent dropping items when keys overlap
|
||||||
|
|
||||||
|
### Features
|
||||||
|
1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis bounds
|
||||||
|
1. [#1714](https://github.com/influxdata/chronograf/pull/1714): Add ability to edit a dashboard graph's y-axis label
|
||||||
|
|
||||||
|
### UI Improvements
|
||||||
|
1. [#1796](https://github.com/influxdata/chronograf/pull/1796): Add spinner write data modal to indicate data is being written
|
||||||
|
1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Fix bar graphs overlapping
|
||||||
|
1. [#1805](https://github.com/influxdata/chronograf/pull/1805): Assign a series consistent coloring when it appears in multiple cells
|
||||||
|
1. [#1800](https://github.com/influxdata/chronograf/pull/1800): Increase size of line protocol manual entry in Data Explorer's Write Data overlay
|
||||||
|
1. [#1812](https://github.com/influxdata/chronograf/pull/1812): Improve error message when request for Status Page News Feed fails
|
||||||
|
|
||||||
|
## v1.3.5.0 [2017-07-27]
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
1. [#1708](https://github.com/influxdata/chronograf/pull/1708): Fix z-index issue in dashboard cell context menu
|
1. [#1708](https://github.com/influxdata/chronograf/pull/1708): Fix z-index issue in dashboard cell context menu
|
||||||
1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path
|
1. [#1752](https://github.com/influxdata/chronograf/pull/1752): Clarify BoltPath server flag help text by making example the default path
|
||||||
|
|
|
@ -21,7 +21,7 @@ We really like to receive feature requests, as it helps us prioritize our work.
|
||||||
|
|
||||||
Contributing to the source code
|
Contributing to the source code
|
||||||
-------------------------------
|
-------------------------------
|
||||||
Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses NPM for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
|
Chronograf is built using Go for its API backend and serving the front-end assets. The front-end visualization is built with React and uses Yarn for package management. The assumption is that all your Go development are done in `$GOPATH/src`. `GOPATH` can be any directory under which Chronograf and all its dependencies will be cloned. For full details on the project structure, follow along below.
|
||||||
|
|
||||||
Submitting a pull request
|
Submitting a pull request
|
||||||
-------------------------
|
-------------------------
|
||||||
|
@ -43,9 +43,9 @@ Signing the CLA
|
||||||
If you are going to be contributing back to Chronograf please take a second to sign our CLA, which can be found
|
If you are going to be contributing back to Chronograf please take a second to sign our CLA, which can be found
|
||||||
[on our website](https://influxdata.com/community/cla/).
|
[on our website](https://influxdata.com/community/cla/).
|
||||||
|
|
||||||
Installing NPM
|
Installing Yarn
|
||||||
--------------
|
--------------
|
||||||
You'll need to install NPM to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the NPM downloads page](https://nodejs.org/en/download/).
|
You'll need to install Yarn to manage the JavaScript modules that the front-end uses. This varies depending on what platform you're developing on, but you should be able to find an installer on [the Yarn installation page](https://yarnpkg.com/en/docs/install).
|
||||||
|
|
||||||
Installing Go
|
Installing Go
|
||||||
-------------
|
-------------
|
||||||
|
@ -105,7 +105,7 @@ Retaining the directory structure `$GOPATH/src/github.com/influxdata` is necessa
|
||||||
|
|
||||||
Build and Test
|
Build and Test
|
||||||
--------------
|
--------------
|
||||||
Make sure you have `go` and `npm` installed and the project structure as shown above. We provide a `Makefile` to get up and running quickly, so all you'll need to do is run the following:
|
Make sure you have `go` and `yarn` installed and the project structure as shown above. We provide a `Makefile` to get up and running quickly, so all you'll need to do is run the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd $GOPATH/src/github.com/influxdata/chronograf
|
cd $GOPATH/src/github.com/influxdata/chronograf
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -60,11 +60,11 @@ canned/bin_gen.go: canned/*.json
|
||||||
go generate -x ./canned
|
go generate -x ./canned
|
||||||
|
|
||||||
.jssrc: $(UISOURCES)
|
.jssrc: $(UISOURCES)
|
||||||
cd ui && npm run build
|
cd ui && yarn run build
|
||||||
@touch .jssrc
|
@touch .jssrc
|
||||||
|
|
||||||
.dev-jssrc: $(UISOURCES)
|
.dev-jssrc: $(UISOURCES)
|
||||||
cd ui && npm run build:dev
|
cd ui && yarn run build:dev
|
||||||
@touch .dev-jssrc
|
@touch .dev-jssrc
|
||||||
|
|
||||||
dep: .jsdep .godep
|
dep: .jsdep .godep
|
||||||
|
@ -98,7 +98,7 @@ gotestrace:
|
||||||
go test -race `go list ./... | grep -v /vendor/`
|
go test -race `go list ./... | grep -v /vendor/`
|
||||||
|
|
||||||
jstest:
|
jstest:
|
||||||
cd ui && npm test
|
cd ui && yarn test
|
||||||
|
|
||||||
run: ${BINARY}
|
run: ${BINARY}
|
||||||
./chronograf
|
./chronograf
|
||||||
|
@ -108,7 +108,7 @@ run-dev: chronogiraffe
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi
|
if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi
|
||||||
cd ui && npm run clean
|
cd ui && yarn run clean
|
||||||
cd ui && rm -rf node_modules
|
cd ui && rm -rf node_modules
|
||||||
rm -f dist/dist_gen.go canned/bin_gen.go server/swagger_gen.go
|
rm -f dist/dist_gen.go canned/bin_gen.go server/swagger_gen.go
|
||||||
@rm -f .godep .jsdep .jssrc .dev-jssrc .bindata
|
@rm -f .godep .jsdep .jssrc .dev-jssrc .bindata
|
||||||
|
|
|
@ -183,14 +183,9 @@ func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) {
|
||||||
|
|
||||||
axes := make(map[string]*Axis, len(c.Axes))
|
axes := make(map[string]*Axis, len(c.Axes))
|
||||||
for a, r := range c.Axes {
|
for a, r := range c.Axes {
|
||||||
// need to explicitly allocate a new array because r.Bounds is
|
|
||||||
// over-written and the resulting slices from previous iterations will
|
|
||||||
// point to later iteration's data. It is _not_ enough to simply re-slice
|
|
||||||
// r.Bounds
|
|
||||||
axis := [2]int64{}
|
|
||||||
copy(axis[:], r.Bounds[:2])
|
|
||||||
axes[a] = &Axis{
|
axes[a] = &Axis{
|
||||||
Bounds: axis[:],
|
Bounds: r.Bounds,
|
||||||
|
Label: r.Label,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,9 +264,16 @@ func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error {
|
||||||
|
|
||||||
axes := make(map[string]chronograf.Axis, len(c.Axes))
|
axes := make(map[string]chronograf.Axis, len(c.Axes))
|
||||||
for a, r := range c.Axes {
|
for a, r := range c.Axes {
|
||||||
axis := chronograf.Axis{}
|
if r.Bounds != nil {
|
||||||
copy(axis.Bounds[:], r.Bounds[:2])
|
axes[a] = chronograf.Axis{
|
||||||
axes[a] = axis
|
Bounds: r.Bounds,
|
||||||
|
Label: r.Label,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
axes[a] = chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cells[i] = chronograf.DashboardCell{
|
cells[i] = chronograf.DashboardCell{
|
||||||
|
|
|
@ -118,7 +118,9 @@ func (m *DashboardCell) GetAxes() map[string]*Axis {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Axis struct {
|
type Axis struct {
|
||||||
Bounds []int64 `protobuf:"varint,1,rep,name=bounds" json:"bounds,omitempty"`
|
LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"`
|
||||||
|
Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"`
|
||||||
|
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Axis) Reset() { *m = Axis{} }
|
func (m *Axis) Reset() { *m = Axis{} }
|
||||||
|
@ -313,65 +315,66 @@ func init() {
|
||||||
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||||
|
|
||||||
var fileDescriptorInternal = []byte{
|
var fileDescriptorInternal = []byte{
|
||||||
// 952 bytes of a gzipped FileDescriptorProto
|
// 974 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xcf, 0x8e, 0xe3, 0xc4,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0xdf, 0x6e, 0xe3, 0xc4,
|
||||||
0x13, 0x56, 0xc7, 0x76, 0x12, 0x57, 0x66, 0xe7, 0xf7, 0x53, 0x6b, 0xc5, 0x9a, 0x45, 0x42, 0xc1,
|
0x17, 0xd6, 0xc4, 0x76, 0x12, 0x9f, 0x76, 0xfb, 0xfb, 0x69, 0xb4, 0x62, 0xcd, 0x72, 0x13, 0x2c,
|
||||||
0x02, 0x29, 0x20, 0x31, 0xa0, 0x5d, 0x21, 0x21, 0x6e, 0x99, 0x09, 0x5a, 0x85, 0x99, 0x5d, 0x86,
|
0x90, 0x02, 0x12, 0x05, 0xed, 0x0a, 0x09, 0x71, 0x97, 0x36, 0x68, 0x55, 0xda, 0x5d, 0xca, 0xa4,
|
||||||
0xce, 0xcc, 0x70, 0x42, 0xab, 0x4e, 0x52, 0x99, 0x58, 0xeb, 0xc4, 0xa6, 0x6d, 0x4f, 0xe2, 0xb7,
|
0x2d, 0xdc, 0xa0, 0xd5, 0xc4, 0x39, 0x4d, 0xac, 0x75, 0x62, 0x33, 0xb6, 0x9b, 0xf8, 0x2d, 0x78,
|
||||||
0xe0, 0x09, 0x90, 0x90, 0x38, 0x71, 0xe0, 0xc0, 0x0b, 0xf0, 0x10, 0xbc, 0x10, 0xaa, 0xee, 0xf6,
|
0x02, 0x24, 0x24, 0xae, 0xb8, 0xe0, 0x82, 0x17, 0xe0, 0x21, 0x78, 0x21, 0x74, 0x66, 0xc6, 0x7f,
|
||||||
0x9f, 0xb0, 0xb3, 0x68, 0x4f, 0xdc, 0xfa, 0xab, 0xea, 0x7c, 0xe5, 0xfe, 0xea, 0xab, 0x52, 0xe0,
|
0xc2, 0x76, 0xd1, 0x5e, 0x71, 0x37, 0xdf, 0x39, 0xe3, 0x6f, 0x66, 0xbe, 0xf3, 0x9d, 0x23, 0xc3,
|
||||||
0x38, 0xda, 0xe6, 0xa8, 0xb6, 0x32, 0x3e, 0x49, 0x55, 0x92, 0x27, 0xbc, 0x5f, 0xe1, 0xf0, 0xf7,
|
0x51, 0xbc, 0x29, 0x50, 0x6d, 0x64, 0x72, 0x9c, 0xa9, 0xb4, 0x48, 0xf9, 0xb0, 0xc6, 0xe1, 0xef,
|
||||||
0x0e, 0x74, 0x67, 0x49, 0xa1, 0x16, 0xc8, 0x8f, 0xa1, 0x33, 0x9d, 0x04, 0x6c, 0xc8, 0x46, 0x8e,
|
0x3d, 0xe8, 0xcf, 0xd2, 0x52, 0x45, 0xc8, 0x8f, 0xa0, 0x77, 0x36, 0x0d, 0xd8, 0x88, 0x8d, 0x1d,
|
||||||
0xe8, 0x4c, 0x27, 0x9c, 0x83, 0xfb, 0x42, 0x6e, 0x30, 0xe8, 0x0c, 0xd9, 0xc8, 0x17, 0xfa, 0x4c,
|
0xd1, 0x3b, 0x9b, 0x72, 0x0e, 0xee, 0x0b, 0xb9, 0xc6, 0xa0, 0x37, 0x62, 0x63, 0x5f, 0xe8, 0x35,
|
||||||
0xb1, 0xab, 0x32, 0xc5, 0xc0, 0x31, 0x31, 0x3a, 0xf3, 0xc7, 0xd0, 0xbf, 0xce, 0x88, 0x6d, 0x83,
|
0xc5, 0xae, 0xaa, 0x0c, 0x03, 0xc7, 0xc4, 0x68, 0xcd, 0x1f, 0xc3, 0xf0, 0x3a, 0x27, 0xb6, 0x35,
|
||||||
0x81, 0xab, 0xe3, 0x35, 0xa6, 0xdc, 0xa5, 0xcc, 0xb2, 0x5d, 0xa2, 0x96, 0x81, 0x67, 0x72, 0x15,
|
0x06, 0xae, 0x8e, 0x37, 0x98, 0x72, 0x97, 0x32, 0xcf, 0xb7, 0xa9, 0x5a, 0x04, 0x9e, 0xc9, 0xd5,
|
||||||
0xe6, 0xff, 0x07, 0xe7, 0x5a, 0x5c, 0x04, 0x5d, 0x1d, 0xa6, 0x23, 0x0f, 0xa0, 0x37, 0xc1, 0x95,
|
0x98, 0xff, 0x1f, 0x9c, 0x6b, 0x71, 0x11, 0xf4, 0x75, 0x98, 0x96, 0x3c, 0x80, 0xc1, 0x14, 0x6f,
|
||||||
0x2c, 0xe2, 0x3c, 0xe8, 0x0d, 0xd9, 0xa8, 0x2f, 0x2a, 0x48, 0x3c, 0x57, 0x18, 0xe3, 0xad, 0x92,
|
0x65, 0x99, 0x14, 0xc1, 0x60, 0xc4, 0xc6, 0x43, 0x51, 0x43, 0xe2, 0xb9, 0xc2, 0x04, 0x97, 0x4a,
|
||||||
0xab, 0xa0, 0x6f, 0x78, 0x2a, 0xcc, 0x4f, 0x80, 0x4f, 0xb7, 0x19, 0x2e, 0x0a, 0x85, 0xb3, 0x57,
|
0xde, 0x06, 0x43, 0xc3, 0x53, 0x63, 0x7e, 0x0c, 0xfc, 0x6c, 0x93, 0x63, 0x54, 0x2a, 0x9c, 0xbd,
|
||||||
0x51, 0x7a, 0x83, 0x2a, 0x5a, 0x95, 0x81, 0xaf, 0x09, 0xee, 0xc9, 0x50, 0x95, 0xe7, 0x98, 0x4b,
|
0x8a, 0xb3, 0x1b, 0x54, 0xf1, 0x6d, 0x15, 0xf8, 0x9a, 0xe0, 0x9e, 0x0c, 0x9d, 0xf2, 0x1c, 0x0b,
|
||||||
0xaa, 0x0d, 0x9a, 0xaa, 0x82, 0x3c, 0x84, 0xa3, 0xd9, 0x5a, 0x2a, 0x5c, 0xce, 0x70, 0xa1, 0x30,
|
0x49, 0x67, 0x83, 0xa6, 0xaa, 0x21, 0x0f, 0xe1, 0x70, 0xb6, 0x92, 0x0a, 0x17, 0x33, 0x8c, 0x14,
|
||||||
0x0f, 0x06, 0x3a, 0x7d, 0x10, 0x0b, 0x7f, 0x62, 0xe0, 0x4f, 0x64, 0xb6, 0x9e, 0x27, 0x52, 0x2d,
|
0x16, 0xc1, 0x81, 0x4e, 0xef, 0xc5, 0xc2, 0x9f, 0x18, 0xf8, 0x53, 0x99, 0xaf, 0xe6, 0xa9, 0x54,
|
||||||
0xdf, 0x4a, 0xb3, 0x4f, 0xc1, 0x5b, 0x60, 0x1c, 0x67, 0x81, 0x33, 0x74, 0x46, 0x83, 0x27, 0x8f,
|
0x8b, 0xb7, 0xd2, 0xec, 0x13, 0xf0, 0x22, 0x4c, 0x92, 0x3c, 0x70, 0x46, 0xce, 0xf8, 0xe0, 0xc9,
|
||||||
0x4e, 0xea, 0x66, 0xd4, 0x3c, 0x67, 0x18, 0xc7, 0xc2, 0xdc, 0xe2, 0x9f, 0x83, 0x9f, 0xe3, 0x26,
|
0xa3, 0xe3, 0xa6, 0x18, 0x0d, 0xcf, 0x29, 0x26, 0x89, 0x30, 0xbb, 0xf8, 0x67, 0xe0, 0x17, 0xb8,
|
||||||
0x8d, 0x65, 0x8e, 0x59, 0xe0, 0xea, 0x9f, 0xf0, 0xe6, 0x27, 0x57, 0x36, 0x25, 0x9a, 0x4b, 0xe1,
|
0xce, 0x12, 0x59, 0x60, 0x1e, 0xb8, 0xfa, 0x13, 0xde, 0x7e, 0x72, 0x65, 0x53, 0xa2, 0xdd, 0x14,
|
||||||
0x6f, 0x1d, 0x78, 0x70, 0x40, 0xc5, 0x8f, 0x80, 0xed, 0xf5, 0x57, 0x79, 0x82, 0xed, 0x09, 0x95,
|
0xfe, 0xd6, 0x83, 0x07, 0x7b, 0x54, 0xfc, 0x10, 0xd8, 0x4e, 0xdf, 0xca, 0x13, 0x6c, 0x47, 0xa8,
|
||||||
0xfa, 0x8b, 0x3c, 0xc1, 0x4a, 0x42, 0x3b, 0xdd, 0x3f, 0x4f, 0xb0, 0x1d, 0xa1, 0xb5, 0xee, 0x9a,
|
0xd2, 0x37, 0xf2, 0x04, 0xab, 0x08, 0x6d, 0x75, 0xfd, 0x3c, 0xc1, 0xb6, 0x84, 0x56, 0xba, 0x6a,
|
||||||
0x27, 0xd8, 0x9a, 0x7f, 0x0c, 0xbd, 0x1f, 0x0b, 0x54, 0x11, 0x66, 0x81, 0xa7, 0x2b, 0xff, 0xaf,
|
0x9e, 0x60, 0x2b, 0xfe, 0x11, 0x0c, 0x7e, 0x2c, 0x51, 0xc5, 0x98, 0x07, 0x9e, 0x3e, 0xf9, 0x7f,
|
||||||
0xa9, 0xfc, 0x5d, 0x81, 0xaa, 0x14, 0x55, 0x9e, 0x5e, 0xaa, 0x3b, 0x6e, 0xda, 0xa7, 0xcf, 0x14,
|
0xed, 0xc9, 0xdf, 0x96, 0xa8, 0x2a, 0x51, 0xe7, 0xe9, 0xa5, 0xba, 0xe2, 0xa6, 0x7c, 0x7a, 0x4d,
|
||||||
0xcb, 0xc9, 0x1d, 0x3d, 0x13, 0xa3, 0xb3, 0x55, 0xc8, 0xf4, 0x8c, 0x14, 0xfa, 0x02, 0x5c, 0xb9,
|
0xb1, 0x82, 0xdc, 0x31, 0x30, 0x31, 0x5a, 0x5b, 0x85, 0x4c, 0xcd, 0x48, 0xa1, 0xcf, 0xc1, 0x95,
|
||||||
0xc7, 0x2c, 0xf0, 0x35, 0xff, 0x07, 0x6f, 0x10, 0xe3, 0x64, 0xbc, 0xc7, 0xec, 0xeb, 0x6d, 0xae,
|
0x3b, 0xcc, 0x03, 0x5f, 0xf3, 0xbf, 0xff, 0x06, 0x31, 0x8e, 0x27, 0x3b, 0xcc, 0xbf, 0xda, 0x14,
|
||||||
0x4a, 0xa1, 0xaf, 0x3f, 0x7e, 0x06, 0x7e, 0x1d, 0x22, 0xe7, 0xbc, 0xc2, 0x52, 0x3f, 0xd0, 0x17,
|
0xaa, 0x12, 0x7a, 0xfb, 0xe3, 0x67, 0xe0, 0x37, 0x21, 0x72, 0xce, 0x2b, 0xac, 0xf4, 0x03, 0x7d,
|
||||||
0x74, 0xe4, 0x1f, 0x82, 0x77, 0x27, 0xe3, 0xc2, 0x08, 0x3f, 0x78, 0x72, 0xdc, 0xd0, 0x8e, 0xf7,
|
0x41, 0x4b, 0xfe, 0x01, 0x78, 0x77, 0x32, 0x29, 0x8d, 0xf0, 0x07, 0x4f, 0x8e, 0x5a, 0xda, 0xc9,
|
||||||
0x51, 0x26, 0x4c, 0xf2, 0xab, 0xce, 0x97, 0x2c, 0x7c, 0x1f, 0x5c, 0x0a, 0xf1, 0x77, 0xa0, 0x3b,
|
0x2e, 0xce, 0x85, 0x49, 0x7e, 0xd9, 0xfb, 0x82, 0x85, 0xdf, 0x83, 0x4b, 0x21, 0xaa, 0x75, 0x82,
|
||||||
0x4f, 0x8a, 0xed, 0x32, 0x0b, 0xd8, 0xd0, 0x19, 0x39, 0xc2, 0xa2, 0xf0, 0x4f, 0x46, 0x56, 0x33,
|
0x4b, 0x19, 0x55, 0x27, 0x69, 0xb9, 0x59, 0xe4, 0x01, 0x1b, 0x39, 0x63, 0x47, 0xec, 0xc5, 0xf8,
|
||||||
0xd2, 0xb6, 0xda, 0x6b, 0x3e, 0xfe, 0x5d, 0xe8, 0x93, 0xec, 0x2f, 0xef, 0xa4, 0xb2, 0x2d, 0xee,
|
0x3b, 0xd0, 0x9f, 0x9b, 0x6c, 0x6f, 0xe4, 0x8c, 0x7d, 0x61, 0x11, 0x7f, 0x08, 0x5e, 0x22, 0xe7,
|
||||||
0x11, 0xbe, 0x91, 0x8a, 0x7f, 0x06, 0x5d, 0x5d, 0xe4, 0x9e, 0x36, 0x57, 0x74, 0x37, 0x94, 0x17,
|
0x98, 0xd8, 0x36, 0x30, 0x20, 0xfc, 0x93, 0x91, 0x49, 0x4d, 0x51, 0x3a, 0xc6, 0x30, 0xcf, 0x7e,
|
||||||
0xf6, 0x5a, 0x2d, 0x96, 0xdb, 0x12, 0xeb, 0x21, 0x78, 0xb1, 0x9c, 0x63, 0x6c, 0x67, 0xc5, 0x00,
|
0x17, 0x86, 0x54, 0xb0, 0x97, 0x77, 0x52, 0x59, 0x73, 0x0c, 0x08, 0xdf, 0x48, 0xc5, 0x3f, 0x85,
|
||||||
0x32, 0x10, 0xa9, 0x5e, 0x6a, 0xad, 0xef, 0x65, 0x36, 0xbd, 0x31, 0xb7, 0xc2, 0x6b, 0x78, 0x70,
|
0xbe, 0xbe, 0xde, 0x3d, 0x06, 0xa9, 0xe9, 0x6e, 0x28, 0x2f, 0xec, 0xb6, 0x46, 0x66, 0xb7, 0x23,
|
||||||
0x50, 0xb1, 0xae, 0xc4, 0x0e, 0x2b, 0x35, 0x82, 0xf9, 0x56, 0x20, 0x1a, 0xb3, 0x0c, 0x63, 0x5c,
|
0x73, 0x73, 0x25, 0xaf, 0x73, 0x25, 0xb2, 0x1e, 0xd5, 0xab, 0xd2, 0x55, 0xba, 0x97, 0xd9, 0x54,
|
||||||
0xe4, 0xb8, 0xd4, 0x16, 0xe9, 0x8b, 0x1a, 0x87, 0xbf, 0xb0, 0x86, 0x57, 0xd7, 0xa3, 0x41, 0x5a,
|
0xd5, 0xec, 0x0a, 0xaf, 0xe1, 0xc1, 0xde, 0x89, 0xcd, 0x49, 0x6c, 0xff, 0xa4, 0x56, 0x6a, 0xdf,
|
||||||
0x24, 0x9b, 0x8d, 0xdc, 0x2e, 0x2d, 0x75, 0x05, 0x49, 0xb7, 0xe5, 0xdc, 0x52, 0x77, 0x96, 0x73,
|
0x4a, 0x4b, 0x0d, 0x9a, 0x63, 0x82, 0x51, 0x81, 0x0b, 0xad, 0xca, 0x50, 0x34, 0x38, 0xfc, 0x85,
|
||||||
0xc2, 0x2a, 0xb5, 0x4b, 0xa3, 0xa3, 0x52, 0x3e, 0x84, 0xc1, 0x06, 0x65, 0x56, 0x28, 0xdc, 0xe0,
|
0xb5, 0xbc, 0xfa, 0x3c, 0x6a, 0xc1, 0x28, 0x5d, 0xaf, 0xe5, 0x66, 0x61, 0xa9, 0x6b, 0x48, 0xba,
|
||||||
0x36, 0xb7, 0x12, 0xb4, 0x43, 0xfc, 0x11, 0xf4, 0x72, 0x79, 0xfb, 0x92, 0xda, 0x6c, 0xb4, 0xe8,
|
0x2d, 0xe6, 0x96, 0xba, 0xb7, 0x98, 0x13, 0x56, 0x99, 0xd5, 0xb9, 0xa7, 0x32, 0x3e, 0x82, 0x83,
|
||||||
0xe6, 0xf2, 0xf6, 0x1c, 0x4b, 0xfe, 0x1e, 0xf8, 0xab, 0x08, 0xe3, 0xa5, 0x4e, 0x19, 0xf3, 0xf5,
|
0x35, 0xca, 0xbc, 0x54, 0xb8, 0xc6, 0x4d, 0x61, 0x25, 0xe8, 0x86, 0xf8, 0x23, 0x18, 0x14, 0x72,
|
||||||
0x75, 0xe0, 0x1c, 0xcb, 0xf0, 0x57, 0x06, 0xdd, 0x19, 0xaa, 0x3b, 0x54, 0x6f, 0x35, 0x99, 0xed,
|
0xf9, 0x92, 0x0c, 0x62, 0xb4, 0xe8, 0x17, 0x72, 0x79, 0x8e, 0x15, 0x7f, 0x0f, 0xfc, 0xdb, 0x18,
|
||||||
0xcd, 0xe5, 0xfc, 0xcb, 0xe6, 0x72, 0xef, 0xdf, 0x5c, 0x5e, 0xb3, 0xb9, 0x1e, 0x82, 0x37, 0x53,
|
0x93, 0x85, 0x4e, 0x19, 0xdb, 0x0e, 0x75, 0xe0, 0x1c, 0xab, 0xf0, 0x57, 0x06, 0xfd, 0x19, 0xaa,
|
||||||
0x8b, 0xe9, 0x44, 0x7f, 0x91, 0x23, 0x0c, 0x20, 0x8f, 0x8d, 0x17, 0x79, 0x74, 0x87, 0x76, 0x9d,
|
0x3b, 0x54, 0x6f, 0xd5, 0xd3, 0xdd, 0x99, 0xe7, 0xfc, 0xcb, 0xcc, 0x73, 0xef, 0x9f, 0x79, 0x5e,
|
||||||
0x59, 0x14, 0xfe, 0xcc, 0xa0, 0x7b, 0x21, 0xcb, 0xa4, 0xc8, 0x5f, 0x73, 0xd8, 0x10, 0x06, 0xe3,
|
0x3b, 0xf3, 0x1e, 0x82, 0x37, 0x53, 0xd1, 0xd9, 0x54, 0xdf, 0xc8, 0x11, 0x06, 0x90, 0xf3, 0x26,
|
||||||
0x34, 0x8d, 0xa3, 0x85, 0xcc, 0xa3, 0x64, 0x6b, 0xbf, 0xb6, 0x1d, 0xa2, 0x1b, 0xcf, 0x5b, 0xda,
|
0x51, 0x11, 0xdf, 0xa1, 0x1d, 0x84, 0x16, 0x85, 0x3f, 0x33, 0xe8, 0x5f, 0xc8, 0x2a, 0x2d, 0x8b,
|
||||||
0x99, 0xef, 0x6e, 0x87, 0x68, 0x18, 0xce, 0xf4, 0xc2, 0x31, 0xdb, 0xa3, 0x35, 0x0c, 0x66, 0xcf,
|
0xd7, 0x1c, 0x36, 0x82, 0x83, 0x49, 0x96, 0x25, 0x71, 0x24, 0x8b, 0x38, 0xdd, 0xd8, 0xdb, 0x76,
|
||||||
0xe8, 0x24, 0x3d, 0x70, 0x5c, 0xe4, 0xc9, 0x2a, 0x4e, 0x76, 0xfa, 0x25, 0x7d, 0x51, 0xe3, 0xf0,
|
0x43, 0xb4, 0xe3, 0x79, 0x47, 0x3b, 0x73, 0xef, 0x6e, 0x88, 0xda, 0xe8, 0x54, 0x8f, 0x2a, 0x33,
|
||||||
0x2f, 0x06, 0xee, 0x7f, 0xb5, 0x48, 0x8e, 0x80, 0x45, 0xb6, 0x91, 0x2c, 0xaa, 0xd7, 0x4a, 0xaf,
|
0x77, 0x3a, 0x6d, 0x64, 0x26, 0x94, 0x4e, 0xd2, 0x03, 0x27, 0x65, 0x91, 0xde, 0x26, 0xe9, 0x56,
|
||||||
0xb5, 0x56, 0x02, 0xe8, 0x95, 0x4a, 0x6e, 0x6f, 0x31, 0x0b, 0xfa, 0x7a, 0x56, 0x2b, 0xa8, 0x33,
|
0xbf, 0x64, 0x28, 0x1a, 0x1c, 0xfe, 0xc5, 0xc0, 0xfd, 0xaf, 0x46, 0xd0, 0x21, 0xb0, 0xd8, 0x16,
|
||||||
0x7a, 0x46, 0xcc, 0x3e, 0xf1, 0x45, 0x05, 0x6b, 0xcf, 0x43, 0xe3, 0xf9, 0xf0, 0x0f, 0x06, 0x5e,
|
0x92, 0xc5, 0xcd, 0x40, 0x1a, 0x74, 0x06, 0x52, 0x00, 0x83, 0x4a, 0xc9, 0xcd, 0x12, 0xf3, 0x60,
|
||||||
0xed, 0xdc, 0xb3, 0x43, 0xe7, 0x9e, 0x35, 0xce, 0x9d, 0x9c, 0x56, 0xce, 0x9d, 0x9c, 0x12, 0x16,
|
0xa8, 0xfb, 0xbb, 0x86, 0x3a, 0xa3, 0x7b, 0xc4, 0x4c, 0x22, 0x5f, 0xd4, 0xb0, 0xf1, 0x3c, 0xb4,
|
||||||
0x97, 0x95, 0x73, 0xc5, 0x25, 0xa9, 0xf6, 0x4c, 0x25, 0x45, 0x7a, 0x5a, 0x1a, 0x79, 0x7d, 0x51,
|
0x9e, 0x0f, 0xff, 0x60, 0xe0, 0x35, 0xce, 0x3d, 0xdd, 0x77, 0xee, 0x69, 0xeb, 0xdc, 0xe9, 0x49,
|
||||||
0x63, 0x6a, 0xf7, 0xf7, 0x6b, 0x54, 0xf6, 0xcd, 0xbe, 0xb0, 0x88, 0xcc, 0x71, 0xa1, 0xa7, 0xda,
|
0xed, 0xdc, 0xe9, 0x09, 0x61, 0x71, 0x59, 0x3b, 0x57, 0x5c, 0x92, 0x6a, 0xcf, 0x54, 0x5a, 0x66,
|
||||||
0xbc, 0xd2, 0x00, 0xfe, 0x11, 0x78, 0x82, 0x5e, 0xa1, 0x9f, 0x7a, 0x20, 0x90, 0x0e, 0x0b, 0x93,
|
0x27, 0x95, 0x91, 0xd7, 0x17, 0x0d, 0xa6, 0x72, 0x7f, 0xb7, 0x42, 0x65, 0xdf, 0xec, 0x0b, 0x8b,
|
||||||
0x0d, 0x9f, 0xda, 0x6b, 0xc4, 0x72, 0x9d, 0xa6, 0xa8, 0xac, 0xa7, 0x0d, 0xd0, 0xdc, 0xc9, 0x0e,
|
0xc8, 0x1c, 0x17, 0xba, 0xab, 0xcd, 0x2b, 0x0d, 0xe0, 0x1f, 0x82, 0x27, 0xe8, 0x15, 0xfa, 0xa9,
|
||||||
0xcd, 0x3a, 0x72, 0x84, 0x01, 0xe1, 0x0f, 0xe0, 0x8f, 0x63, 0x54, 0xb9, 0x28, 0xe2, 0xd7, 0x97,
|
0x7b, 0x02, 0xe9, 0xb0, 0x30, 0xd9, 0xf0, 0xa9, 0xdd, 0x46, 0x2c, 0xd7, 0x59, 0x86, 0xca, 0x7a,
|
||||||
0x18, 0x07, 0xf7, 0x9b, 0xd9, 0xb7, 0x2f, 0xaa, 0x49, 0xa0, 0x73, 0xe3, 0x5f, 0xe7, 0x1f, 0xfe,
|
0xda, 0x00, 0xcd, 0x9d, 0x6e, 0xd1, 0x8c, 0x23, 0x47, 0x18, 0x10, 0xfe, 0x00, 0xfe, 0x24, 0x41,
|
||||||
0x3d, 0x97, 0xa9, 0x9c, 0x4e, 0x74, 0x63, 0x1d, 0x61, 0x51, 0xf8, 0x09, 0xb8, 0x34, 0x27, 0x2d,
|
0x55, 0x88, 0x32, 0x79, 0x7d, 0x88, 0x71, 0x70, 0xbf, 0x9e, 0x7d, 0xf3, 0xa2, 0xee, 0x04, 0x5a,
|
||||||
0x66, 0xf7, 0x4d, 0x33, 0x36, 0xef, 0xea, 0x7f, 0x1c, 0x4f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff,
|
0xb7, 0xfe, 0x75, 0xfe, 0xe1, 0xdf, 0x73, 0x99, 0xc9, 0xb3, 0xa9, 0x2e, 0xac, 0x23, 0x2c, 0x0a,
|
||||||
0x94, 0xd8, 0xce, 0x85, 0x83, 0x08, 0x00, 0x00,
|
0x3f, 0x06, 0x97, 0xfa, 0xa4, 0xc3, 0xec, 0xbe, 0xa9, 0xc7, 0xe6, 0x7d, 0xfd, 0xaf, 0xf2, 0xf4,
|
||||||
|
0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, 0x79, 0x87, 0x37, 0xbd, 0x08, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,9 @@ message DashboardCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Axis {
|
message Axis {
|
||||||
repeated int64 bounds = 1; // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively
|
repeated int64 legacyBounds = 1; // legacyBounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively
|
||||||
|
repeated string bounds = 2; // bounds are an arbitrary list of client-defined bounds.
|
||||||
|
string label = 3; // label is a description of this axis
|
||||||
}
|
}
|
||||||
|
|
||||||
message Template {
|
message Template {
|
||||||
|
|
|
@ -160,7 +160,8 @@ func Test_MarshalDashboard(t *testing.T) {
|
||||||
},
|
},
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"y": chronograf.Axis{
|
"y": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "3", "1-7", "foo"},
|
||||||
|
Label: "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: "line",
|
Type: "line",
|
||||||
|
@ -179,3 +180,149 @@ func Test_MarshalDashboard(t *testing.T) {
|
||||||
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(dashboard, actual))
|
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(dashboard, actual))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) {
|
||||||
|
dashboard := chronograf.Dashboard{
|
||||||
|
ID: 1,
|
||||||
|
Cells: []chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "Super awesome query",
|
||||||
|
Queries: []chronograf.DashboardQuery{
|
||||||
|
{
|
||||||
|
Command: "select * from cpu",
|
||||||
|
Label: "CPU Utilization",
|
||||||
|
Range: &chronograf.Range{
|
||||||
|
Upper: int64(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
LegacyBounds: [2]int64{0, 5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "line",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Templates: []chronograf.Template{},
|
||||||
|
Name: "Dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := chronograf.Dashboard{
|
||||||
|
ID: 1,
|
||||||
|
Cells: []chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "Super awesome query",
|
||||||
|
Queries: []chronograf.DashboardQuery{
|
||||||
|
{
|
||||||
|
Command: "select * from cpu",
|
||||||
|
Label: "CPU Utilization",
|
||||||
|
Range: &chronograf.Range{
|
||||||
|
Upper: int64(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "line",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Templates: []chronograf.Template{},
|
||||||
|
Name: "Dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual chronograf.Dashboard
|
||||||
|
if buf, err := internal.MarshalDashboard(dashboard); err != nil {
|
||||||
|
t.Fatal("Error marshaling dashboard: err", err)
|
||||||
|
} else if err := internal.UnmarshalDashboard(buf, &actual); err != nil {
|
||||||
|
t.Fatal("Error unmarshaling dashboard: err:", err)
|
||||||
|
} else if !cmp.Equal(expected, actual) {
|
||||||
|
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_MarshalDashboard_WithNoLegacyBounds(t *testing.T) {
|
||||||
|
dashboard := chronograf.Dashboard{
|
||||||
|
ID: 1,
|
||||||
|
Cells: []chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "Super awesome query",
|
||||||
|
Queries: []chronograf.DashboardQuery{
|
||||||
|
{
|
||||||
|
Command: "select * from cpu",
|
||||||
|
Label: "CPU Utilization",
|
||||||
|
Range: &chronograf.Range{
|
||||||
|
Upper: int64(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
LegacyBounds: [2]int64{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "line",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Templates: []chronograf.Template{},
|
||||||
|
Name: "Dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := chronograf.Dashboard{
|
||||||
|
ID: 1,
|
||||||
|
Cells: []chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "9b5367de-c552-4322-a9e8-7f384cbd235c",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "Super awesome query",
|
||||||
|
Queries: []chronograf.DashboardQuery{
|
||||||
|
{
|
||||||
|
Command: "select * from cpu",
|
||||||
|
Label: "CPU Utilization",
|
||||||
|
Range: &chronograf.Range{
|
||||||
|
Upper: int64(100),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: "line",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Templates: []chronograf.Template{},
|
||||||
|
Name: "Dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual chronograf.Dashboard
|
||||||
|
if buf, err := internal.MarshalDashboard(dashboard); err != nil {
|
||||||
|
t.Fatal("Error marshaling dashboard: err", err)
|
||||||
|
} else if err := internal.UnmarshalDashboard(buf, &actual); err != nil {
|
||||||
|
t.Fatal("Error unmarshaling dashboard: err:", err)
|
||||||
|
} else if !cmp.Equal(expected, actual) {
|
||||||
|
t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -569,7 +569,9 @@ type Dashboard struct {
|
||||||
|
|
||||||
// Axis represents the visible extents of a visualization
|
// Axis represents the visible extents of a visualization
|
||||||
type Axis struct {
|
type Axis struct {
|
||||||
Bounds [2]int64 `json:"bounds"` // bounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively
|
Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell
|
||||||
|
LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis
|
||||||
|
Label string `json:"label"` // label is a description of this Axis
|
||||||
}
|
}
|
||||||
|
|
||||||
// DashboardCell holds visual and query information for a cell
|
// DashboardCell holds visual and query information for a cell
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/influxdata/chronograf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ chronograf.DashboardsStore = &DashboardsStore{}
|
||||||
|
|
||||||
|
type DashboardsStore struct {
|
||||||
|
AddF func(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error)
|
||||||
|
AllF func(ctx context.Context) ([]chronograf.Dashboard, error)
|
||||||
|
DeleteF func(ctx context.Context, target chronograf.Dashboard) error
|
||||||
|
GetF func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error)
|
||||||
|
UpdateF func(ctx context.Context, target chronograf.Dashboard) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DashboardsStore) Add(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) {
|
||||||
|
return d.AddF(ctx, newDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) {
|
||||||
|
return d.AllF(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DashboardsStore) Delete(ctx context.Context, target chronograf.Dashboard) error {
|
||||||
|
return d.DeleteF(ctx, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) {
|
||||||
|
return d.GetF(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DashboardsStore) Update(ctx context.Context, target chronograf.Dashboard) error {
|
||||||
|
return d.UpdateF(ctx, target)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package mocks
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/chronograf"
|
"github.com/influxdata/chronograf"
|
||||||
)
|
)
|
||||||
|
@ -72,3 +73,11 @@ func (tl *TestLogger) stringifyArg(arg interface{}) []byte {
|
||||||
return []byte("UNKNOWN")
|
return []byte("UNKNOWN")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dump dumps out logs into a given testing.T's logs
|
||||||
|
func (tl *TestLogger) Dump(t *testing.T) {
|
||||||
|
t.Log("== Dumping Test Logs ==")
|
||||||
|
for _, msg := range tl.Messages {
|
||||||
|
t.Logf("lvl: %s, msg: %s", msg.Level, msg.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,11 +30,35 @@ func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardC
|
||||||
base := "/chronograf/v1/dashboards"
|
base := "/chronograf/v1/dashboards"
|
||||||
cells := make([]dashboardCellResponse, len(dcells))
|
cells := make([]dashboardCellResponse, len(dcells))
|
||||||
for i, cell := range dcells {
|
for i, cell := range dcells {
|
||||||
if len(cell.Queries) == 0 {
|
newCell := chronograf.DashboardCell{}
|
||||||
cell.Queries = make([]chronograf.DashboardQuery, 0)
|
|
||||||
|
newCell.Queries = make([]chronograf.DashboardQuery, len(cell.Queries))
|
||||||
|
copy(newCell.Queries, cell.Queries)
|
||||||
|
|
||||||
|
// ensure x, y, and y2 axes always returned
|
||||||
|
labels := []string{"x", "y", "y2"}
|
||||||
|
newCell.Axes = make(map[string]chronograf.Axis, len(labels))
|
||||||
|
|
||||||
|
newCell.X = cell.X
|
||||||
|
newCell.Y = cell.Y
|
||||||
|
newCell.W = cell.W
|
||||||
|
newCell.H = cell.H
|
||||||
|
newCell.Name = cell.Name
|
||||||
|
newCell.ID = cell.ID
|
||||||
|
newCell.Type = cell.Type
|
||||||
|
|
||||||
|
for _, lbl := range labels {
|
||||||
|
if axis, found := cell.Axes[lbl]; !found {
|
||||||
|
newCell.Axes[lbl] = chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newCell.Axes[lbl] = axis
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cells[i] = dashboardCellResponse{
|
cells[i] = dashboardCellResponse{
|
||||||
DashboardCell: cell,
|
DashboardCell: newCell,
|
||||||
Links: dashboardCellLinks{
|
Links: dashboardCellLinks{
|
||||||
Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID),
|
Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
package server_test
|
package server_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/bouk/httprouter"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/influxdata/chronograf"
|
"github.com/influxdata/chronograf"
|
||||||
|
"github.com/influxdata/chronograf/mocks"
|
||||||
"github.com/influxdata/chronograf/server"
|
"github.com/influxdata/chronograf/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,13 +29,13 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
&chronograf.DashboardCell{
|
&chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "100"},
|
||||||
},
|
},
|
||||||
"y": chronograf.Axis{
|
"y": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "100"},
|
||||||
},
|
},
|
||||||
"y2": chronograf.Axis{
|
"y2": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "100"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -37,10 +46,10 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
&chronograf.DashboardCell{
|
&chronograf.DashboardCell{
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"axis of evil": chronograf.Axis{
|
"axis of evil": chronograf.Axis{
|
||||||
Bounds: [2]int64{666, 666},
|
Bounds: []string{"666", "666"},
|
||||||
},
|
},
|
||||||
"axis of awesome": chronograf.Axis{
|
"axis of awesome": chronograf.Axis{
|
||||||
Bounds: [2]int64{1337, 31337},
|
Bounds: []string{"1337", "31337"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -58,3 +67,139 @@ func Test_Cells_CorrectAxis(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Service_DashboardCells(t *testing.T) {
|
||||||
|
cellsTests := []struct {
|
||||||
|
name string
|
||||||
|
reqURL *url.URL
|
||||||
|
ctxParams map[string]string
|
||||||
|
mockResponse []chronograf.DashboardCell
|
||||||
|
expected []chronograf.DashboardCell
|
||||||
|
expectedCode int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"happy path",
|
||||||
|
&url.URL{
|
||||||
|
Path: "/chronograf/v1/dashboards/1/cells",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"id": "1",
|
||||||
|
},
|
||||||
|
[]chronograf.DashboardCell{},
|
||||||
|
[]chronograf.DashboardCell{},
|
||||||
|
http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell axes should always be \"x\", \"y\", and \"y2\"",
|
||||||
|
&url.URL{
|
||||||
|
Path: "/chronograf/v1/dashboards/1/cells",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"id": "1",
|
||||||
|
},
|
||||||
|
[]chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "CPU",
|
||||||
|
Type: "bar",
|
||||||
|
Queries: []chronograf.DashboardQuery{},
|
||||||
|
Axes: map[string]chronograf.Axis{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]chronograf.DashboardCell{
|
||||||
|
{
|
||||||
|
ID: "3899be5a-f6eb-4347-b949-de2f4fbea859",
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: 4,
|
||||||
|
H: 4,
|
||||||
|
Name: "CPU",
|
||||||
|
Type: "bar",
|
||||||
|
Queries: []chronograf.DashboardQuery{},
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"x": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
"y2": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
http.StatusOK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cellsTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// setup context with params
|
||||||
|
ctx := context.Background()
|
||||||
|
params := httprouter.Params{}
|
||||||
|
for k, v := range test.ctxParams {
|
||||||
|
params = append(params, httprouter.Param{k, v})
|
||||||
|
}
|
||||||
|
ctx = httprouter.WithParams(ctx, params)
|
||||||
|
|
||||||
|
// setup response recorder and request
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest("GET", test.reqURL.RequestURI(), strings.NewReader("")).WithContext(ctx)
|
||||||
|
|
||||||
|
// setup mock DashboardCells store and logger
|
||||||
|
tlog := &mocks.TestLogger{}
|
||||||
|
svc := &server.Service{
|
||||||
|
DashboardsStore: &mocks.DashboardsStore{
|
||||||
|
GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) {
|
||||||
|
return chronograf.Dashboard{
|
||||||
|
ID: chronograf.DashboardID(1),
|
||||||
|
Cells: test.mockResponse,
|
||||||
|
Templates: []chronograf.Template{},
|
||||||
|
Name: "empty dashboard",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Logger: tlog,
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoke DashboardCell handler
|
||||||
|
svc.DashboardCells(rr, req)
|
||||||
|
|
||||||
|
// setup frame to decode response into
|
||||||
|
respFrame := []struct {
|
||||||
|
chronograf.DashboardCell
|
||||||
|
Links json.RawMessage `json:"links"` // ignore links
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// decode response
|
||||||
|
resp := rr.Result()
|
||||||
|
|
||||||
|
if resp.StatusCode != test.expectedCode {
|
||||||
|
tlog.Dump(t)
|
||||||
|
t.Fatalf("%q - Status codes do not match. Want %d (%s), Got %d (%s)", test.name, test.expectedCode, http.StatusText(test.expectedCode), resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil {
|
||||||
|
t.Fatalf("%q - Error unmarshaling response body: err: %s", test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract actual
|
||||||
|
actual := []chronograf.DashboardCell{}
|
||||||
|
for _, rsp := range respFrame {
|
||||||
|
actual = append(actual, rsp.DashboardCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare actual and expected
|
||||||
|
if !cmp.Equal(actual, test.expected) {
|
||||||
|
t.Fatalf("%q - Dashboard Cells do not match: diff: %s", test.name, cmp.Diff(actual, test.expected))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,20 +28,19 @@ type getDashboardsResponse struct {
|
||||||
|
|
||||||
func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse {
|
func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse {
|
||||||
base := "/chronograf/v1/dashboards"
|
base := "/chronograf/v1/dashboards"
|
||||||
DashboardDefaults(&d)
|
dd := AddQueryConfigs(DashboardDefaults(d))
|
||||||
AddQueryConfigs(&d)
|
cells := newCellResponses(dd.ID, dd.Cells)
|
||||||
cells := newCellResponses(d.ID, d.Cells)
|
templates := newTemplateResponses(dd.ID, dd.Templates)
|
||||||
templates := newTemplateResponses(d.ID, d.Templates)
|
|
||||||
|
|
||||||
return &dashboardResponse{
|
return &dashboardResponse{
|
||||||
ID: d.ID,
|
ID: dd.ID,
|
||||||
Name: d.Name,
|
Name: dd.Name,
|
||||||
Cells: cells,
|
Cells: cells,
|
||||||
Templates: templates,
|
Templates: templates,
|
||||||
Links: dashboardLinks{
|
Links: dashboardLinks{
|
||||||
Self: fmt.Sprintf("%s/%d", base, d.ID),
|
Self: fmt.Sprintf("%s/%d", base, dd.ID),
|
||||||
Cells: fmt.Sprintf("%s/%d/cells", base, d.ID),
|
Cells: fmt.Sprintf("%s/%d/cells", base, dd.ID),
|
||||||
Templates: fmt.Sprintf("%s/%d/templates", base, d.ID),
|
Templates: fmt.Sprintf("%s/%d/templates", base, dd.ID),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,24 +228,36 @@ func ValidDashboardRequest(d *chronograf.Dashboard) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DashboardDefaults(d)
|
(*d) = DashboardDefaults(*d)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DashboardDefaults updates the dashboard with the default values
|
// DashboardDefaults updates the dashboard with the default values
|
||||||
// if none are specified
|
// if none are specified
|
||||||
func DashboardDefaults(d *chronograf.Dashboard) {
|
func DashboardDefaults(d chronograf.Dashboard) (newDash chronograf.Dashboard) {
|
||||||
|
newDash.ID = d.ID
|
||||||
|
newDash.Templates = d.Templates
|
||||||
|
newDash.Name = d.Name
|
||||||
|
newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells))
|
||||||
|
|
||||||
for i, c := range d.Cells {
|
for i, c := range d.Cells {
|
||||||
CorrectWidthHeight(&c)
|
CorrectWidthHeight(&c)
|
||||||
d.Cells[i] = c
|
newDash.Cells[i] = c
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddQueryConfigs updates all the celsl in the dashboard to have query config
|
// AddQueryConfigs updates all the celsl in the dashboard to have query config
|
||||||
// objects corresponding to their influxql queries.
|
// objects corresponding to their influxql queries.
|
||||||
func AddQueryConfigs(d *chronograf.Dashboard) {
|
func AddQueryConfigs(d chronograf.Dashboard) (newDash chronograf.Dashboard) {
|
||||||
|
newDash.ID = d.ID
|
||||||
|
newDash.Templates = d.Templates
|
||||||
|
newDash.Name = d.Name
|
||||||
|
newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells))
|
||||||
|
|
||||||
for i, c := range d.Cells {
|
for i, c := range d.Cells {
|
||||||
AddQueryConfig(&c)
|
AddQueryConfig(&c)
|
||||||
d.Cells[i] = c
|
newDash.Cells[i] = c
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ func TestDashboardDefaults(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
if DashboardDefaults(&tt.d); !reflect.DeepEqual(tt.d, tt.want) {
|
if actual := DashboardDefaults(tt.d); !reflect.DeepEqual(actual, tt.want) {
|
||||||
t.Errorf("%q. DashboardDefaults() = %v, want %v", tt.name, tt.d, tt.want)
|
t.Errorf("%q. DashboardDefaults() = %v, want %v", tt.name, tt.d, tt.want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,10 +222,11 @@ func Test_newDashboardResponse(t *testing.T) {
|
||||||
},
|
},
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "100"},
|
||||||
},
|
},
|
||||||
"y": chronograf.Axis{
|
"y": chronograf.Axis{
|
||||||
Bounds: [2]int64{2, 95},
|
Bounds: []string{"2", "95"},
|
||||||
|
Label: "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -268,10 +269,14 @@ func Test_newDashboardResponse(t *testing.T) {
|
||||||
},
|
},
|
||||||
Axes: map[string]chronograf.Axis{
|
Axes: map[string]chronograf.Axis{
|
||||||
"x": chronograf.Axis{
|
"x": chronograf.Axis{
|
||||||
Bounds: [2]int64{0, 100},
|
Bounds: []string{"0", "100"},
|
||||||
},
|
},
|
||||||
"y": chronograf.Axis{
|
"y": chronograf.Axis{
|
||||||
Bounds: [2]int64{2, 95},
|
Bounds: []string{"2", "95"},
|
||||||
|
Label: "foo",
|
||||||
|
},
|
||||||
|
"y2": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -284,6 +289,17 @@ func Test_newDashboardResponse(t *testing.T) {
|
||||||
ID: "b",
|
ID: "b",
|
||||||
W: 4,
|
W: 4,
|
||||||
H: 4,
|
H: 4,
|
||||||
|
Axes: map[string]chronograf.Axis{
|
||||||
|
"x": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
"y": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
"y2": chronograf.Axis{
|
||||||
|
Bounds: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
Queries: []chronograf.DashboardQuery{
|
Queries: []chronograf.DashboardQuery{
|
||||||
{
|
{
|
||||||
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
|
Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m",
|
||||||
|
|
|
@ -50,7 +50,7 @@ type Server struct {
|
||||||
KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"`
|
KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"`
|
||||||
KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"`
|
KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"`
|
||||||
|
|
||||||
NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"hunter2\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
|
NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"`
|
||||||
|
|
||||||
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
Develop bool `short:"d" long:"develop" description:"Run server in develop mode."`
|
||||||
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"`
|
||||||
|
|
|
@ -33,4 +33,4 @@ yarn upgrade packageName
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
Tests can be run via command line with `npm test`, from within the `/ui` directory. For more detailed reporting, use `npm test -- --reporters=verbose`.
|
Tests can be run via command line with `yarn test`, from within the `/ui` directory. For more detailed reporting, use `yarn test -- --reporters=verbose`.
|
||||||
|
|
|
@ -16,7 +16,7 @@ module.exports = function(config) {
|
||||||
'spec/index.js': ['webpack', 'sourcemap'],
|
'spec/index.js': ['webpack', 'sourcemap'],
|
||||||
},
|
},
|
||||||
// For more detailed reporting on tests, you can add 'verbose' and/or 'progress'.
|
// For more detailed reporting on tests, you can add 'verbose' and/or 'progress'.
|
||||||
// This can also be done via the command line with `npm test -- --reporters=verbose`.
|
// This can also be done via the command line with `yarn test -- --reporters=verbose`.
|
||||||
reporters: ['dots'],
|
reporters: ['dots'],
|
||||||
webpack: {
|
webpack: {
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "chronograf-ui",
|
"name": "chronograf-ui",
|
||||||
"version": "1.3.5-0",
|
"version": "1.3.6-0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
@ -9,13 +9,13 @@
|
||||||
"url": "github:influxdata/chronograf"
|
"url": "github:influxdata/chronograf"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js",
|
"build": "yarn run clean && env NODE_ENV=production node_modules/webpack/bin/webpack.js -p --config ./webpack/prodConfig.js",
|
||||||
"build:dev": "node_modules/webpack/bin/webpack.js --config ./webpack/devConfig.js",
|
"build:dev": "node_modules/webpack/bin/webpack.js --config ./webpack/devConfig.js",
|
||||||
"start": "node_modules/webpack/bin/webpack.js -w --config ./webpack/devConfig.js",
|
"start": "node_modules/webpack/bin/webpack.js -w --config ./webpack/devConfig.js",
|
||||||
"lint": "node_modules/eslint/bin/eslint.js src/",
|
"lint": "node_modules/eslint/bin/eslint.js src/",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
"test:lint": "npm run lint; npm run test",
|
"test:lint": "yarn run lint; yarn run test",
|
||||||
"test:dev": "nodemon --exec npm run test:lint",
|
"test:dev": "nodemon --exec yarn run test:lint",
|
||||||
"clean": "rm -rf build",
|
"clean": "rm -rf build",
|
||||||
"storybook": "node ./storybook",
|
"storybook": "node ./storybook",
|
||||||
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
|
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
|
||||||
|
|
|
@ -60,6 +60,7 @@ const c1 = {
|
||||||
w: 4,
|
w: 4,
|
||||||
h: 4,
|
h: 4,
|
||||||
id: 1,
|
id: 1,
|
||||||
|
i: 'im-a-cell-id-index',
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
name: 'Gigawatts',
|
name: 'Gigawatts',
|
||||||
}
|
}
|
||||||
|
@ -71,18 +72,6 @@ const editingCell = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const cells = [c1]
|
const cells = [c1]
|
||||||
const tempVar = {
|
|
||||||
...d1.templates[0],
|
|
||||||
id: '1',
|
|
||||||
type: 'measurement',
|
|
||||||
label: 'test query',
|
|
||||||
tempVar: '$HOSTS',
|
|
||||||
query: {
|
|
||||||
db: 'db1',
|
|
||||||
text: 'SHOW TAGS WHERE HUNTER = "coo"',
|
|
||||||
},
|
|
||||||
values: ['h1', 'h2', 'h3'],
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('DataExplorer.Reducers.UI', () => {
|
describe('DataExplorer.Reducers.UI', () => {
|
||||||
it('can load the dashboards', () => {
|
it('can load the dashboards', () => {
|
||||||
|
|
|
@ -17,10 +17,18 @@ describe('getRangeForDygraphSpec', () => {
|
||||||
|
|
||||||
it('does not get range when a range is provided', () => {
|
it('does not get range when a range is provided', () => {
|
||||||
const timeSeries = [[date, min], [date, max], [date, mid]]
|
const timeSeries = [[date, min], [date, max], [date, mid]]
|
||||||
const providedRange = [0, 4]
|
const providedRange = ['0', '4']
|
||||||
const actual = getRange(timeSeries, providedRange)
|
const actual = getRange(timeSeries, providedRange)
|
||||||
|
|
||||||
expect(actual).to.deep.equal(providedRange)
|
expect(actual).to.deep.equal([0, 4])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not use the user submitted range if they are equal', () => {
|
||||||
|
const timeSeries = [[date, min], [date, max], [date, mid]]
|
||||||
|
const providedRange = ['0', '0']
|
||||||
|
const actual = getRange(timeSeries, providedRange)
|
||||||
|
|
||||||
|
expect(actual).to.deep.equal([min, max])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets the range for multiple timeSeries', () => {
|
it('gets the range for multiple timeSeries', () => {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React, {Component, PropTypes} from 'react'
|
import React, {Component, PropTypes} from 'react'
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {Link} from 'react-router'
|
import {Link} from 'react-router'
|
||||||
|
import uuid from 'node-uuid'
|
||||||
|
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
|
|
||||||
|
@ -130,10 +132,7 @@ class AlertsTable extends Component {
|
||||||
>
|
>
|
||||||
{alerts.map(({name, level, time, host, value}) => {
|
{alerts.map(({name, level, time, host, value}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="alert-history-table--tr" key={uuid.v4()}>
|
||||||
className="alert-history-table--tr"
|
|
||||||
key={`${name}-${level}-${time}-${host}-${value}`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="alert-history-table--td"
|
className="alert-history-table--td"
|
||||||
style={{width: colName}}
|
style={{width: colName}}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// TODO: add logic for for Prefix, Suffix, Scale, and Multiplier
|
||||||
|
const AxesOptions = ({onSetRange, onSetLabel, axes}) => {
|
||||||
|
const min = _.get(axes, ['y', 'bounds', '0'], '')
|
||||||
|
const max = _.get(axes, ['y', 'bounds', '1'], '')
|
||||||
|
const label = _.get(axes, ['y', 'label'], '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="display-options--cell">
|
||||||
|
<h5 className="display-options--header">Y Axis Controls</h5>
|
||||||
|
<form autoComplete="off" style={{margin: '0 -6px'}}>
|
||||||
|
<div className="form-group col-sm-12">
|
||||||
|
<label htmlFor="prefix">Title</label>
|
||||||
|
<input
|
||||||
|
className="form-control input-sm"
|
||||||
|
type="text"
|
||||||
|
name="label"
|
||||||
|
id="label"
|
||||||
|
value={label}
|
||||||
|
onChange={onSetLabel}
|
||||||
|
placeholder="auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-sm-6">
|
||||||
|
<label htmlFor="min">Min</label>
|
||||||
|
<input
|
||||||
|
className="form-control input-sm"
|
||||||
|
type="number"
|
||||||
|
name="min"
|
||||||
|
id="min"
|
||||||
|
value={min}
|
||||||
|
onChange={onSetRange}
|
||||||
|
placeholder="auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-sm-6">
|
||||||
|
<label htmlFor="max">Max</label>
|
||||||
|
<input
|
||||||
|
className="form-control input-sm"
|
||||||
|
type="number"
|
||||||
|
name="max"
|
||||||
|
id="max"
|
||||||
|
value={max}
|
||||||
|
onChange={onSetRange}
|
||||||
|
placeholder="auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="display-options--footnote">
|
||||||
|
Values left blank will be set automatically
|
||||||
|
</p>
|
||||||
|
{/* <div className="form-group col-sm-6">
|
||||||
|
<label htmlFor="prefix">Labels Prefix</label>
|
||||||
|
<input
|
||||||
|
className="form-control input-sm"
|
||||||
|
type="text"
|
||||||
|
name="prefix"
|
||||||
|
id="prefix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-sm-6">
|
||||||
|
<label htmlFor="prefix">Labels Suffix</label>
|
||||||
|
<input
|
||||||
|
className="form-control input-sm"
|
||||||
|
type="text"
|
||||||
|
name="suffix"
|
||||||
|
id="suffix"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-sm-6">
|
||||||
|
<label>Labels Format</label>
|
||||||
|
<ul className="nav nav-tablist nav-tablist-sm">
|
||||||
|
<li className="active">K/M/B</li>
|
||||||
|
<li>K/M/G</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="form-group col-sm-6">
|
||||||
|
<label>Scale</label>
|
||||||
|
<ul className="nav nav-tablist nav-tablist-sm">
|
||||||
|
<li className="active">Linear</li>
|
||||||
|
<li>Logarithmic</li>
|
||||||
|
</ul>
|
||||||
|
</div> */}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const {arrayOf, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
AxesOptions.propTypes = {
|
||||||
|
onSetRange: func.isRequired,
|
||||||
|
onSetLabel: func.isRequired,
|
||||||
|
axes: shape({
|
||||||
|
y: shape({
|
||||||
|
bounds: arrayOf(string),
|
||||||
|
label: string,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AxesOptions
|
|
@ -7,12 +7,15 @@ import ResizeContainer from 'shared/components/ResizeContainer'
|
||||||
import QueryMaker from 'src/data_explorer/components/QueryMaker'
|
import QueryMaker from 'src/data_explorer/components/QueryMaker'
|
||||||
import Visualization from 'src/data_explorer/components/Visualization'
|
import Visualization from 'src/data_explorer/components/Visualization'
|
||||||
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
||||||
|
import DisplayOptions from 'src/dashboards/components/DisplayOptions'
|
||||||
|
|
||||||
import * as queryModifiers from 'src/utils/queryTransitions'
|
import * as queryModifiers from 'src/utils/queryTransitions'
|
||||||
|
|
||||||
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
||||||
import buildInfluxQLQuery from 'utils/influxql'
|
import buildInfluxQLQuery from 'utils/influxql'
|
||||||
import {getQueryConfig} from 'shared/apis'
|
import {getQueryConfig} from 'shared/apis'
|
||||||
|
|
||||||
|
import {buildYLabel} from 'shared/presenters'
|
||||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||||
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
|
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
|
||||||
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
|
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
|
||||||
|
@ -22,17 +25,17 @@ class CellEditorOverlay extends Component {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.queryStateReducer = ::this.queryStateReducer
|
this.queryStateReducer = ::this.queryStateReducer
|
||||||
|
|
||||||
this.handleAddQuery = ::this.handleAddQuery
|
this.handleAddQuery = ::this.handleAddQuery
|
||||||
this.handleDeleteQuery = ::this.handleDeleteQuery
|
this.handleDeleteQuery = ::this.handleDeleteQuery
|
||||||
|
|
||||||
this.handleSaveCell = ::this.handleSaveCell
|
this.handleSaveCell = ::this.handleSaveCell
|
||||||
|
|
||||||
this.handleSelectGraphType = ::this.handleSelectGraphType
|
this.handleSelectGraphType = ::this.handleSelectGraphType
|
||||||
|
this.handleClickDisplayOptionsTab = ::this.handleClickDisplayOptionsTab
|
||||||
this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex
|
this.handleSetActiveQueryIndex = ::this.handleSetActiveQueryIndex
|
||||||
this.handleEditRawText = ::this.handleEditRawText
|
this.handleEditRawText = ::this.handleEditRawText
|
||||||
|
this.handleSetYAxisBounds = ::this.handleSetYAxisBounds
|
||||||
|
this.handleSetLabel = ::this.handleSetLabel
|
||||||
|
|
||||||
const {cell: {name, type, queries}} = props
|
const {cell: {name, type, queries, axes}} = props
|
||||||
|
|
||||||
const queriesWorkingDraft = _.cloneDeep(
|
const queriesWorkingDraft = _.cloneDeep(
|
||||||
queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()}))
|
queries.map(({queryConfig}) => ({...queryConfig, id: uuid.v4()}))
|
||||||
|
@ -43,9 +46,26 @@ class CellEditorOverlay extends Component {
|
||||||
cellWorkingType: type,
|
cellWorkingType: type,
|
||||||
queriesWorkingDraft,
|
queriesWorkingDraft,
|
||||||
activeQueryIndex: 0,
|
activeQueryIndex: 0,
|
||||||
|
isDisplayOptionsTabActive: false,
|
||||||
|
axes: this.setDefaultLabels(axes, queries),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDefaultLabels(axes, queries) {
|
||||||
|
if (!queries.length) {
|
||||||
|
return axes
|
||||||
|
}
|
||||||
|
|
||||||
|
if (axes.y.label) {
|
||||||
|
return axes
|
||||||
|
}
|
||||||
|
|
||||||
|
const q = queries[0].queryConfig
|
||||||
|
const label = buildYLabel(q)
|
||||||
|
|
||||||
|
return {...axes, y: {...axes.y, label}}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const {status, queryID} = this.props.queryStatus
|
const {status, queryID} = this.props.queryStatus
|
||||||
const nextStatus = nextProps.queryStatus
|
const nextStatus = nextProps.queryStatus
|
||||||
|
@ -73,6 +93,24 @@ class CellEditorOverlay extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSetYAxisBounds(e) {
|
||||||
|
const {min, max} = e.target.form
|
||||||
|
const {axes} = this.state
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
axes: {...axes, y: {...axes.y, bounds: [min.value, max.value]}},
|
||||||
|
})
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSetLabel(e) {
|
||||||
|
const {label} = e.target.form
|
||||||
|
const {axes} = this.state
|
||||||
|
|
||||||
|
this.setState({axes: {...axes, y: {...axes.y, label: label.value}}})
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
handleAddQuery(options) {
|
handleAddQuery(options) {
|
||||||
const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options)
|
const newQuery = Object.assign({}, defaultQueryConfig(uuid.v4()), options)
|
||||||
const nextQueries = this.state.queriesWorkingDraft.concat(newQuery)
|
const nextQueries = this.state.queriesWorkingDraft.concat(newQuery)
|
||||||
|
@ -87,31 +125,44 @@ class CellEditorOverlay extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveCell() {
|
handleSaveCell() {
|
||||||
const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state
|
const {
|
||||||
|
queriesWorkingDraft,
|
||||||
|
cellWorkingType: type,
|
||||||
|
cellWorkingName: name,
|
||||||
|
axes,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
const {cell} = this.props
|
const {cell} = this.props
|
||||||
|
|
||||||
const newCell = _.cloneDeep(cell)
|
const queries = queriesWorkingDraft.map(q => {
|
||||||
newCell.name = cellWorkingName
|
|
||||||
newCell.type = cellWorkingType
|
|
||||||
newCell.queries = queriesWorkingDraft.map(q => {
|
|
||||||
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
|
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
|
||||||
const query = q.rawText || buildInfluxQLQuery(timeRange, q)
|
const query = q.rawText || buildInfluxQLQuery(timeRange, q)
|
||||||
const label = q.rawText ? '' : `${q.measurement}.${q.fields[0].field}`
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryConfig: q,
|
queryConfig: q,
|
||||||
query,
|
query,
|
||||||
label,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.props.onSave(newCell)
|
this.props.onSave({
|
||||||
|
...cell,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
queries,
|
||||||
|
axes,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectGraphType(graphType) {
|
handleSelectGraphType(graphType) {
|
||||||
this.setState({cellWorkingType: graphType})
|
this.setState({cellWorkingType: graphType})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClickDisplayOptionsTab(isDisplayOptionsTabActive) {
|
||||||
|
return () => {
|
||||||
|
this.setState({isDisplayOptionsTabActive})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleSetActiveQueryIndex(activeQueryIndex) {
|
handleSetActiveQueryIndex(activeQueryIndex) {
|
||||||
this.setState({activeQueryIndex})
|
this.setState({activeQueryIndex})
|
||||||
}
|
}
|
||||||
|
@ -146,7 +197,9 @@ class CellEditorOverlay extends Component {
|
||||||
activeQueryIndex,
|
activeQueryIndex,
|
||||||
cellWorkingName,
|
cellWorkingName,
|
||||||
cellWorkingType,
|
cellWorkingType,
|
||||||
|
isDisplayOptionsTabActive,
|
||||||
queriesWorkingDraft,
|
queriesWorkingDraft,
|
||||||
|
axes,
|
||||||
} = this.state
|
} = this.state
|
||||||
|
|
||||||
const queryActions = {
|
const queryActions = {
|
||||||
|
@ -177,27 +230,36 @@ class CellEditorOverlay extends Component {
|
||||||
cellType={cellWorkingType}
|
cellType={cellWorkingType}
|
||||||
cellName={cellWorkingName}
|
cellName={cellWorkingName}
|
||||||
editQueryStatus={editQueryStatus}
|
editQueryStatus={editQueryStatus}
|
||||||
|
axes={axes}
|
||||||
views={[]}
|
views={[]}
|
||||||
/>
|
/>
|
||||||
<div className="overlay-technology--editor">
|
<div className="overlay-technology--editor">
|
||||||
<OverlayControls
|
<OverlayControls
|
||||||
selectedGraphType={cellWorkingType}
|
isDisplayOptionsTabActive={isDisplayOptionsTabActive}
|
||||||
onSelectGraphType={this.handleSelectGraphType}
|
onClickDisplayOptions={this.handleClickDisplayOptionsTab}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSave={this.handleSaveCell}
|
onSave={this.handleSaveCell}
|
||||||
isSavable={queriesWorkingDraft.every(isQuerySavable)}
|
isSavable={queriesWorkingDraft.every(isQuerySavable)}
|
||||||
/>
|
/>
|
||||||
<QueryMaker
|
{isDisplayOptionsTabActive
|
||||||
source={source}
|
? <DisplayOptions
|
||||||
templates={templates}
|
selectedGraphType={cellWorkingType}
|
||||||
queries={queriesWorkingDraft}
|
onSelectGraphType={this.handleSelectGraphType}
|
||||||
actions={queryActions}
|
onSetRange={this.handleSetYAxisBounds}
|
||||||
autoRefresh={autoRefresh}
|
onSetLabel={this.handleSetLabel}
|
||||||
timeRange={timeRange}
|
axes={axes}
|
||||||
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
/>
|
||||||
onDeleteQuery={this.handleDeleteQuery}
|
: <QueryMaker
|
||||||
activeQueryIndex={activeQueryIndex}
|
source={source}
|
||||||
/>
|
templates={templates}
|
||||||
|
queries={queriesWorkingDraft}
|
||||||
|
actions={queryActions}
|
||||||
|
autoRefresh={autoRefresh}
|
||||||
|
timeRange={timeRange}
|
||||||
|
setActiveQueryIndex={this.handleSetActiveQueryIndex}
|
||||||
|
onDeleteQuery={this.handleDeleteQuery}
|
||||||
|
activeQueryIndex={activeQueryIndex}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
</ResizeContainer>
|
</ResizeContainer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -232,6 +294,7 @@ CellEditorOverlay.propTypes = {
|
||||||
queryID: string,
|
queryID: string,
|
||||||
status: shape({}),
|
status: shape({}),
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
dashboardID: string.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CellEditorOverlay
|
export default CellEditorOverlay
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
|
||||||
|
import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector'
|
||||||
|
import AxesOptions from 'src/dashboards/components/AxesOptions'
|
||||||
|
|
||||||
|
const DisplayOptions = ({
|
||||||
|
selectedGraphType,
|
||||||
|
onSelectGraphType,
|
||||||
|
onSetLabel,
|
||||||
|
onSetRange,
|
||||||
|
axes,
|
||||||
|
}) =>
|
||||||
|
<div className="display-options">
|
||||||
|
<GraphTypeSelector
|
||||||
|
selectedGraphType={selectedGraphType}
|
||||||
|
onSelectGraphType={onSelectGraphType}
|
||||||
|
/>
|
||||||
|
<AxesOptions onSetLabel={onSetLabel} onSetRange={onSetRange} axes={axes} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
DisplayOptions.propTypes = {
|
||||||
|
selectedGraphType: string.isRequired,
|
||||||
|
onSelectGraphType: func.isRequired,
|
||||||
|
onSetRange: func.isRequired,
|
||||||
|
onSetLabel: func.isRequired,
|
||||||
|
axes: shape({}).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DisplayOptions
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
import {graphTypes} from 'src/dashboards/graphics/graph'
|
||||||
|
|
||||||
|
const GraphTypeSelector = ({selectedGraphType, onSelectGraphType}) =>
|
||||||
|
<div className="display-options--cell display-options--cellx2">
|
||||||
|
<h5 className="display-options--header">Visualization Type</h5>
|
||||||
|
<div className="viz-type-selector">
|
||||||
|
{graphTypes.map(graphType =>
|
||||||
|
<div
|
||||||
|
key={graphType.type}
|
||||||
|
className={classnames('viz-type-selector--option', {
|
||||||
|
active: graphType.type === selectedGraphType,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div onClick={() => onSelectGraphType(graphType.type)}>
|
||||||
|
{graphType.graphic}
|
||||||
|
<p>
|
||||||
|
{graphType.menuOption}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {func, string} = PropTypes
|
||||||
|
|
||||||
|
GraphTypeSelector.propTypes = {
|
||||||
|
selectedGraphType: string.isRequired,
|
||||||
|
onSelectGraphType: func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphTypeSelector
|
|
@ -3,51 +3,51 @@ import classnames from 'classnames'
|
||||||
|
|
||||||
import ConfirmButtons from 'shared/components/ConfirmButtons'
|
import ConfirmButtons from 'shared/components/ConfirmButtons'
|
||||||
|
|
||||||
import graphTypes from 'hson!shared/data/graphTypes.hson'
|
const OverlayControls = ({
|
||||||
|
onCancel,
|
||||||
const OverlayControls = props => {
|
onSave,
|
||||||
const {
|
isDisplayOptionsTabActive,
|
||||||
onCancel,
|
onClickDisplayOptions,
|
||||||
onSave,
|
isSavable,
|
||||||
selectedGraphType,
|
}) =>
|
||||||
onSelectGraphType,
|
<div className="overlay-controls">
|
||||||
isSavable,
|
<h3 className="overlay--graph-name">Cell Editor</h3>
|
||||||
} = props
|
<ul className="nav nav-tablist nav-tablist-sm">
|
||||||
return (
|
<li
|
||||||
<div className="overlay-controls">
|
key="queries"
|
||||||
<h3 className="overlay--graph-name">Cell Editor</h3>
|
className={classnames({
|
||||||
<div className="overlay-controls--right">
|
active: !isDisplayOptionsTabActive,
|
||||||
<p>Visualization Type:</p>
|
})}
|
||||||
<ul className="nav nav-tablist nav-tablist-sm">
|
onClick={onClickDisplayOptions(false)}
|
||||||
{graphTypes.map(graphType =>
|
>
|
||||||
<li
|
Queries
|
||||||
key={graphType.type}
|
</li>
|
||||||
className={classnames({
|
<li
|
||||||
active: graphType.type === selectedGraphType,
|
key="displayOptions"
|
||||||
})}
|
className={classnames({
|
||||||
onClick={() => onSelectGraphType(graphType.type)}
|
active: isDisplayOptionsTabActive,
|
||||||
>
|
})}
|
||||||
{graphType.menuOption}
|
onClick={onClickDisplayOptions(true)}
|
||||||
</li>
|
>
|
||||||
)}
|
Display Options
|
||||||
</ul>
|
</li>
|
||||||
<ConfirmButtons
|
</ul>
|
||||||
onCancel={onCancel}
|
<div className="overlay-controls--right">
|
||||||
onConfirm={onSave}
|
<ConfirmButtons
|
||||||
isDisabled={!isSavable}
|
onCancel={onCancel}
|
||||||
/>
|
onConfirm={onSave}
|
||||||
</div>
|
isDisabled={!isSavable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
const {func, string, bool} = PropTypes
|
const {func, bool} = PropTypes
|
||||||
|
|
||||||
OverlayControls.propTypes = {
|
OverlayControls.propTypes = {
|
||||||
onCancel: func.isRequired,
|
onCancel: func.isRequired,
|
||||||
onSave: func.isRequired,
|
onSave: func.isRequired,
|
||||||
selectedGraphType: string.isRequired,
|
isDisplayOptionsTabActive: bool.isRequired,
|
||||||
onSelectGraphType: func.isRequired,
|
onClickDisplayOptions: func.isRequired,
|
||||||
isSavable: bool,
|
isSavable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,7 +209,11 @@ class DashboardPage extends Component {
|
||||||
const dygraphs = [...this.state.dygraphs, dygraph]
|
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||||
const {dashboards, params} = this.props
|
const {dashboards, params} = this.props
|
||||||
const dashboard = dashboards.find(d => d.id === +params.dashboardID)
|
const dashboard = dashboards.find(d => d.id === +params.dashboardID)
|
||||||
if (dashboard && dygraphs.length === dashboard.cells.length) {
|
if (
|
||||||
|
dashboard &&
|
||||||
|
dygraphs.length === dashboard.cells.length &&
|
||||||
|
dashboard.cells.length > 1
|
||||||
|
) {
|
||||||
Dygraph.synchronize(dygraphs, {
|
Dygraph.synchronize(dygraphs, {
|
||||||
selection: true,
|
selection: true,
|
||||||
zoom: false,
|
zoom: false,
|
||||||
|
@ -248,7 +252,7 @@ class DashboardPage extends Component {
|
||||||
inPresentationMode,
|
inPresentationMode,
|
||||||
handleChooseAutoRefresh,
|
handleChooseAutoRefresh,
|
||||||
handleClickPresentationButton,
|
handleClickPresentationButton,
|
||||||
params: {sourceID},
|
params: {sourceID, dashboardID},
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const lowerType = lower && lower.includes('Z') ? 'timeStamp' : 'constant'
|
const lowerType = lower && lower.includes('Z') ? 'timeStamp' : 'constant'
|
||||||
|
@ -322,6 +326,7 @@ class DashboardPage extends Component {
|
||||||
{selectedCell
|
{selectedCell
|
||||||
? <CellEditorOverlay
|
? <CellEditorOverlay
|
||||||
source={source}
|
source={source}
|
||||||
|
dashboardID={dashboardID}
|
||||||
templates={templatesIncludingDashTime}
|
templates={templatesIncludingDashTime}
|
||||||
cell={selectedCell}
|
cell={selectedCell}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
|
@ -372,8 +377,8 @@ class DashboardPage extends Component {
|
||||||
autoRefresh={autoRefresh}
|
autoRefresh={autoRefresh}
|
||||||
synchronizer={this.synchronizer}
|
synchronizer={this.synchronizer}
|
||||||
onAddCell={this.handleAddCell}
|
onAddCell={this.handleAddCell}
|
||||||
inPresentationMode={inPresentationMode}
|
|
||||||
onEditCell={this.handleEditDashboardCell}
|
onEditCell={this.handleEditDashboardCell}
|
||||||
|
inPresentationMode={inPresentationMode}
|
||||||
onPositionChange={this.handleUpdatePosition}
|
onPositionChange={this.handleUpdatePosition}
|
||||||
onDeleteCell={this.handleDeleteDashboardCell}
|
onDeleteCell={this.handleDeleteDashboardCell}
|
||||||
onUpdateCell={this.handleUpdateDashboardCell}
|
onUpdateCell={this.handleUpdateDashboardCell}
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const graphTypes = [
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
menuOption: 'Line',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Line"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
points="5,122.2 63,81.2 121,95.5 179,40.2 237,108.5 295,83.2"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-a"
|
||||||
|
points="5,122.2 5,145 295,145 295,83.2 237,108.5 179,40.2 121,95.5 63,81.2"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
points="5,88.5 63,95 121,36.2 179,19 237,126.2 295,100.8"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
points="5,88.5 5,145 295,145 295,100.8 237,126.2 179,19 121,36.2 63,95"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-c"
|
||||||
|
points="5,76.2 63,90.2 121,59.2 179,31.5 237,79.8 295,93.5"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-c"
|
||||||
|
points="5,76.2 5,145 295,145 295,93.5 237,79.8 179,31.5 121,59.2 63,90.2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line-stacked',
|
||||||
|
menuOption: 'Stacked',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="LineStacked"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
points="5,97.5 63,111.8 121,36.2 179,51 237,102.9 295,70.2"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
points="5,58.8 63,81.2 121,5 179,40.2 237,96.2 295,49"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-c"
|
||||||
|
points="5,107.5 63,128.5 121,79.8 179,76.5 237,113.2 295,93.5"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-a"
|
||||||
|
points="179,51 121,36.2 63,111.8 5,97.5 5,107.5 63,128.5 121,79.8 179,76.5 237,113.2 295,93.5 295,70.2 237,102.9"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
points="237,96.2 179,40.2 121,5 63,81.2 5,58.8 5,97.5 63,111.8 121,36.2 179,51 237,102.9 295,70.2 295,49"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-c"
|
||||||
|
points="179,76.5 121,79.8 63,128.5 5,107.5 5,145 295,145 295,93.5 237,113.2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line-stepplot',
|
||||||
|
menuOption: 'Step-Plot',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="StepPlot"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-a"
|
||||||
|
points="295,85.5 266,85.5 266,108.5 208,108.5 208,94.5 150,94.5 150,41 92,41 92,66.6 34,66.6 34,54.8 5,54.8 5,145 295,145"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
points="5,54.8 34,54.8 34,66.6 92,66.6 92,41 150,41 150,94.5 208,94.5 208,108.5 266,108.5 266,85.5 295,85.5"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
points="34,111 34,85.8 92,85.8 92,5 150,5 150,24.5 208,24.5 208,128.2 266,128.2 266,75 295,75 295,145 5,145 5,111"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
points="5,111 34,111 34,85.8 92,85.8 92,5 150,5 150,24.5 208,24.5 208,128.2 266,128.2 266,75 295,75"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'single-stat',
|
||||||
|
menuOption: 'SingleStat',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="SingleStat"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M243.3,39.6h-37.9v32.7c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4v26.8c0,6.3-5.1,11.4-11.4,11.4 h-15.2c-6.3,0-11.4-5.1-11.4-11.4V88.6"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
points="94.6,89.1 56.7,89.1 83.2,39.6 83.2,110.4 "
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M144.2,77.8c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V50.9c0-6.3,5.1-11.4,11.4-11.4h15.2 c6.3,0,11.4,5.1,11.4,11.4v48.1c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M155.8,50.9c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4c0,24.1-37.9,24.8-37.9,59.5h37.9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line-plus-single-stat',
|
||||||
|
menuOption: 'Line + Stat',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="LineAndSingleStat"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
points="5,122.2 5,145 295,145 295,38.3 237,41.3 179,50 121,126.3 63,90.7"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
points="5,122.2 63,90.7 121,126.3 179,50 237,41.3 295,38.3"
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-c"
|
||||||
|
points="5,26.2 5,145 295,145 295,132.3 239.3,113.3 179,15 121,25 63,71.7"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-c"
|
||||||
|
points="5,26.2 63,71.7 121,25 179,15 239.3,113.3 295,132.3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M243.3,39.6h-37.9v32.7c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4v26.8 c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V88.6"
|
||||||
|
/>
|
||||||
|
<polyline
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
points="94.6,89.1 56.7,89.1 83.2,39.6 83.2,110.4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M144.2,77.8c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4V50.9c0-6.3,5.1-11.4,11.4-11.4h15.2 c6.3,0,11.4,5.1,11.4,11.4v48.1c0,6.3-5.1,11.4-11.4,11.4h-15.2c-6.3,0-11.4-5.1-11.4-11.4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M155.8,50.9c0-6.3,5.1-11.4,11.4-11.4h15.2c6.3,0,11.4,5.1,11.4,11.4c0,24.1-37.9,24.8-37.9,59.5h37.9"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
menuOption: 'Bar',
|
||||||
|
graphic: (
|
||||||
|
<div className="viz-type-selector--graphic">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="Bar"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 300 150"
|
||||||
|
preserveAspectRatio="none meet"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-a"
|
||||||
|
d="M145,7c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v136c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V7z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-c"
|
||||||
|
d="M195,57c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v86c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V57z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
d="M245,117c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v26c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V117z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-a"
|
||||||
|
d="M295,107c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v36c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V107z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-b"
|
||||||
|
d="M95,87c0-1.1-0.9-2-2-2H57c-1.1,0-2,0.9-2,2v56c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V87z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-fill graphic-fill-c"
|
||||||
|
d="M45,130c0-1.1-0.9-2-2-2H7c-1.1,0-2,0.9-2,2v13c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V130z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M145,7c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v136c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V7z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-c"
|
||||||
|
d="M195,57c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v86c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V57z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
d="M245,117c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v26c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V117z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-a"
|
||||||
|
d="M295,107c0-1.1-0.9-2-2-2h-36c-1.1,0-2,0.9-2,2v36c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V107z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-b"
|
||||||
|
d="M95,87c0-1.1-0.9-2-2-2H57c-1.1,0-2,0.9-2,2v56c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V87z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="viz-type-selector--graphic-line graphic-line-c"
|
||||||
|
d="M45,130c0-1.1-0.9-2-2-2H7c-1.1,0-2,0.9-2,2v13c0,1.1,0.9,2,2,2h36c1.1,0,2-0.9,2-2V130z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
|
@ -12,7 +12,6 @@ import {Table, Column, Cell} from 'fixed-data-table'
|
||||||
|
|
||||||
const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes
|
const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes
|
||||||
|
|
||||||
const defaultTableHeight = 1000
|
|
||||||
const emptySeries = {columns: [], values: []}
|
const emptySeries = {columns: [], values: []}
|
||||||
|
|
||||||
const CustomCell = React.createClass({
|
const CustomCell = React.createClass({
|
||||||
|
@ -64,7 +63,7 @@ const ChronoTable = React.createClass({
|
||||||
|
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
height: defaultTableHeight,
|
height: 500,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -139,11 +138,11 @@ const ChronoTable = React.createClass({
|
||||||
const maximumTabsCount = 11
|
const maximumTabsCount = 11
|
||||||
// adjust height to proper value by subtracting the heights of the UI around it
|
// adjust height to proper value by subtracting the heights of the UI around it
|
||||||
// tab height, graph-container vertical padding, graph-heading height, multitable-header height
|
// tab height, graph-container vertical padding, graph-heading height, multitable-header height
|
||||||
const stylePixelOffset = 136
|
|
||||||
const rowHeight = 34
|
|
||||||
const defaultColumnWidth = 200
|
|
||||||
const headerHeight = 30
|
|
||||||
const minWidth = 70
|
const minWidth = 70
|
||||||
|
const rowHeight = 34
|
||||||
|
const headerHeight = 30
|
||||||
|
const stylePixelOffset = 125
|
||||||
|
const defaultColumnWidth = 200
|
||||||
const styleAdjustedHeight = height - stylePixelOffset
|
const styleAdjustedHeight = height - stylePixelOffset
|
||||||
const width =
|
const width =
|
||||||
columns && columns.length > 1 ? defaultColumnWidth : containerWidth
|
columns && columns.length > 1 ? defaultColumnWidth : containerWidth
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import React, {PropTypes} from 'react'
|
import React, {PropTypes} from 'react'
|
||||||
|
|
||||||
import Table from './Table'
|
import Table from './Table'
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh'
|
import RefreshingGraph from 'shared/components/RefreshingGraph'
|
||||||
import LineGraph from 'shared/components/LineGraph'
|
|
||||||
import SingleStat from 'shared/components/SingleStat'
|
|
||||||
const RefreshingLineGraph = AutoRefresh(LineGraph)
|
|
||||||
const RefreshingSingleStat = AutoRefresh(SingleStat)
|
|
||||||
|
|
||||||
const VisView = ({
|
const VisView = ({
|
||||||
|
axes,
|
||||||
view,
|
view,
|
||||||
queries,
|
queries,
|
||||||
cellType,
|
cellType,
|
||||||
|
@ -16,7 +13,7 @@ const VisView = ({
|
||||||
heightPixels,
|
heightPixels,
|
||||||
editQueryStatus,
|
editQueryStatus,
|
||||||
activeQueryIndex,
|
activeQueryIndex,
|
||||||
isInDataExplorer,
|
resizerBottomHeight,
|
||||||
}) => {
|
}) => {
|
||||||
const activeQuery = queries[activeQueryIndex]
|
const activeQuery = queries[activeQueryIndex]
|
||||||
const defaultQuery = queries[0]
|
const defaultQuery = queries[0]
|
||||||
|
@ -34,46 +31,30 @@ const VisView = ({
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
query={query}
|
query={query}
|
||||||
height={heightPixels}
|
height={resizerBottomHeight}
|
||||||
editQueryStatus={editQueryStatus}
|
editQueryStatus={editQueryStatus}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cellType === 'single-stat') {
|
|
||||||
return (
|
|
||||||
<RefreshingSingleStat
|
|
||||||
queries={queries.length ? [queries[0]] : []}
|
|
||||||
autoRefresh={autoRefresh}
|
|
||||||
templates={templates}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayOptions = {
|
|
||||||
stepPlot: cellType === 'line-stepplot',
|
|
||||||
stackedGraph: cellType === 'line-stacked',
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RefreshingLineGraph
|
<RefreshingGraph
|
||||||
|
axes={axes}
|
||||||
|
type={cellType}
|
||||||
queries={queries}
|
queries={queries}
|
||||||
autoRefresh={autoRefresh}
|
|
||||||
templates={templates}
|
templates={templates}
|
||||||
activeQueryIndex={activeQueryIndex}
|
cellHeight={heightPixels}
|
||||||
isInDataExplorer={isInDataExplorer}
|
autoRefresh={autoRefresh}
|
||||||
showSingleStat={cellType === 'line-plus-single-stat'}
|
|
||||||
isBarGraph={cellType === 'bar'}
|
|
||||||
displayOptions={displayOptions}
|
|
||||||
editQueryStatus={editQueryStatus}
|
editQueryStatus={editQueryStatus}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
const {arrayOf, func, number, shape, string} = PropTypes
|
||||||
|
|
||||||
VisView.propTypes = {
|
VisView.propTypes = {
|
||||||
view: string.isRequired,
|
view: string.isRequired,
|
||||||
|
axes: shape(),
|
||||||
queries: arrayOf(shape()).isRequired,
|
queries: arrayOf(shape()).isRequired,
|
||||||
cellType: string,
|
cellType: string,
|
||||||
templates: arrayOf(shape()),
|
templates: arrayOf(shape()),
|
||||||
|
@ -81,7 +62,7 @@ VisView.propTypes = {
|
||||||
heightPixels: number,
|
heightPixels: number,
|
||||||
editQueryStatus: func.isRequired,
|
editQueryStatus: func.isRequired,
|
||||||
activeQueryIndex: number,
|
activeQueryIndex: number,
|
||||||
isInDataExplorer: bool,
|
resizerBottomHeight: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VisView
|
export default VisView
|
||||||
|
|
|
@ -26,6 +26,12 @@ const Visualization = React.createClass({
|
||||||
heightPixels: number,
|
heightPixels: number,
|
||||||
editQueryStatus: func.isRequired,
|
editQueryStatus: func.isRequired,
|
||||||
views: arrayOf(string).isRequired,
|
views: arrayOf(string).isRequired,
|
||||||
|
axes: shape({
|
||||||
|
y: shape({
|
||||||
|
bounds: arrayOf(string),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
resizerBottomHeight: number,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
contextTypes: {
|
||||||
|
@ -48,6 +54,7 @@ const Visualization = React.createClass({
|
||||||
getDefaultProps() {
|
getDefaultProps() {
|
||||||
return {
|
return {
|
||||||
cellName: '',
|
cellName: '',
|
||||||
|
cellType: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -76,6 +83,7 @@ const Visualization = React.createClass({
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
axes,
|
||||||
views,
|
views,
|
||||||
height,
|
height,
|
||||||
cellType,
|
cellType,
|
||||||
|
@ -88,16 +96,19 @@ const Visualization = React.createClass({
|
||||||
editQueryStatus,
|
editQueryStatus,
|
||||||
activeQueryIndex,
|
activeQueryIndex,
|
||||||
isInDataExplorer,
|
isInDataExplorer,
|
||||||
|
resizerBottomHeight,
|
||||||
} = this.props
|
} = this.props
|
||||||
const {source: {links: {proxy}}} = this.context
|
const {source: {links: {proxy}}} = this.context
|
||||||
const {view} = this.state
|
const {view} = this.state
|
||||||
|
|
||||||
const statements = queryConfigs.map(query => {
|
const statements = queryConfigs.map(query => {
|
||||||
const text = query.rawText || buildInfluxQLQuery(timeRange, query)
|
const text =
|
||||||
return {text, id: query.id}
|
query.rawText || buildInfluxQLQuery(query.range || timeRange, query)
|
||||||
|
return {text, id: query.id, queryConfig: query}
|
||||||
})
|
})
|
||||||
|
|
||||||
const queries = statements.filter(s => s.text !== null).map(s => {
|
const queries = statements.filter(s => s.text !== null).map(s => {
|
||||||
return {host: [proxy], text: s.text, id: s.id}
|
return {host: [proxy], text: s.text, id: s.id, queryConfig: s.queryConfig}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -116,6 +127,7 @@ const Visualization = React.createClass({
|
||||||
>
|
>
|
||||||
<VisView
|
<VisView
|
||||||
view={view}
|
view={view}
|
||||||
|
axes={axes}
|
||||||
queries={queries}
|
queries={queries}
|
||||||
templates={templates}
|
templates={templates}
|
||||||
cellType={cellType}
|
cellType={cellType}
|
||||||
|
@ -124,6 +136,7 @@ const Visualization = React.createClass({
|
||||||
editQueryStatus={editQueryStatus}
|
editQueryStatus={editQueryStatus}
|
||||||
activeQueryIndex={activeQueryIndex}
|
activeQueryIndex={activeQueryIndex}
|
||||||
isInDataExplorer={isInDataExplorer}
|
isInDataExplorer={isInDataExplorer}
|
||||||
|
resizerBottomHeight={resizerBottomHeight}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,7 +33,7 @@ const WriteDataBody = ({
|
||||||
ref={fileInput}
|
ref={fileInput}
|
||||||
accept="text/*, application/gzip"
|
accept="text/*, application/gzip"
|
||||||
/>
|
/>
|
||||||
<button className="btn btn-sm btn-primary" onClick={handleFileOpen}>
|
<button className="btn btn-md btn-primary" onClick={handleFileOpen}>
|
||||||
{uploadContent ? 'Upload a Different File' : 'Upload a File'}
|
{uploadContent ? 'Upload a Different File' : 'Upload a File'}
|
||||||
</button>
|
</button>
|
||||||
{uploadContent
|
{uploadContent
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
array,
|
||||||
arrayOf,
|
arrayOf,
|
||||||
bool,
|
bool,
|
||||||
element,
|
element,
|
||||||
|
@ -43,6 +44,12 @@ const AutoRefresh = ComposedComponent => {
|
||||||
text: string,
|
text: string,
|
||||||
}).isRequired
|
}).isRequired
|
||||||
).isRequired,
|
).isRequired,
|
||||||
|
axes: shape({
|
||||||
|
bounds: shape({
|
||||||
|
y: array,
|
||||||
|
y2: array,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
editQueryStatus: func,
|
editQueryStatus: func,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,13 @@ import getRange from 'shared/parsing/getRangeForDygraph'
|
||||||
|
|
||||||
import {LINE_COLORS, multiColumnBarPlotter} from 'src/shared/graphs/helpers'
|
import {LINE_COLORS, multiColumnBarPlotter} from 'src/shared/graphs/helpers'
|
||||||
import DygraphLegend from 'src/shared/components/DygraphLegend'
|
import DygraphLegend from 'src/shared/components/DygraphLegend'
|
||||||
|
import {buildYLabel} from 'shared/presenters'
|
||||||
|
|
||||||
|
const hasherino = (str, len) =>
|
||||||
|
str
|
||||||
|
.split('')
|
||||||
|
.map(char => char.charCodeAt(0))
|
||||||
|
.reduce((hash, code) => hash + code, 0) % len
|
||||||
|
|
||||||
export default class Dygraph extends Component {
|
export default class Dygraph extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -35,12 +42,14 @@ export default class Dygraph extends Component {
|
||||||
this.handleHideLegend = ::this.handleHideLegend
|
this.handleHideLegend = ::this.handleHideLegend
|
||||||
this.handleToggleFilter = ::this.handleToggleFilter
|
this.handleToggleFilter = ::this.handleToggleFilter
|
||||||
this.visibility = ::this.visibility
|
this.visibility = ::this.visibility
|
||||||
|
this.getLabel = ::this.getLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
containerStyle: {},
|
containerStyle: {},
|
||||||
isGraphFilled: true,
|
isGraphFilled: true,
|
||||||
overrideLineColors: null,
|
overrideLineColors: null,
|
||||||
|
dygraphRef: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
getTimeSeries() {
|
getTimeSeries() {
|
||||||
|
@ -50,11 +59,23 @@ export default class Dygraph extends Component {
|
||||||
return timeSeries.length ? timeSeries : [[0]]
|
return timeSeries.length ? timeSeries : [[0]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLabel(axis) {
|
||||||
|
const {axes, queries} = this.props
|
||||||
|
const label = _.get(axes, [axis, 'label'], '')
|
||||||
|
const queryConfig = _.get(queries, ['0', 'queryConfig'], false)
|
||||||
|
|
||||||
|
if (label || !queryConfig) {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildYLabel(queryConfig)
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const timeSeries = this.getTimeSeries()
|
const timeSeries = this.getTimeSeries()
|
||||||
// dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'};
|
// dygraphSeries is a legend label and its corresponding y-axis e.g. {legendLabel1: 'y', legendLabel2: 'y2'};
|
||||||
const {
|
const {
|
||||||
ranges,
|
axes,
|
||||||
dygraphSeries,
|
dygraphSeries,
|
||||||
ruleValues,
|
ruleValues,
|
||||||
overrideLineColors,
|
overrideLineColors,
|
||||||
|
@ -65,18 +86,29 @@ export default class Dygraph extends Component {
|
||||||
|
|
||||||
const graphRef = this.graphRef
|
const graphRef = this.graphRef
|
||||||
const legendRef = this.legendRef
|
const legendRef = this.legendRef
|
||||||
let finalLineColors = overrideLineColors
|
const finalLineColors = [...(overrideLineColors || LINE_COLORS)]
|
||||||
|
|
||||||
if (finalLineColors === null) {
|
const hashColorDygraphSeries = {}
|
||||||
finalLineColors = LINE_COLORS
|
const {length} = finalLineColors
|
||||||
|
|
||||||
|
for (const seriesName in dygraphSeries) {
|
||||||
|
const series = dygraphSeries[seriesName]
|
||||||
|
const hashIndex = hasherino(seriesName, length)
|
||||||
|
const color = finalLineColors[hashIndex]
|
||||||
|
hashColorDygraphSeries[seriesName] = {...series, color}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const yAxis = _.get(axes, ['y', 'bounds'], [null, null])
|
||||||
|
const y2Axis = _.get(axes, ['y2', 'bounds'], undefined)
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
plugins: [
|
plugins: isBarGraph
|
||||||
new Dygraphs.Plugins.Crosshair({
|
? []
|
||||||
direction: 'vertical',
|
: [
|
||||||
}),
|
new Dygraphs.Plugins.Crosshair({
|
||||||
],
|
direction: 'vertical',
|
||||||
|
}),
|
||||||
|
],
|
||||||
labelsSeparateLines: false,
|
labelsSeparateLines: false,
|
||||||
labelsKMB: true,
|
labelsKMB: true,
|
||||||
rightGap: 0,
|
rightGap: 0,
|
||||||
|
@ -85,22 +117,22 @@ export default class Dygraph extends Component {
|
||||||
fillGraph: isGraphFilled,
|
fillGraph: isGraphFilled,
|
||||||
axisLineWidth: 2,
|
axisLineWidth: 2,
|
||||||
gridLineWidth: 1,
|
gridLineWidth: 1,
|
||||||
highlightCircleSize: 3,
|
highlightCircleSize: isBarGraph ? 0 : 3,
|
||||||
animatedZooms: true,
|
animatedZooms: true,
|
||||||
hideOverlayOnMouseOut: false,
|
hideOverlayOnMouseOut: false,
|
||||||
colors: finalLineColors,
|
colors: finalLineColors,
|
||||||
series: dygraphSeries,
|
series: hashColorDygraphSeries,
|
||||||
axes: {
|
axes: {
|
||||||
y: {
|
y: {
|
||||||
valueRange: getRange(timeSeries, ranges.y, ruleValues),
|
valueRange: getRange(timeSeries, yAxis, ruleValues),
|
||||||
},
|
},
|
||||||
y2: {
|
y2: {
|
||||||
valueRange: getRange(timeSeries, ranges.y2),
|
valueRange: getRange(timeSeries, y2Axis),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
highlightSeriesOpts: {
|
highlightSeriesOpts: {
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
highlightCircleSize: 5,
|
highlightCircleSize: isBarGraph ? 0 : 5,
|
||||||
},
|
},
|
||||||
legendFormatter: legend => {
|
legendFormatter: legend => {
|
||||||
if (!legend.x) {
|
if (!legend.x) {
|
||||||
|
@ -235,11 +267,12 @@ export default class Dygraph extends Component {
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const {
|
const {
|
||||||
labels,
|
labels,
|
||||||
ranges,
|
axes,
|
||||||
options,
|
options,
|
||||||
dygraphSeries,
|
dygraphSeries,
|
||||||
ruleValues,
|
ruleValues,
|
||||||
isBarGraph,
|
isBarGraph,
|
||||||
|
overrideLineColors,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const dygraph = this.dygraph
|
const dygraph = this.dygraph
|
||||||
|
@ -249,22 +282,39 @@ export default class Dygraph extends Component {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const y = _.get(axes, ['y', 'bounds'], [null, null])
|
||||||
|
const y2 = _.get(axes, ['y2', 'bounds'], undefined)
|
||||||
const timeSeries = this.getTimeSeries()
|
const timeSeries = this.getTimeSeries()
|
||||||
|
const ylabel = this.getLabel('y')
|
||||||
|
const finalLineColors = [...(overrideLineColors || LINE_COLORS)]
|
||||||
|
|
||||||
|
const hashColorDygraphSeries = {}
|
||||||
|
const {length} = finalLineColors
|
||||||
|
|
||||||
|
for (const seriesName in dygraphSeries) {
|
||||||
|
const series = dygraphSeries[seriesName]
|
||||||
|
const hashIndex = hasherino(seriesName, length)
|
||||||
|
const color = finalLineColors[hashIndex]
|
||||||
|
hashColorDygraphSeries[seriesName] = {...series, color}
|
||||||
|
}
|
||||||
|
|
||||||
const updateOptions = {
|
const updateOptions = {
|
||||||
labels,
|
labels,
|
||||||
file: timeSeries,
|
file: timeSeries,
|
||||||
|
ylabel,
|
||||||
axes: {
|
axes: {
|
||||||
y: {
|
y: {
|
||||||
valueRange: getRange(timeSeries, ranges.y, ruleValues),
|
valueRange: getRange(timeSeries, y, ruleValues),
|
||||||
},
|
},
|
||||||
y2: {
|
y2: {
|
||||||
valueRange: getRange(timeSeries, ranges.y2),
|
valueRange: getRange(timeSeries, y2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stepPlot: options.stepPlot,
|
stepPlot: options.stepPlot,
|
||||||
stackedGraph: options.stackedGraph,
|
stackedGraph: options.stackedGraph,
|
||||||
underlayCallback: options.underlayCallback,
|
underlayCallback: options.underlayCallback,
|
||||||
series: dygraphSeries,
|
colors: finalLineColors,
|
||||||
|
series: hashColorDygraphSeries,
|
||||||
plotter: isBarGraph ? multiColumnBarPlotter : null,
|
plotter: isBarGraph ? multiColumnBarPlotter : null,
|
||||||
visibility: this.visibility(),
|
visibility: this.visibility(),
|
||||||
}
|
}
|
||||||
|
@ -350,6 +400,7 @@ export default class Dygraph extends Component {
|
||||||
<div
|
<div
|
||||||
ref={r => {
|
ref={r => {
|
||||||
this.graphRef = r
|
this.graphRef = r
|
||||||
|
this.props.dygraphRef(r)
|
||||||
}}
|
}}
|
||||||
style={this.props.containerStyle}
|
style={this.props.containerStyle}
|
||||||
className="dygraph-child-container"
|
className="dygraph-child-container"
|
||||||
|
@ -359,13 +410,18 @@ export default class Dygraph extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {array, arrayOf, func, number, bool, shape, string} = PropTypes
|
const {array, arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
Dygraph.propTypes = {
|
Dygraph.propTypes = {
|
||||||
ranges: shape({
|
axes: shape({
|
||||||
y: arrayOf(number),
|
y: shape({
|
||||||
y2: arrayOf(number),
|
bounds: array,
|
||||||
|
}),
|
||||||
|
y2: shape({
|
||||||
|
bounds: array,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
queries: arrayOf(shape),
|
||||||
timeSeries: array.isRequired,
|
timeSeries: array.isRequired,
|
||||||
labels: array.isRequired,
|
labels: array.isRequired,
|
||||||
options: shape({}),
|
options: shape({}),
|
||||||
|
@ -384,4 +440,5 @@ Dygraph.propTypes = {
|
||||||
}),
|
}),
|
||||||
synchronizer: func,
|
synchronizer: func,
|
||||||
setResolution: func,
|
setResolution: func,
|
||||||
|
dygraphRef: func,
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,7 @@ class LayoutRenderer extends Component {
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return cells.map(cell => {
|
return cells.map(cell => {
|
||||||
const {type, h} = cell
|
const {type, h, axes} = cell
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={cell.i}>
|
<div key={cell.i}>
|
||||||
|
@ -175,6 +175,7 @@ class LayoutRenderer extends Component {
|
||||||
type={type}
|
type={type}
|
||||||
queries={this.standardizeQueries(cell, source)}
|
queries={this.standardizeQueries(cell, source)}
|
||||||
cellHeight={h}
|
cellHeight={h}
|
||||||
|
axes={axes}
|
||||||
/>}
|
/>}
|
||||||
</NameableGraph>
|
</NameableGraph>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React, {PropTypes} from 'react'
|
||||||
import Dygraph from 'shared/components/Dygraph'
|
import Dygraph from 'shared/components/Dygraph'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import shallowCompare from 'react-addons-shallow-compare'
|
import shallowCompare from 'react-addons-shallow-compare'
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'
|
import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'
|
||||||
import lastValues from 'shared/parsing/lastValues'
|
import lastValues from 'shared/parsing/lastValues'
|
||||||
|
@ -15,9 +14,15 @@ export default React.createClass({
|
||||||
displayName: 'LineGraph',
|
displayName: 'LineGraph',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
data: arrayOf(shape({}).isRequired).isRequired,
|
data: arrayOf(shape({}).isRequired).isRequired,
|
||||||
ranges: shape({
|
axes: shape({
|
||||||
y: arrayOf(number),
|
y: shape({
|
||||||
y2: arrayOf(number),
|
bounds: array,
|
||||||
|
label: string,
|
||||||
|
}),
|
||||||
|
y2: shape({
|
||||||
|
bounds: array,
|
||||||
|
label: string,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
title: string,
|
title: string,
|
||||||
isFetchingInitially: bool,
|
isFetchingInitially: bool,
|
||||||
|
@ -81,15 +86,15 @@ export default React.createClass({
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
ranges,
|
axes,
|
||||||
isFetchingInitially,
|
isFetchingInitially,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
isGraphFilled,
|
isGraphFilled,
|
||||||
isBarGraph,
|
isBarGraph,
|
||||||
overrideLineColors,
|
overrideLineColors,
|
||||||
title,
|
title,
|
||||||
underlayCallback,
|
|
||||||
queries,
|
queries,
|
||||||
|
underlayCallback,
|
||||||
showSingleStat,
|
showSingleStat,
|
||||||
displayOptions,
|
displayOptions,
|
||||||
ruleValues,
|
ruleValues,
|
||||||
|
@ -120,8 +125,6 @@ export default React.createClass({
|
||||||
axisLabelWidth: 60,
|
axisLabelWidth: 60,
|
||||||
drawAxesAtZero: true,
|
drawAxesAtZero: true,
|
||||||
underlayCallback,
|
underlayCallback,
|
||||||
ylabel: _.get(queries, ['0', 'label'], ''),
|
|
||||||
y2label: _.get(queries, ['1', 'label'], ''),
|
|
||||||
...displayOptions,
|
...displayOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,26 +154,25 @@ export default React.createClass({
|
||||||
roundedValue = Math.round(+lastValue * precision) / precision
|
roundedValue = Math.round(+lastValue * precision) / precision
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lineColors = showSingleStat
|
||||||
|
? singleStatLineColors
|
||||||
|
: overrideLineColors
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={`dygraph ${this.yLabelClass()}`} style={{height: '100%'}}>
|
||||||
className={classnames('dygraph', {
|
|
||||||
'graph--hasYLabel': !!(options.ylabel || options.y2label),
|
|
||||||
})}
|
|
||||||
style={{height: '100%'}}
|
|
||||||
>
|
|
||||||
{isRefreshing ? this.renderSpinner() : null}
|
{isRefreshing ? this.renderSpinner() : null}
|
||||||
<Dygraph
|
<Dygraph
|
||||||
|
axes={axes}
|
||||||
|
queries={queries}
|
||||||
|
dygraphRef={this.dygraphRefFunc}
|
||||||
containerStyle={{width: '100%', height: '100%'}}
|
containerStyle={{width: '100%', height: '100%'}}
|
||||||
overrideLineColors={
|
overrideLineColors={lineColors}
|
||||||
showSingleStat ? singleStatLineColors : overrideLineColors
|
|
||||||
}
|
|
||||||
isGraphFilled={showSingleStat ? false : isGraphFilled}
|
isGraphFilled={showSingleStat ? false : isGraphFilled}
|
||||||
isBarGraph={isBarGraph}
|
isBarGraph={isBarGraph}
|
||||||
timeSeries={timeSeries}
|
timeSeries={timeSeries}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
options={showSingleStat ? singleStatOptions : options}
|
options={showSingleStat ? singleStatOptions : options}
|
||||||
dygraphSeries={dygraphSeries}
|
dygraphSeries={dygraphSeries}
|
||||||
ranges={ranges || this.getRanges()}
|
|
||||||
ruleValues={ruleValues}
|
ruleValues={ruleValues}
|
||||||
synchronizer={synchronizer}
|
synchronizer={synchronizer}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
|
@ -193,6 +195,26 @@ export default React.createClass({
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
yLabelClass() {
|
||||||
|
const dygraph = this.dygraphRef
|
||||||
|
|
||||||
|
if (!dygraph) {
|
||||||
|
return 'graph--hasYLabel'
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = dygraph.querySelector('.dygraph-ylabel')
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'graph--hasYLabel'
|
||||||
|
},
|
||||||
|
|
||||||
|
dygraphRefFunc(dygraphRef) {
|
||||||
|
this.dygraphRef = dygraphRef
|
||||||
|
},
|
||||||
|
|
||||||
renderSpinner() {
|
renderSpinner() {
|
||||||
return (
|
return (
|
||||||
<div className="graph-panel__refreshing">
|
<div className="graph-panel__refreshing">
|
||||||
|
@ -202,25 +224,4 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
getRanges() {
|
|
||||||
const {queries} = this.props
|
|
||||||
if (!queries) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ranges = {}
|
|
||||||
const q0 = queries[0]
|
|
||||||
const q1 = queries[1]
|
|
||||||
|
|
||||||
if (q0 && q0.range) {
|
|
||||||
ranges.y = [q0.range.lower, q0.range.upper]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (q1 && q1.range) {
|
|
||||||
ranges.y2 = [q1.range.lower, q1.range.upper]
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranges
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,13 +8,15 @@ const RefreshingLineGraph = AutoRefresh(LineGraph)
|
||||||
const RefreshingSingleStat = AutoRefresh(SingleStat)
|
const RefreshingSingleStat = AutoRefresh(SingleStat)
|
||||||
|
|
||||||
const RefreshingGraph = ({
|
const RefreshingGraph = ({
|
||||||
timeRange,
|
axes,
|
||||||
autoRefresh,
|
|
||||||
templates,
|
|
||||||
synchronizer,
|
|
||||||
type,
|
type,
|
||||||
queries,
|
queries,
|
||||||
|
templates,
|
||||||
|
timeRange,
|
||||||
cellHeight,
|
cellHeight,
|
||||||
|
autoRefresh,
|
||||||
|
synchronizer,
|
||||||
|
editQueryStatus,
|
||||||
}) => {
|
}) => {
|
||||||
if (type === 'single-stat') {
|
if (type === 'single-stat') {
|
||||||
return (
|
return (
|
||||||
|
@ -42,6 +44,8 @@ const RefreshingGraph = ({
|
||||||
isBarGraph={type === 'bar'}
|
isBarGraph={type === 'bar'}
|
||||||
displayOptions={displayOptions}
|
displayOptions={displayOptions}
|
||||||
synchronizer={synchronizer}
|
synchronizer={synchronizer}
|
||||||
|
editQueryStatus={editQueryStatus}
|
||||||
|
axes={axes}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -56,8 +60,10 @@ RefreshingGraph.propTypes = {
|
||||||
templates: arrayOf(shape()),
|
templates: arrayOf(shape()),
|
||||||
synchronizer: func,
|
synchronizer: func,
|
||||||
type: string.isRequired,
|
type: string.isRequired,
|
||||||
|
cellHeight: number,
|
||||||
|
axes: shape(),
|
||||||
queries: arrayOf(shape()).isRequired,
|
queries: arrayOf(shape()).isRequired,
|
||||||
cellHeight: number.isRequired,
|
editQueryStatus: func,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RefreshingGraph
|
export default RefreshingGraph
|
||||||
|
|
|
@ -31,6 +31,12 @@ class ResizeContainer extends Component {
|
||||||
initialBottomHeight: defaultInitialBottomHeight,
|
initialBottomHeight: defaultInitialBottomHeight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
bottomHeightPixels: this.bottom.getBoundingClientRect().height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleStartDrag() {
|
handleStartDrag() {
|
||||||
this.setState({isDragging: true})
|
this.setState({isDragging: true})
|
||||||
}
|
}
|
||||||
|
@ -51,7 +57,7 @@ class ResizeContainer extends Component {
|
||||||
const {minTopHeight, minBottomHeight} = this.props
|
const {minTopHeight, minBottomHeight} = this.props
|
||||||
const oneHundred = 100
|
const oneHundred = 100
|
||||||
const containerHeight = parseInt(
|
const containerHeight = parseInt(
|
||||||
getComputedStyle(this.refs.resizeContainer).height,
|
getComputedStyle(this.resizeContainer).height,
|
||||||
10
|
10
|
||||||
)
|
)
|
||||||
// verticalOffset moves the resize handle as many pixels as the page-heading is taking up.
|
// verticalOffset moves the resize handle as many pixels as the page-heading is taking up.
|
||||||
|
@ -85,11 +91,12 @@ class ResizeContainer extends Component {
|
||||||
this.setState({
|
this.setState({
|
||||||
topHeight: `${newTopPanelPercent}%`,
|
topHeight: `${newTopPanelPercent}%`,
|
||||||
bottomHeight: `${newBottomPanelPercent}%`,
|
bottomHeight: `${newBottomPanelPercent}%`,
|
||||||
|
bottomHeightPixels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {topHeight, bottomHeight, isDragging} = this.state
|
const {bottomHeightPixels, topHeight, bottomHeight, isDragging} = this.state
|
||||||
const {containerClass, children} = this.props
|
const {containerClass, children} = this.props
|
||||||
|
|
||||||
if (React.Children.count(children) > maximumNumChildren) {
|
if (React.Children.count(children) > maximumNumChildren) {
|
||||||
|
@ -107,10 +114,12 @@ class ResizeContainer extends Component {
|
||||||
onMouseLeave={this.handleMouseLeave}
|
onMouseLeave={this.handleMouseLeave}
|
||||||
onMouseUp={this.handleStopDrag}
|
onMouseUp={this.handleStopDrag}
|
||||||
onMouseMove={this.handleDrag}
|
onMouseMove={this.handleDrag}
|
||||||
ref="resizeContainer"
|
ref={r => (this.resizeContainer = r)}
|
||||||
>
|
>
|
||||||
<div className="resize--top" style={{height: topHeight}}>
|
<div className="resize--top" style={{height: topHeight}}>
|
||||||
{React.cloneElement(children[0])}
|
{React.cloneElement(children[0], {
|
||||||
|
resizerBottomHeight: bottomHeightPixels,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
isDragging={isDragging}
|
isDragging={isDragging}
|
||||||
|
@ -120,8 +129,11 @@ class ResizeContainer extends Component {
|
||||||
<div
|
<div
|
||||||
className="resize--bottom"
|
className="resize--bottom"
|
||||||
style={{height: bottomHeight, top: topHeight}}
|
style={{height: bottomHeight, top: topHeight}}
|
||||||
|
ref={r => (this.bottom = r)}
|
||||||
>
|
>
|
||||||
{React.cloneElement(children[1])}
|
{React.cloneElement(children[1], {
|
||||||
|
resizerBottomHeight: bottomHeightPixels,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[
|
[
|
||||||
{type: "line", menuOption: "Line"},
|
{type: "line", menuOption: "Line", graphic: "Wogglez"},
|
||||||
{type: "line-stacked", menuOption: "Stacked"},
|
{type: "line-stacked", menuOption: "Stacked", graphic: "Rogglez"},
|
||||||
{type: "line-stepplot", menuOption: "Step-Plot"},
|
{type: "line-stepplot", menuOption: "Step-Plot", graphic: "Fogglez"},
|
||||||
{type: "single-stat", menuOption: "SingleStat"},
|
{type: "single-stat", menuOption: "SingleStat", graphic: "Bogglez"},
|
||||||
{type: "line-plus-single-stat", menuOption: "Line + Stat"},
|
{type: "line-plus-single-stat", menuOption: "Line + Stat", graphic: "Togglez"},
|
||||||
{type: "bar", menuOption: "Bar"},
|
{type: "bar", menuOption: "Bar", graphic: "Zogglez"},
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const darkenColor = colorStr => {
|
||||||
return `rgb(${color.r},${color.g},${color.b})`
|
return `rgb(${color.r},${color.g},${color.b})`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bar Graph code below is from http://dygraphs.com/tests/plotters.html
|
// Bar Graph code below is adapted from http://dygraphs.com/tests/plotters.html
|
||||||
export const multiColumnBarPlotter = e => {
|
export const multiColumnBarPlotter = e => {
|
||||||
// We need to handle all the series simultaneously.
|
// We need to handle all the series simultaneously.
|
||||||
if (e.seriesIndex !== 0) {
|
if (e.seriesIndex !== 0) {
|
||||||
|
@ -51,24 +51,34 @@ export const multiColumnBarPlotter = e => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const barWidth = Math.floor(2.0 / 3 * minSep)
|
// calculate bar width using some graphics math while
|
||||||
|
// ensuring a bar is never smaller than one px, so it is always rendered
|
||||||
|
const barWidth = Math.max(Math.floor(2.0 / 3.0 * minSep), 1.0)
|
||||||
|
|
||||||
const fillColors = []
|
const fillColors = []
|
||||||
const strokeColors = g.getColors()
|
const strokeColors = g.getColors()
|
||||||
|
|
||||||
|
let selPointX
|
||||||
|
if (g.selPoints_ && g.selPoints_.length) {
|
||||||
|
selPointX = g.selPoints_[0].canvasx
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < strokeColors.length; i++) {
|
for (let i = 0; i < strokeColors.length; i++) {
|
||||||
fillColors.push(darkenColor(strokeColors[i]))
|
fillColors.push(darkenColor(strokeColors[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
|
||||||
for (let j = 0; j < sets.length; j++) {
|
for (let j = 0; j < sets.length; j++) {
|
||||||
ctx.fillStyle = fillColors[j]
|
|
||||||
ctx.strokeStyle = strokeColors[j]
|
ctx.strokeStyle = strokeColors[j]
|
||||||
for (let i = 0; i < sets[j].length; i++) {
|
for (let i = 0; i < sets[j].length; i++) {
|
||||||
const p = sets[j][i]
|
const p = sets[j][i]
|
||||||
const centerX = p.canvasx
|
const centerX = p.canvasx
|
||||||
|
ctx.fillStyle = fillColors[j]
|
||||||
const xLeft =
|
const xLeft =
|
||||||
sets.length === 1
|
sets.length === 1
|
||||||
? centerX - barWidth / 2
|
? centerX - barWidth
|
||||||
: centerX - barWidth / 2 * (1 - j / (sets.length - 1))
|
: centerX - barWidth * (1 - j / sets.length)
|
||||||
|
|
||||||
ctx.fillRect(
|
ctx.fillRect(
|
||||||
xLeft,
|
xLeft,
|
||||||
|
@ -77,12 +87,15 @@ export const multiColumnBarPlotter = e => {
|
||||||
yBottom - p.canvasy
|
yBottom - p.canvasy
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.strokeRect(
|
// hover highlighting
|
||||||
xLeft,
|
if (selPointX === centerX) {
|
||||||
p.canvasy,
|
ctx.strokeRect(
|
||||||
barWidth / sets.length,
|
xLeft,
|
||||||
yBottom - p.canvasy
|
p.canvasy,
|
||||||
)
|
barWidth / sets.length,
|
||||||
|
yBottom - p.canvasy
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
const PADDING_FACTOR = 0.1
|
const PADDING_FACTOR = 0.1
|
||||||
|
|
||||||
export default function getRange(
|
const considerEmpty = (userNumber, number) => {
|
||||||
timeSeries,
|
if (userNumber === '') {
|
||||||
override,
|
return null
|
||||||
ruleValues = {value: null, rangeValue: null}
|
|
||||||
) {
|
|
||||||
if (override) {
|
|
||||||
return override
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userNumber) {
|
||||||
|
return +userNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRange = (
|
||||||
|
timeSeries,
|
||||||
|
userSelectedRange = [null, null],
|
||||||
|
ruleValues = {value: null, rangeValue: null}
|
||||||
|
) => {
|
||||||
const {value, rangeValue, operator} = ruleValues
|
const {value, rangeValue, operator} = ruleValues
|
||||||
|
const [userMin, userMax] = userSelectedRange
|
||||||
|
|
||||||
const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR)
|
const subtractPadding = val => +val - Math.abs(val * PADDING_FACTOR)
|
||||||
const addPadding = val => +val + Math.abs(val * PADDING_FACTOR)
|
const addPadding = val => +val + Math.abs(val * PADDING_FACTOR)
|
||||||
|
@ -52,10 +61,22 @@ export default function getRange(
|
||||||
[null, null]
|
[null, null]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [min, max] = range
|
||||||
|
|
||||||
// If time series is such that min and max are equal use Dygraph defaults
|
// If time series is such that min and max are equal use Dygraph defaults
|
||||||
if (range[0] === range[1]) {
|
if (min === max) {
|
||||||
return [null, null]
|
return [null, null]
|
||||||
}
|
}
|
||||||
|
|
||||||
return range
|
if (userMin === userMax) {
|
||||||
|
return [min, max]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userMin && userMax) {
|
||||||
|
return [considerEmpty(userMin), considerEmpty(userMax)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [considerEmpty(userMin, min), considerEmpty(userMax, max)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default getRange
|
||||||
|
|
|
@ -108,3 +108,9 @@ function getRolesForUser(roles, user) {
|
||||||
|
|
||||||
return buildRoles(userRoles)
|
return buildRoles(userRoles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const buildYLabel = queryConfig => {
|
||||||
|
return queryConfig.rawText
|
||||||
|
? ''
|
||||||
|
: `${queryConfig.measurement}.${queryConfig.fields[0].field}`
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ import {errorThrown} from 'shared/actions/errors'
|
||||||
|
|
||||||
import * as actionTypes from 'src/status/constants/actionTypes'
|
import * as actionTypes from 'src/status/constants/actionTypes'
|
||||||
|
|
||||||
import {HTTP_NOT_FOUND} from 'shared/constants'
|
|
||||||
|
|
||||||
const fetchJSONFeedRequested = () => ({
|
const fetchJSONFeedRequested = () => ({
|
||||||
type: actionTypes.FETCH_JSON_FEED_REQUESTED,
|
type: actionTypes.FETCH_JSON_FEED_REQUESTED,
|
||||||
})
|
})
|
||||||
|
@ -45,15 +43,11 @@ export const fetchJSONFeedAsync = url => async dispatch => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
dispatch(fetchJSONFeedFailed())
|
dispatch(fetchJSONFeedFailed())
|
||||||
if (error.status === HTTP_NOT_FOUND) {
|
dispatch(
|
||||||
dispatch(
|
errorThrown(
|
||||||
errorThrown(
|
error,
|
||||||
error,
|
`Failed to fetch JSON Feed for News Feed from '${url}'`
|
||||||
`Failed to fetch News Feed. JSON Feed at '${url}' returned 404 (Not Found)`
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} else {
|
)
|
||||||
dispatch(errorThrown(error, 'Failed to fetch NewsFeed'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
@import 'layout/flash-messages';
|
@import 'layout/flash-messages';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
@import 'components/ceo-display-options';
|
||||||
@import 'components/confirm-buttons';
|
@import 'components/confirm-buttons';
|
||||||
@import 'components/custom-time-range';
|
@import 'components/custom-time-range';
|
||||||
@import 'components/dygraphs';
|
@import 'components/dygraphs';
|
||||||
|
@ -47,7 +48,6 @@
|
||||||
@import 'components/tables';
|
@import 'components/tables';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
|
|
||||||
@import 'pages/config-endpoints';
|
@import 'pages/config-endpoints';
|
||||||
@import 'pages/signup';
|
@import 'pages/signup';
|
||||||
@import 'pages/auth-page';
|
@import 'pages/auth-page';
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
Cell Editor Overlay - Display Options
|
||||||
|
------------------------------------------------------
|
||||||
|
*/
|
||||||
|
.display-options {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
background-color: $g2-kevlar;
|
||||||
|
padding: 0 18px 8px 18px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.display-options--cell {
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $g3-castle;
|
||||||
|
padding: 30px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
&:last-child { margin-right: 0; }
|
||||||
|
}
|
||||||
|
.display-options--cellx2 {
|
||||||
|
flex: 2 0 0;
|
||||||
|
}
|
||||||
|
.display-options--header {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
@include no-user-select();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.viz-type-selector {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
height: calc(100% - 22px);
|
||||||
|
margin: -4px;
|
||||||
|
}
|
||||||
|
.viz-type-selector--option {
|
||||||
|
flex: 1 0 33.3333%;
|
||||||
|
height: 50%;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
> div > p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 900;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6.25%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 50%);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actual "card"
|
||||||
|
> div {
|
||||||
|
background-color: $g3-castle;
|
||||||
|
border: 2px solid $g4-onyx;
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
transition:
|
||||||
|
color 0.25s ease,
|
||||||
|
border-color 0.25s ease,
|
||||||
|
background-color 0.25s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $g4-onyx;
|
||||||
|
border-color: $g5-pepper;
|
||||||
|
color: $g15-platinum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Active state "card"
|
||||||
|
.viz-type-selector--option.active > div,
|
||||||
|
.viz-type-selector--option.active > div:hover {
|
||||||
|
background-color: $g5-pepper;
|
||||||
|
border-color: $g7-graphite;
|
||||||
|
color: $g18-cloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viz-type-selector--graphic {
|
||||||
|
width: calc(100% - 48px);
|
||||||
|
height: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
transform: translate3d(0,0,0);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.viz-type-selector--graphic-line {
|
||||||
|
stroke-width: 3px;
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
|
||||||
|
&.graphic-line-a {stroke: $g11-sidewalk;}
|
||||||
|
&.graphic-line-b {stroke: $g9-mountain;}
|
||||||
|
&.graphic-line-c {stroke: $g7-graphite;}
|
||||||
|
}
|
||||||
|
.viz-type-selector--graphic-fill {
|
||||||
|
opacity: 0.035;
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
|
||||||
|
&.graphic-fill-a {fill: $g11-sidewalk;} &.graphic-fill-b {fill: $g9-mountain;}
|
||||||
|
&.graphic-fill-c {fill: $g7-graphite;}
|
||||||
|
}
|
||||||
|
.viz-type-selector--option.active .viz-type-selector--graphic {
|
||||||
|
.viz-type-selector--graphic-line.graphic-line-a {stroke: $c-pool;}
|
||||||
|
.viz-type-selector--graphic-line.graphic-line-b {stroke: $c-dreamsicle;}
|
||||||
|
.viz-type-selector--graphic-line.graphic-line-c {stroke: $c-rainforest;}
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-a {fill: $c-pool;}
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-b {fill: $c-dreamsicle;}
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-c {fill: $c-rainforest;}
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-a,
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-b,
|
||||||
|
.viz-type-selector--graphic-fill.graphic-fill-c {opacity: 0.18;}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.display-options--cell .form-group .nav.nav-tablist {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
flex: 1 0 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-options--footnote {
|
||||||
|
color: $g11-sidewalk;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-style: italic;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 6px;
|
||||||
|
@include no-user-select();
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
$write-data--max-width: 960px;
|
$write-data--max-width: 960px;
|
||||||
$write-data--gutter: 30px;
|
$write-data--gutter: 30px;
|
||||||
$write-data--margin: 18px;
|
$write-data--margin: 18px;
|
||||||
$write-data--input-height: 120px;
|
$write-data--input-height: calc(90vh - 48px - 60px - 36px); // Heights of everything but input height
|
||||||
$write-data--transition: opacity 0.4s ease;
|
$write-data--transition: opacity 0.4s ease;
|
||||||
|
|
||||||
.write-data-form {
|
.write-data-form {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$overlay-controls-height: 50px;
|
$overlay-controls-height: 60px;
|
||||||
$overlay-controls-bg: $g2-kevlar;
|
$overlay-controls-bg: $g2-kevlar;
|
||||||
$overlay-z: 100;
|
$overlay-z: 100;
|
||||||
|
|
||||||
|
@ -33,17 +33,16 @@ $overlay-z: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-controls {
|
.overlay-controls {
|
||||||
padding: 0 16px;
|
padding: 0 18px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex: 0 0 $overlay-controls-height;
|
flex: 0 0 $overlay-controls-height;
|
||||||
width: calc(100% - #{($page-wrapper-padding * 2)});
|
width: 100%;
|
||||||
left: $page-wrapper-padding;
|
left: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
background-color: $g2-kevlar;
|
background-color: $g2-kevlar;
|
||||||
border-radius: $radius $radius 0 0;
|
|
||||||
}
|
}
|
||||||
.overlay-controls--right {
|
.overlay-controls--right {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -74,7 +73,9 @@ $overlay-z: 100;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 16px 0;
|
}
|
||||||
|
.overlay-technology--editor .query-maker--empty {
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.overlay-controls .confirm-buttons {
|
.overlay-controls .confirm-buttons {
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
|
@ -86,8 +87,8 @@ $overlay-z: 100;
|
||||||
}
|
}
|
||||||
.overlay-technology .query-maker {
|
.overlay-technology .query-maker {
|
||||||
flex: 1 0 0%;
|
flex: 1 0 0%;
|
||||||
padding: 0 8px;
|
padding: 0 18px;
|
||||||
border-radius: 0 0 $radius $radius;
|
margin: 0;
|
||||||
background-color: $g2-kevlar;
|
background-color: $g2-kevlar;
|
||||||
}
|
}
|
||||||
.overlay-technology .query-maker--tabs {
|
.overlay-technology .query-maker--tabs {
|
||||||
|
|
Loading…
Reference in New Issue